From b387913d0e76aa7863f8766868cd2fb3b3fffcde Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 29 Nov 2017 22:11:32 -0800 Subject: Remove "Fixture" from the test assembly name --- .../TestData/InstanceTransform/Package.en-us.wxl | 11 +++++ .../TestData/InstanceTransform/Package.wxs | 27 ++++++++++++ .../InstanceTransform/PackageComponents.wxs | 10 +++++ .../TestData/InstanceTransform/data/test.txt | 1 + .../TestData/SimpleModule/Module.en-us.wxl | 10 +++++ .../TestData/SimpleModule/Module.wixproj | 48 ++++++++++++++++++++++ .../TestData/SimpleModule/Module.wxs | 16 ++++++++ .../TestData/SimpleModule/data/test.txt | 1 + .../TestData/SingleFile/Package.en-us.wxl | 11 +++++ .../TestData/SingleFile/Package.wxs | 21 ++++++++++ .../TestData/SingleFile/PackageComponents.wxs | 10 +++++ .../TestData/SingleFile/data/test.txt | 1 + 12 files changed, 167 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs new file mode 100644 index 00000000..9c529668 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl new file mode 100644 index 00000000..c74e86a7 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl @@ -0,0 +1,10 @@ + + + + + + Example Company + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj new file mode 100644 index 00000000..597d4318 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj @@ -0,0 +1,48 @@ + + + + Debug + x86 + 0.9 + 27df04c6-3cef-4b9a-bac6-4e78d188384f + MergeModule1 + Module + MergeModule1 + MergeModule1 + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + Debug + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + Debug + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + + + + + + + + + + FgwepExtension.wixext + $(WixExtDir)\FgwepExtension.wixext.dll + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs new file mode 100644 index 00000000..260339ba --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs new file mode 100644 index 00000000..cdc323ec --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file -- cgit v1.2.3-55-g6feb From 720c4a0db1a2fb2aa3e08e5c99d5198873e448ba Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 1 Dec 2017 01:09:42 -0800 Subject: Introduce ExampleExtension for testing --- .../ExtensibilityServices/ParseHelper.cs | 5 ++ .../TupleDefinitionCreator.cs | 15 ++++ .../Example.Extension/Example.Extension.csproj | 18 +++++ .../Example.Extension/ExampleCompilerExtension.cs | 84 ++++++++++++++++++++++ src/test/Example.Extension/ExampleExtensionData.cs | 33 +++++++++ .../Example.Extension/ExampleExtensionFactory.cs | 32 +++++++++ .../ExamplePreprocessorExtension.cs | 55 ++++++++++++++ src/test/Example.Extension/ExampleTuple.cs | 31 ++++++++ src/test/Example.Extension/TupleDefinitions.cs | 18 +++++ .../ExtensionFixture.cs | 60 ++++++++++++++++ .../TestData/ExampleExtension/Package.en-us.wxl | 11 +++ .../TestData/ExampleExtension/Package.wxs | 21 ++++++ .../ExampleExtension/PackageComponents.wxs | 12 ++++ .../TestData/ExampleExtension/data/example.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 5 ++ 15 files changed, 401 insertions(+) create mode 100644 src/test/Example.Extension/Example.Extension.csproj create mode 100644 src/test/Example.Extension/ExampleCompilerExtension.cs create mode 100644 src/test/Example.Extension/ExampleExtensionData.cs create mode 100644 src/test/Example.Extension/ExampleExtensionFactory.cs create mode 100644 src/test/Example.Extension/ExamplePreprocessorExtension.cs create mode 100644 src/test/Example.Extension/ExampleTuple.cs create mode 100644 src/test/Example.Extension/TupleDefinitions.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs index 87ad0da8..8a67efe9 100644 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs @@ -633,6 +633,11 @@ namespace WixToolset.Core.ExtensibilityServices } } + public SourceLineNumber GetSourceLineNumbers(XElement element) + { + return Preprocessor.GetSourceLineNumbers(element); + } + public string GetConditionInnerText(XElement element) { var value = Common.GetInnerText(element)?.Trim().Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '); diff --git a/src/WixToolset.Core/ExtensibilityServices/TupleDefinitionCreator.cs b/src/WixToolset.Core/ExtensibilityServices/TupleDefinitionCreator.cs index 4075def8..b442da2b 100644 --- a/src/WixToolset.Core/ExtensibilityServices/TupleDefinitionCreator.cs +++ b/src/WixToolset.Core/ExtensibilityServices/TupleDefinitionCreator.cs @@ -19,8 +19,16 @@ namespace WixToolset.Core.ExtensibilityServices private IEnumerable ExtensionData { get; set; } + private Dictionary CustomDefinitionByName { get; } = new Dictionary(); + + public void AddCustomTupleDefinition(IntermediateTupleDefinition definition) + { + this.CustomDefinitionByName.Add(definition.Name, definition); + } + public bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition) { + // First, look in the built-ins. tupleDefinition = TupleDefinitions.ByName(name); if (tupleDefinition == null) @@ -30,6 +38,7 @@ namespace WixToolset.Core.ExtensibilityServices this.LoadExtensionData(); } + // Second, look in the extensions. foreach (var data in this.ExtensionData) { if (data.TryGetTupleDefinitionByName(name, out tupleDefinition)) @@ -37,6 +46,12 @@ namespace WixToolset.Core.ExtensibilityServices break; } } + + // Finally, look in the custom tuple definitions provided during an intermediate load. + if (tupleDefinition == null) + { + this.CustomDefinitionByName.TryGetValue(name, out tupleDefinition); + } } return tupleDefinition != null; diff --git a/src/test/Example.Extension/Example.Extension.csproj b/src/test/Example.Extension/Example.Extension.csproj new file mode 100644 index 00000000..80c64b25 --- /dev/null +++ b/src/test/Example.Extension/Example.Extension.csproj @@ -0,0 +1,18 @@ + + + + + + netstandard2.0 + false + + + + + + + + + + + diff --git a/src/test/Example.Extension/ExampleCompilerExtension.cs b/src/test/Example.Extension/ExampleCompilerExtension.cs new file mode 100644 index 00000000..5b20e48f --- /dev/null +++ b/src/test/Example.Extension/ExampleCompilerExtension.cs @@ -0,0 +1,84 @@ +// 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 Example.Extension +{ + using System; + using System.Collections.Generic; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class ExampleCompilerExtension : BaseCompilerExtension + { + public ExampleCompilerExtension() + { + this.Namespace = "http://www.example.com/scheams/v1/wxs"; + } + + public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) + { + var processed = false; + + switch (parentElement.Name.LocalName) + { + case "Component": + switch (element.Name.LocalName) + { + case "Example": + this.ParseExampleElement(intermediate, section, element); + processed = true; + break; + } + break; + } + + if (!processed) + { + base.ParseElement(intermediate, section, parentElement, element, context); + } + } + + private void ParseExampleElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + string value = null; + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + + case "Value": + value = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseAttribute(intermediate, section, element, attrib, null); + } + } + + if (null == id) + { + //this.Messaging(WixErrors.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + var tuple = this.ParseHelper.CreateRow(section, sourceLineNumbers, "Example", id); + tuple.Set(1, value); + } + } + } +} diff --git a/src/test/Example.Extension/ExampleExtensionData.cs b/src/test/Example.Extension/ExampleExtensionData.cs new file mode 100644 index 00000000..c3cb0473 --- /dev/null +++ b/src/test/Example.Extension/ExampleExtensionData.cs @@ -0,0 +1,33 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class ExampleExtensionData : IExtensionData + { + public string DefaultCulture => null; + + public Intermediate GetLibrary(ITupleDefinitionCreator tupleDefinitions) + { + return null; + } + + public bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition) + { + switch (name) + { + case "Example": + tupleDefinition = TupleDefinitions.Example; + break; + + default: + tupleDefinition = null; + break; + } + + return tupleDefinition != null; + } + } +} \ No newline at end of file diff --git a/src/test/Example.Extension/ExampleExtensionFactory.cs b/src/test/Example.Extension/ExampleExtensionFactory.cs new file mode 100644 index 00000000..9539ee85 --- /dev/null +++ b/src/test/Example.Extension/ExampleExtensionFactory.cs @@ -0,0 +1,32 @@ +// 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 Example.Extension +{ + using System; + using WixToolset.Extensibility; + + public class ExampleExtensionFactory : IExtensionFactory + { + public bool TryCreateExtension(Type extensionType, out object extension) + { + if (extensionType == typeof(IPreprocessorExtension)) + { + extension = new ExamplePreprocessorExtension(); + } + else if (extensionType == typeof(ICompilerExtension)) + { + extension = new ExampleCompilerExtension(); + } + else if (extensionType == typeof(IExtensionData)) + { + extension = new ExampleExtensionData(); + } + else + { + extension = null; + } + + return extension != null; + } + } +} diff --git a/src/test/Example.Extension/ExamplePreprocessorExtension.cs b/src/test/Example.Extension/ExamplePreprocessorExtension.cs new file mode 100644 index 00000000..c16c8b5a --- /dev/null +++ b/src/test/Example.Extension/ExamplePreprocessorExtension.cs @@ -0,0 +1,55 @@ +// 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 Example.Extension +{ + using System; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class ExamplePreprocessorExtension : IPreprocessorExtension + { + public ExamplePreprocessorExtension() + { + } + + public IPreprocessorCore Core { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public string[] Prefixes => throw new NotImplementedException(); + + public string EvaluateFunction(string prefix, string function, string[] args) + { + throw new NotImplementedException(); + } + + public void Finish() + { + throw new NotImplementedException(); + } + + public string GetVariableValue(string prefix, string name) + { + throw new NotImplementedException(); + } + + public void Initialize() + { + throw new NotImplementedException(); + } + + public void PreprocessDocument(XDocument document) + { + throw new NotImplementedException(); + } + + public string PreprocessParameter(string name) + { + throw new NotImplementedException(); + } + + public bool ProcessPragma(SourceLineNumber sourceLineNumbers, string prefix, string pragma, string args, XContainer parent) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/test/Example.Extension/ExampleTuple.cs b/src/test/Example.Extension/ExampleTuple.cs new file mode 100644 index 00000000..f280a5c8 --- /dev/null +++ b/src/test/Example.Extension/ExampleTuple.cs @@ -0,0 +1,31 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + + public enum ExampleTupleFields + { + Example, + Value, + } + + public class ExampleTuple : IntermediateTuple + { + public ExampleTuple() : base(TupleDefinitions.Example, null, null) + { + } + + public ExampleTuple(SourceLineNumber sourceLineNumber, Identifier id = null) : base(TupleDefinitions.Example, sourceLineNumber, id) + { + } + + public IntermediateField this[ExampleTupleFields index] => this.Fields[(int)index]; + + public string Value + { + get => this.Fields[(int)ExampleTupleFields.Value]?.AsString(); + set => this.Set((int)ExampleTupleFields.Value, value); + } + } +} diff --git a/src/test/Example.Extension/TupleDefinitions.cs b/src/test/Example.Extension/TupleDefinitions.cs new file mode 100644 index 00000000..2c320fbc --- /dev/null +++ b/src/test/Example.Extension/TupleDefinitions.cs @@ -0,0 +1,18 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + + public static class TupleDefinitions + { + public static readonly IntermediateTupleDefinition Example = new IntermediateTupleDefinition( + "Example", + new[] + { + new IntermediateFieldDefinition(nameof(ExampleTupleFields.Example), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(ExampleTupleFields.Value), IntermediateFieldType.String), + }, + typeof(ExampleTuple)); + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs new file mode 100644 index 00000000..5181c748 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs @@ -0,0 +1,60 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using Example.Extension; + using WixToolset.Core; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolsetTest.CoreIntegration.Utility; + using Xunit; + + public class ExtensionFixture + { + [Fact] + public void CanBuildWithExampleExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var program = new Program(); + var result = program.Run(new WixToolsetServiceProvider(), new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") + }); + + Assert.Equal(0, result); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\example.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wir")); + Assert.Single(intermediate.Sections); + + var wixFile = intermediate.Sections.SelectMany(s => s.Tuples).OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + + var example = intermediate.Sections.SelectMany(s => s.Tuples).Where(t => t.Definition.Type == TupleDefinitionType.MustBeFromAnExtension).Single(); + Assert.Equal("Foo", example.Id.Id); + Assert.Equal("Foo", example[0].AsString()); + Assert.Equal("Bar", example[1].AsString()); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs new file mode 100644 index 00000000..cdc323ec --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs new file mode 100644 index 00000000..7f17b538 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index f9042cda..ede5967f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -15,6 +15,10 @@ + + + + @@ -23,6 +27,7 @@ + -- cgit v1.2.3-55-g6feb From 95f2f4425b900374c7d7b583ae810b096121b3c4 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 2 Dec 2017 00:46:11 -0800 Subject: Implement support for IExtensionCommandLine and IPreprocessorExtension --- src/WixToolset.Core/CommandLine/BuildCommand.cs | 15 +- src/WixToolset.Core/CommandLine/CommandLine.cs | 29 +- src/WixToolset.Core/CommandLine/CompileCommand.cs | 13 +- src/WixToolset.Core/Compiler.cs | 7 +- .../ExtensibilityServices/PreprocessHelper.cs | 479 ++++++++++++++++++ .../Preprocess/PreprocessorOperation.cs | 19 + src/WixToolset.Core/PreprocessContext.cs | 36 ++ src/WixToolset.Core/Preprocessor.cs | 453 ++++++----------- src/WixToolset.Core/PreprocessorCore.cs | 560 --------------------- src/WixToolset.Core/WixToolsetServiceProvider.cs | 11 + .../Example.Extension/ExampleExtensionFactory.cs | 11 +- .../ExamplePreprocessorExtension.cs | 55 -- .../ExamplePreprocessorExtensionAndCommandLine.cs | 46 ++ .../ExtensionFixture.cs | 43 ++ .../TestData/ExampleExtension/Package.wxs | 2 + 15 files changed, 842 insertions(+), 937 deletions(-) create mode 100644 src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs create mode 100644 src/WixToolset.Core/Preprocess/PreprocessorOperation.cs create mode 100644 src/WixToolset.Core/PreprocessContext.cs delete mode 100644 src/WixToolset.Core/PreprocessorCore.cs delete mode 100644 src/test/Example.Extension/ExamplePreprocessorExtension.cs create mode 100644 src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index 54bf688d..79bacd22 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -40,6 +40,8 @@ namespace WixToolset.Core public IExtensionManager ExtensionManager { get; } + public IEnumerable IncludeSearchPaths { get; } + public IEnumerable LocFiles { get; } public IEnumerable LibraryFiles { get; } @@ -102,15 +104,18 @@ namespace WixToolset.Core { var intermediates = new List(); - foreach (var sourceFile in this.SourceFiles) { - //var preprocessContext = this.ServiceProvider.GetService(); - //preprocessContext.SourcePath = sourceFile.SourcePath; - //preprocessContext.Variables = this.PreprocessorVariables; + var preprocessContext = this.ServiceProvider.GetService(); + preprocessContext.Messaging = Messaging.Instance; + preprocessContext.Extensions = this.ExtensionManager.Create(); + preprocessContext.Platform = Platform.X86; // TODO: set this correctly + preprocessContext.IncludeSearchPaths = this.IncludeSearchPaths?.ToList() ?? new List(); + preprocessContext.SourceFile = sourceFile.SourcePath; + preprocessContext.Variables = new Dictionary(this.PreprocessorVariables); var preprocessor = new Preprocessor(); - var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables); + var document = preprocessor.Process(preprocessContext); var compileContext = this.ServiceProvider.GetService(); compileContext.Messaging = Messaging.Instance; diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs index c6fe11b7..9bedca9a 100644 --- a/src/WixToolset.Core/CommandLine/CommandLine.cs +++ b/src/WixToolset.Core/CommandLine/CommandLine.cs @@ -22,7 +22,7 @@ namespace WixToolset.Core Bind, } - internal class CommandLine : ICommandLine + internal class CommandLine : ICommandLine, IParseCommandLine { public CommandLine() { @@ -57,10 +57,10 @@ namespace WixToolset.Core args = CommandLine.ParseArgumentsToArray(context.Arguments).Union(args).ToArray(); } - return this.ParseStandardCommandLine(args); + return this.ParseStandardCommandLine(context, args); } - private ICommandLineCommand ParseStandardCommandLine(string[] args) + private ICommandLineCommand ParseStandardCommandLine(ICommandLineContext context, string[] args) { var next = String.Empty; @@ -90,7 +90,7 @@ namespace WixToolset.Core var builtOutputsFile = String.Empty; var wixProjectFile = String.Empty; - this.Parse(args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) => + this.Parse(context, args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) => { if (cmdline.IsSwitch(arg)) { @@ -279,13 +279,13 @@ namespace WixToolset.Core } #endif - private ICommandLine Parse(string[] commandLineArguments, Func parseCommand, Func parseArgument) + private ICommandLine Parse(ICommandLineContext context, string[] commandLineArguments, Func parseCommand, Func parseArgument) { this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments); this.QueueArgumentsAndLoadExtensions(this.OriginalArguments); - this.ProcessRemainingArguments(parseArgument, parseCommand); + this.ProcessRemainingArguments(context, parseArgument, parseCommand); return this; } @@ -413,7 +413,7 @@ namespace WixToolset.Core /// True if a valid switch exists there, false if not. public bool IsSwitch(string arg) { - return arg != null && ('/' == arg[0] || '-' == arg[0]); + return arg != null && arg.Length > 1 && ('/' == arg[0] || '-' == arg[0]); } /// @@ -522,10 +522,15 @@ namespace WixToolset.Core } } - private void ProcessRemainingArguments(Func parseArgument, Func parseCommand) + private void ProcessRemainingArguments(ICommandLineContext context, Func parseArgument, Func parseCommand) { var extensions = this.ExtensionManager.Create(); + foreach (var extension in extensions) + { + extension.PreParse(context); + } + while (!this.ShowHelp && String.IsNullOrEmpty(this.ErrorArgument) && TryDequeue(this.RemainingArguments, out var arg)) @@ -566,10 +571,10 @@ namespace WixToolset.Core { foreach (var extension in extensions) { - //if (extension.ParseArgument(this, arg)) - //{ - // return true; - //} + if (extension.TryParseArgument(this, arg)) + { + return true; + } } return false; diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs index 58ba9d29..e7fcdd4d 100644 --- a/src/WixToolset.Core/CommandLine/CompileCommand.cs +++ b/src/WixToolset.Core/CommandLine/CompileCommand.cs @@ -4,6 +4,7 @@ namespace WixToolset.Core { using System; using System.Collections.Generic; + using System.Linq; using WixToolset.Data; using WixToolset.Extensibility; using WixToolset.Extensibility.Services; @@ -22,6 +23,8 @@ namespace WixToolset.Core private IExtensionManager ExtensionManager { get; } + public IEnumerable IncludeSearchPaths { get; } + private IEnumerable SourceFiles { get; } private IDictionary PreprocessorVariables { get; } @@ -30,8 +33,16 @@ namespace WixToolset.Core { foreach (var sourceFile in this.SourceFiles) { + var preprocessContext = this.ServiceProvider.GetService(); + preprocessContext.Messaging = Messaging.Instance; + preprocessContext.Extensions = this.ExtensionManager.Create(); + preprocessContext.Platform = Platform.X86; // TODO: set this correctly + preprocessContext.IncludeSearchPaths = this.IncludeSearchPaths?.ToList() ?? new List(); + preprocessContext.SourceFile = sourceFile.SourcePath; + preprocessContext.Variables = new Dictionary(this.PreprocessorVariables); + var preprocessor = new Preprocessor(); - var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables); + var document = preprocessor.Process(preprocessContext); var compileContext = this.ServiceProvider.GetService(); compileContext.Messaging = Messaging.Instance; diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 406bc46a..1c1c2f0a 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -10825,9 +10825,10 @@ namespace WixToolset.Core switch (installScopeType) { case Wix.Package.InstallScopeType.perMachine: - row = this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Property); - row.Set(0, "ALLUSERS"); - row.Set(1, "1"); + { + row = this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Property, new Identifier("ALLUSERS", AccessModifier.Public)); + row.Set(1, "1"); + } break; case Wix.Package.InstallScopeType.perUser: sourceBits = sourceBits | 8; diff --git a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs new file mode 100644 index 00000000..3b8011c4 --- /dev/null +++ b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs @@ -0,0 +1,479 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class PreprocessHelper : IPreprocessHelper + { + private static readonly char[] VariableSplitter = new char[] { '.' }; + private static readonly char[] ArgumentSplitter = new char[] { ',' }; + + public PreprocessHelper(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + private IServiceProvider ServiceProvider { get; } + + private Dictionary ExtensionsByPrefix { get; set; } + + public void AddVariable(IPreprocessContext context, string name, string value) + { + this.AddVariable(context, name, value, true); + } + + public void AddVariable(IPreprocessContext context, string name, string value, bool showWarning) + { + var currentValue = this.GetVariableValue(context, "var", name); + + if (null == currentValue) + { + context.Variables.Add(name, value); + } + else + { + if (showWarning) + { + context.Messaging.OnMessage(WixWarnings.VariableDeclarationCollision(context.CurrentSourceLineNumber, name, value, currentValue)); + } + + context.Variables[name] = value; + } + } + + public string EvaluateFunction(IPreprocessContext context, string function) + { + var prefixParts = function.Split(VariableSplitter, 2); + + // Check to make sure there are 2 parts and neither is an empty string. + if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length) + { + throw new WixException(WixErrors.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); + } + + var prefix = prefixParts[0]; + var functionParts = prefixParts[1].Split(new char[] { '(' }, 2); + + // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren. + if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal)) + { + throw new WixException(WixErrors.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); + } + + var functionName = functionParts[0]; + + // Remove the trailing closing paren. + var allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1); + + // Parse the arguments and preprocess them. + var args = allArgs.Split(ArgumentSplitter); + for (var i = 0; i < args.Length; i++) + { + args[i] = this.PreprocessString(context, args[i].Trim()); + } + + var result = this.EvaluateFunction(context, prefix, functionName, args); + + // If the function didn't evaluate, try to evaluate the original value as a variable to support + // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve. + if (result == null) + { + result = this.GetVariableValue(context, function, false); + } + + return result; + } + + public string EvaluateFunction(IPreprocessContext context, string prefix, string function, string[] args) + { + if (String.IsNullOrEmpty(prefix)) + { + throw new ArgumentNullException("prefix"); + } + + if (String.IsNullOrEmpty(function)) + { + throw new ArgumentNullException("function"); + } + + switch (prefix) + { + case "fun": + switch (function) + { + case "AutoVersion": + // Make sure the base version is specified + if (args.Length == 0 || String.IsNullOrEmpty(args[0])) + { + throw new WixException(WixErrors.InvalidPreprocessorFunctionAutoVersion(context.CurrentSourceLineNumber)); + } + + // Build = days since 1/1/2000; Revision = seconds since midnight / 2 + var now = DateTime.UtcNow; + var build = now - new DateTime(2000, 1, 1); + var revision = now - new DateTime(now.Year, now.Month, now.Day); + + return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2)); + + default: + return null; + } + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(context); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + try + { + return extension.EvaluateFunction(prefix, function, args); + } + catch (Exception e) + { + throw new WixException(WixErrors.PreprocessorExtensionEvaluateFunctionFailed(context.CurrentSourceLineNumber, prefix, function, String.Join(",", args), e.Message)); + } + } + else + { + return null; + } + } + } + + public string GetVariableValue(IPreprocessContext context, string variable, bool allowMissingPrefix) + { + // Strip the "$(" off the front. + if (variable.StartsWith("$(", StringComparison.Ordinal)) + { + variable = variable.Substring(2); + } + + var parts = variable.Split(VariableSplitter, 2); + + if (1 == parts.Length) // missing prefix + { + if (allowMissingPrefix) + { + return this.GetVariableValue(context, "var", parts[0]); + } + else + { + throw new WixException(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); + } + } + else + { + // check for empty variable name + if (0 < parts[1].Length) + { + string result = this.GetVariableValue(context, parts[0], parts[1]); + + // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix + if (null == result && allowMissingPrefix && variable.Contains(".")) + { + result = this.GetVariableValue(context, "var", variable); + } + + return result; + } + else + { + throw new WixException(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); + } + } + } + + public string GetVariableValue(IPreprocessContext context, string prefix, string name) + { + if (String.IsNullOrEmpty(prefix)) + { + throw new ArgumentNullException("prefix"); + } + + if (String.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + switch (prefix) + { + case "env": + return Environment.GetEnvironmentVariable(name); + + case "sys": + switch (name) + { + case "CURRENTDIR": + return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar); + + case "SOURCEFILEDIR": + return String.Concat(Path.GetDirectoryName(context.CurrentSourceLineNumber.FileName), Path.DirectorySeparatorChar); + + case "SOURCEFILEPATH": + return context.CurrentSourceLineNumber.FileName; + + case "PLATFORM": + context.Messaging.OnMessage(WixWarnings.DeprecatedPreProcVariable(context.CurrentSourceLineNumber, "$(sys.PLATFORM)", "$(sys.BUILDARCH)")); + + goto case "BUILDARCH"; + + case "BUILDARCH": + switch (context.Platform) + { + case Platform.X86: + return "x86"; + + case Platform.X64: + return "x64"; + + case Platform.IA64: + return "ia64"; + + case Platform.ARM: + return "arm"; + + default: + throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, context.Platform.ToString()); + } + + default: + return null; + } + + case "var": + return context.Variables.TryGetValue(name, out var result) ? result : null; + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(context); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + try + { + return extension.GetVariableValue(prefix, name); + } + catch (Exception e) + { + throw new WixException(WixErrors.PreprocessorExtensionGetVariableValueFailed(context.CurrentSourceLineNumber, prefix, name, e.Message)); + } + } + else + { + return null; + } + } + } + + public void PreprocessPragma(IPreprocessContext context, string pragmaName, string args, XContainer parent) + { + var prefixParts = pragmaName.Split(VariableSplitter, 2); + + // Check to make sure there are 2 parts and neither is an empty string. + if (2 != prefixParts.Length) + { + throw new WixException(WixErrors.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); + } + + var prefix = prefixParts[0]; + var pragma = prefixParts[1]; + + if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma)) + { + throw new WixException(WixErrors.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); + } + + switch (prefix) + { + case "wix": + switch (pragma) + { + // Add any core defined pragmas here + default: + context.Messaging.OnMessage(WixWarnings.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); + break; + } + break; + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(context); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + if (!extension.ProcessPragma(prefix, pragma, args, parent)) + { + context.Messaging.OnMessage(WixWarnings.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); + } + } + break; + } + } + + public string PreprocessString(IPreprocessContext context, string value) + { + var sb = new StringBuilder(); + var currentPosition = 0; + var end = 0; + + while (-1 != (currentPosition = value.IndexOf('$', end))) + { + if (end < currentPosition) + { + sb.Append(value, end, currentPosition - end); + } + + end = currentPosition + 1; + + var remainder = value.Substring(end); + if (remainder.StartsWith("$", StringComparison.Ordinal)) + { + sb.Append("$"); + end++; + } + else if (remainder.StartsWith("(loc.", StringComparison.Ordinal)) + { + currentPosition = remainder.IndexOf(')'); + if (-1 == currentPosition) + { + context.Messaging.OnMessage(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); + break; + } + + sb.Append("$"); // just put the resource reference back as was + sb.Append(remainder, 0, currentPosition + 1); + + end += currentPosition + 1; + } + else if (remainder.StartsWith("(", StringComparison.Ordinal)) + { + var openParenCount = 1; + var closingParenCount = 0; + var isFunction = false; + var foundClosingParen = false; + + // find the closing paren + int closingParenPosition; + for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++) + { + switch (remainder[closingParenPosition]) + { + case '(': + openParenCount++; + isFunction = true; + break; + + case ')': + closingParenCount++; + break; + } + + if (openParenCount == closingParenCount) + { + foundClosingParen = true; + break; + } + } + + // move the currentPosition to the closing paren + currentPosition += closingParenPosition; + + if (!foundClosingParen) + { + if (isFunction) + { + context.Messaging.OnMessage(WixErrors.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, remainder)); + break; + } + else + { + context.Messaging.OnMessage(WixErrors.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); + break; + } + } + + var subString = remainder.Substring(1, closingParenPosition - 1); + string result = null; + if (isFunction) + { + result = this.EvaluateFunction(context, subString); + } + else + { + result = this.GetVariableValue(context, subString, false); + } + + if (null == result) + { + if (isFunction) + { + context.Messaging.OnMessage(WixErrors.UndefinedPreprocessorFunction(context.CurrentSourceLineNumber, subString)); + break; + } + else + { + context.Messaging.OnMessage(WixErrors.UndefinedPreprocessorVariable(context.CurrentSourceLineNumber, subString)); + break; + } + } + else + { + if (!isFunction) + { + //this.OnResolvedVariable(new ResolvedVariableEventArgs(context.CurrentSourceLineNumber, subString, result)); + } + } + + sb.Append(result); + end += closingParenPosition + 1; + } + else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing + { + sb.Append('$'); + } + } + + if (end < value.Length) + { + sb.Append(value.Substring(end)); + } + + return sb.ToString(); + } + + public void RemoveVariable(IPreprocessContext context, string name) + { + if (!context.Variables.Remove(name)) + { + context.Messaging.OnMessage(WixErrors.CannotReundefineVariable(context.CurrentSourceLineNumber, name)); + } + } + + private Dictionary GetExtensionsByPrefix(IPreprocessContext context) + { + if (this.ExtensionsByPrefix == null) + { + this.ExtensionsByPrefix = new Dictionary(); + + foreach (var extension in context.Extensions) + { + if (null != extension.Prefixes) + { + foreach (string prefix in extension.Prefixes) + { + if (!this.ExtensionsByPrefix.ContainsKey(prefix)) + { + this.ExtensionsByPrefix.Add(prefix, extension); + } + } + } + } + } + + return this.ExtensionsByPrefix; + } + } +} diff --git a/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs b/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs new file mode 100644 index 00000000..086a0f1a --- /dev/null +++ b/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs @@ -0,0 +1,19 @@ +// 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.Preprocess +{ + /// + /// Enumeration for preprocessor operations in if statements. + /// + internal enum PreprocessorOperation + { + /// The and operator. + And, + + /// The or operator. + Or, + + /// The not operator. + Not + } +} diff --git a/src/WixToolset.Core/PreprocessContext.cs b/src/WixToolset.Core/PreprocessContext.cs new file mode 100644 index 00000000..c0acc31e --- /dev/null +++ b/src/WixToolset.Core/PreprocessContext.cs @@ -0,0 +1,36 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility; + + /// + /// The preprocessor core. + /// + internal class PreprocessContext : IPreprocessContext + { + internal PreprocessContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public Messaging Messaging { get; set; } + + public IEnumerable Extensions { get; set; } + + public Platform Platform { get; set; } + + public IList IncludeSearchPaths { get; set; } + + public string SourceFile { get; set; } + + public IDictionary Variables { get; set; } + + public SourceLineNumber CurrentSourceLineNumber { get; set; } + } +} diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs index 3aed0735..195ede9e 100644 --- a/src/WixToolset.Core/Preprocessor.cs +++ b/src/WixToolset.Core/Preprocessor.cs @@ -4,7 +4,6 @@ namespace WixToolset.Core { using System; using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Text; @@ -14,6 +13,7 @@ namespace WixToolset.Core using WixToolset.Data; using WixToolset.Extensibility; using WixToolset.Core.Preprocess; + using WixToolset.Extensibility.Services; /// /// Preprocessor object @@ -30,42 +30,22 @@ namespace WixToolset.Core }; private readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings() { - ConformanceLevel = System.Xml.ConformanceLevel.Fragment, + ConformanceLevel = ConformanceLevel.Fragment, ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, XmlResolver = null, }; - private List extensions; - private Dictionary extensionsByPrefix; + private IPreprocessContext Context { get; set; } - private SourceLineNumber currentLineNumber; - private Stack sourceStack; + private Stack CurrentFileStack { get; } = new Stack(); - private PreprocessorCore core; - private TextWriter preprocessOut; + private Dictionary ExtensionsByPrefix { get; } = new Dictionary(); - private Stack includeNextStack; - private Stack currentFileStack; + private Stack IncludeNextStack { get; } = new Stack(); - private Platform currentPlatform; + private Stack SourceStack { get; } = new Stack(); - /// - /// Creates a new preprocesor. - /// - public Preprocessor() - { - this.IncludeSearchPaths = new List(); - - this.extensions = new List(); - this.extensionsByPrefix = new Dictionary(); - - this.sourceStack = new Stack(); - - this.includeNextStack = new Stack(); - this.currentFileStack = new Stack(); - - this.currentPlatform = Platform.X86; - } + private IPreprocessHelper Helper { get; set; } /// /// Event for ifdef/ifndef directives. @@ -87,62 +67,6 @@ namespace WixToolset.Core /// public event ResolvedVariableEventHandler ResolvedVariable; - /// - /// Enumeration for preprocessor operations in if statements. - /// - private enum PreprocessorOperation - { - /// The and operator. - And, - - /// The or operator. - Or, - - /// The not operator. - Not - } - - /// - /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements. - /// - /// The platform which the compiler will use when defaulting 64-bit attributes and elements. - public Platform CurrentPlatform - { - get { return this.currentPlatform; } - set { this.currentPlatform = value; } - } - - /// - /// Ordered list of search paths that the precompiler uses to find included files. - /// - /// List of ordered search paths to use during precompiling. - public IList IncludeSearchPaths { get; private set; } - - /// - /// Specifies the text stream to display the postprocessed data to. - /// - /// TextWriter to write preprocessed xml to. - public TextWriter PreprocessOut - { - get { return this.preprocessOut; } - set { this.preprocessOut = value; } - } - - /// - /// Get the source line information for the current element. The precompiler will insert - /// special source line number processing instructions before each element that it - /// encounters. This is where those line numbers are read and processed. This function - /// may return an array of source line numbers because the element may have come from - /// an included file, in which case the chain of imports is expressed in the array. - /// - /// Element to get source line information for. - /// Returns the stack of imports used to author the element being processed. - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] - public static SourceLineNumber GetSourceLineNumbers(XmlNode node) - { - return null; - } - /// /// Get the source line information for the current element. The precompiler will insert /// special source line number information for each element that it encounters. @@ -159,122 +83,94 @@ namespace WixToolset.Core } /// - /// Adds an extension. + /// Preprocesses a file. /// - /// The extension to add. - public void AddExtension(IPreprocessorExtension extension) + /// The preprocessing context. + /// XDocument with the postprocessed data. + public XDocument Process(IPreprocessContext context) { - this.extensions.Add(extension); + this.Context = context ?? throw new ArgumentNullException(nameof(context)); - if (null != extension.Prefixes) + using (XmlReader reader = XmlReader.Create(context.SourceFile, DocumentXmlReaderSettings)) { - foreach (string prefix in extension.Prefixes) - { - IPreprocessorExtension collidingExtension; - if (!this.extensionsByPrefix.TryGetValue(prefix, out collidingExtension)) - { - this.extensionsByPrefix.Add(prefix, extension); - } - else - { - Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString())); - } - } + return Process(context, reader); } - - //if (null != extension.InspectorExtension) - //{ - // this.inspectorExtensions.Add(extension.InspectorExtension); - //} } /// /// Preprocesses a file. /// - /// The file to preprocess. - /// The variables defined prior to preprocessing. + /// The preprocessing context. + /// XmlReader to processing the context. /// XDocument with the postprocessed data. - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] - public XDocument Process(string sourceFile, IDictionary variables) + public XDocument Process(IPreprocessContext context, XmlReader reader) { - using (Stream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (XmlReader reader = XmlReader.Create(sourceFile, DocumentXmlReaderSettings)) + if (this.Context == null) { - return Process(reader, variables, sourceFile); + this.Context = context ?? throw new ArgumentNullException(nameof(context)); + } + else if (this.Context != context) + { + throw new ArgumentException(nameof(context)); } - } - /// - /// Preprocesses a file. - /// - /// The file to preprocess. - /// The variables defined prior to preprocessing. - /// XDocument with the postprocessed data. - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] - public XDocument Process(XmlReader reader, IDictionary variables, string sourceFile = null) - { - if (String.IsNullOrEmpty(sourceFile) && !String.IsNullOrEmpty(reader.BaseURI)) + if (String.IsNullOrEmpty(this.Context.SourceFile) && !String.IsNullOrEmpty(reader.BaseURI)) { - Uri uri = new Uri(reader.BaseURI); - sourceFile = uri.AbsolutePath; + var uri = new Uri(reader.BaseURI); + this.Context.SourceFile = uri.AbsolutePath; } - this.core = new PreprocessorCore(this.extensionsByPrefix, sourceFile, variables); - this.core.ResolvedVariableHandler = this.ResolvedVariable; - this.core.CurrentPlatform = this.currentPlatform; - this.currentLineNumber = new SourceLineNumber(sourceFile); - this.currentFileStack.Clear(); - this.currentFileStack.Push(this.core.GetVariableValue(this.currentLineNumber, "sys", "SOURCEFILEDIR")); + this.Context.CurrentSourceLineNumber = new SourceLineNumber(this.Context.SourceFile); - // Process the reader into the output. - XDocument output = new XDocument(); - try + this.Helper = this.Context.ServiceProvider.GetService(); + + foreach (var extension in this.Context.Extensions) { - foreach (PreprocessorExtension extension in this.extensions) + if (null != extension.Prefixes) { - extension.Core = this.core; - extension.Initialize(); + foreach (string prefix in extension.Prefixes) + { + if (!this.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension)) + { + this.ExtensionsByPrefix.Add(prefix, extension); + } + else + { + this.Context.Messaging.OnMessage(WixErrors.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString())); + } + } } - this.PreprocessReader(false, reader, output, 0); - } - catch (XmlException e) - { - this.UpdateCurrentLineNumber(reader, 0); - throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message)); + extension.PrePreprocess(context); } - // Fire event with post-processed document. - ProcessedStreamEventArgs args = new ProcessedStreamEventArgs(sourceFile, output); - this.OnProcessedStream(args); + this.CurrentFileStack.Clear(); + this.CurrentFileStack.Push(this.Helper.GetVariableValue(this.Context, "sys", "SOURCEFILEDIR")); - // preprocess the generated XML Document - foreach (PreprocessorExtension extension in this.extensions) + // Process the reader into the output. + XDocument output = new XDocument(); + try { - extension.PreprocessDocument(output); - } + this.PreprocessReader(false, reader, output, 0); - // finalize the preprocessing - foreach (PreprocessorExtension extension in this.extensions) - { - extension.Finish(); - extension.Core = null; + // Fire event with post-processed document. + this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(this.Context.SourceFile, output)); } - - if (this.core.EncounteredError) + catch (XmlException e) { - return null; + this.UpdateCurrentLineNumber(reader, 0); + throw new WixException(WixErrors.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message)); } - else + finally { - if (null != this.preprocessOut) + // Finalize the preprocessing. + foreach (var extension in this.Context.Extensions) { - output.Save(this.preprocessOut); - this.preprocessOut.Flush(); + extension.PostPreprocess(output); } - - return output; } + + return this.Context.Messaging.EncounteredError ? null : output; } /// @@ -339,42 +235,6 @@ namespace WixToolset.Core return true; } - /// - /// Fires an event when an ifdef/ifndef directive is processed. - /// - /// ifdef/ifndef event arguments. - private void OnIfDef(IfDefEventArgs ea) - { - if (null != this.IfDef) - { - this.IfDef(this, ea); - } - } - - /// - /// Fires an event when an included file is processed. - /// - /// Included file event arguments. - private void OnIncludedFile(IncludedFileEventArgs ea) - { - if (null != this.IncludedFile) - { - this.IncludedFile(this, ea); - } - } - - /// - /// Fires an event after the file is preprocessed. - /// - /// Included file event arguments. - private void OnProcessedStream(ProcessedStreamEventArgs ea) - { - if (null != this.ProcessedStream) - { - this.ProcessedStream(this, ea); - } - } - /// /// Tests expression to see if it starts with a keyword. /// @@ -383,7 +243,7 @@ namespace WixToolset.Core /// true if expression starts with a keyword. private static bool StartsWithKeyword(string expression, PreprocessorOperation operation) { - expression = expression.ToUpper(CultureInfo.InvariantCulture); + expression = expression.ToUpperInvariant(); switch (operation) { case PreprocessorOperation.Not: @@ -431,7 +291,7 @@ namespace WixToolset.Core // update information here in case an error occurs before the next read this.UpdateCurrentLineNumber(reader, offset); - SourceLineNumber sourceLineNumbers = this.currentLineNumber; + var sourceLineNumbers = this.Context.CurrentSourceLineNumber; // check for changes in conditional processing if (XmlNodeType.ProcessingInstruction == reader.NodeType) @@ -459,14 +319,14 @@ namespace WixToolset.Core name = reader.Value.Trim(); if (ifContext.IsTrue) { - ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If); + ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.Helper.GetVariableValue(this.Context, name, true)), IfState.If); } else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true { ifContext = new IfContext(); } ignore = true; - OnIfDef(new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); + this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); break; case "ifndef": @@ -474,25 +334,25 @@ namespace WixToolset.Core name = reader.Value.Trim(); if (ifContext.IsTrue) { - ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If); + ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.Helper.GetVariableValue(this.Context, name, true)), IfState.If); } else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true { ifContext = new IfContext(); } ignore = true; - OnIfDef(new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); + this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); break; case "elseif": if (0 == ifStack.Count) { - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); } if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) { - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); } ifContext.IfState = IfState.ElseIf; // we're now in an elseif @@ -510,12 +370,12 @@ namespace WixToolset.Core case "else": if (0 == ifStack.Count) { - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); } if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) { - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); } ifContext.IfState = IfState.Else; // we're now in an else @@ -526,7 +386,7 @@ namespace WixToolset.Core case "endif": if (0 == ifStack.Count) { - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "endif")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif")); } ifContext = (IfContext)ifStack.Pop(); @@ -606,7 +466,7 @@ namespace WixToolset.Core break; case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error - throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "foreach", "endforeach")); + throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach")); case "pragma": this.PreprocessPragma(reader.Value, currentContainer); @@ -619,31 +479,33 @@ namespace WixToolset.Core break; case XmlNodeType.Element: - if (0 < this.includeNextStack.Count && this.includeNextStack.Peek()) + if (0 < this.IncludeNextStack.Count && this.IncludeNextStack.Peek()) { if ("Include" != reader.LocalName) { - this.core.OnMessage(WixErrors.InvalidDocumentElement(this.currentLineNumber, reader.Name, "include", "Include")); + this.Context.Messaging.OnMessage(WixErrors.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include")); } - this.includeNextStack.Pop(); - this.includeNextStack.Push(false); + this.IncludeNextStack.Pop(); + this.IncludeNextStack.Push(false); break; } - bool empty = reader.IsEmptyElement; - XNamespace ns = XNamespace.Get(reader.NamespaceURI); - XElement element = new XElement(ns + reader.LocalName); + var empty = reader.IsEmptyElement; + var ns = XNamespace.Get(reader.NamespaceURI); + var element = new XElement(ns + reader.LocalName); currentContainer.Add(element); this.UpdateCurrentLineNumber(reader, offset); - element.AddAnnotation(this.currentLineNumber); + element.AddAnnotation(sourceLineNumbers); while (reader.MoveToNextAttribute()) { - string value = this.core.PreprocessString(this.currentLineNumber, reader.Value); - XNamespace attribNamespace = XNamespace.Get(reader.NamespaceURI); + var value = this.Helper.PreprocessString(this.Context, reader.Value); + + var attribNamespace = XNamespace.Get(reader.NamespaceURI); attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; + element.Add(new XAttribute(attribNamespace + reader.LocalName, value)); } @@ -662,12 +524,12 @@ namespace WixToolset.Core break; case XmlNodeType.Text: - string postprocessedText = this.core.PreprocessString(this.currentLineNumber, reader.Value); + string postprocessedText = this.Helper.PreprocessString(this.Context, reader.Value); currentContainer.Add(postprocessedText); break; case XmlNodeType.CDATA: - string postprocessedValue = this.core.PreprocessString(this.currentLineNumber, reader.Value); + string postprocessedValue = this.Helper.PreprocessString(this.Context, reader.Value); currentContainer.Add(new XCData(postprocessedValue)); break; @@ -678,13 +540,13 @@ namespace WixToolset.Core if (0 != ifStack.Count) { - throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "if", "endif")); + throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.Context.CurrentSourceLineNumber, "if", "endif")); } // TODO: can this actually happen? if (0 != containerStack.Count) { - throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "nodes", "nodes")); + throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.Context.CurrentSourceLineNumber, "nodes", "nodes")); } } @@ -694,12 +556,10 @@ namespace WixToolset.Core /// Text from source. private void PreprocessError(string errorMessage) { - SourceLineNumber sourceLineNumbers = this.currentLineNumber; - - // resolve other variables in the error message - errorMessage = this.core.PreprocessString(sourceLineNumbers, errorMessage); + // Resolve other variables in the error message. + errorMessage = this.Helper.PreprocessString(this.Context, errorMessage); - throw new WixException(WixErrors.PreprocessorError(sourceLineNumbers, errorMessage)); + throw new WixException(WixErrors.PreprocessorError(this.Context.CurrentSourceLineNumber, errorMessage)); } /// @@ -708,12 +568,10 @@ namespace WixToolset.Core /// Text from source. private void PreprocessWarning(string warningMessage) { - SourceLineNumber sourceLineNumbers = this.currentLineNumber; - - // resolve other variables in the warning message - warningMessage = this.core.PreprocessString(sourceLineNumbers, warningMessage); + // Resolve other variables in the warning message. + warningMessage = this.Helper.PreprocessString(this.Context, warningMessage); - this.core.OnMessage(WixWarnings.PreprocessorWarning(sourceLineNumbers, warningMessage)); + this.Context.Messaging.OnMessage(WixWarnings.PreprocessorWarning(this.Context.CurrentSourceLineNumber, warningMessage)); } /// @@ -722,16 +580,15 @@ namespace WixToolset.Core /// Text from source. private void PreprocessDefine(string originalDefine) { - Match match = defineRegex.Match(originalDefine); - SourceLineNumber sourceLineNumbers = this.currentLineNumber; + var match = defineRegex.Match(originalDefine); if (!match.Success) { - throw new WixException(WixErrors.IllegalDefineStatement(sourceLineNumbers, originalDefine)); + throw new WixException(WixErrors.IllegalDefineStatement(this.Context.CurrentSourceLineNumber, originalDefine)); } - string defineName = match.Groups["varName"].Value; - string defineValue = match.Groups["varValue"].Value; + var defineName = match.Groups["varName"].Value; + var defineValue = match.Groups["varValue"].Value; // strip off the optional quotes if (1 < defineValue.Length && @@ -742,15 +599,15 @@ namespace WixToolset.Core } // resolve other variables in the variable value - defineValue = this.core.PreprocessString(sourceLineNumbers, defineValue); + defineValue = this.Helper.PreprocessString(this.Context, defineValue); if (defineName.StartsWith("var.", StringComparison.Ordinal)) { - this.core.AddVariable(sourceLineNumbers, defineName.Substring(4), defineValue); + this.Helper.AddVariable(this.Context, defineName.Substring(4), defineValue); } else { - this.core.AddVariable(sourceLineNumbers, defineName, defineValue); + this.Helper.AddVariable(this.Context, defineName, defineValue); } } @@ -760,16 +617,15 @@ namespace WixToolset.Core /// Text from source. private void PreprocessUndef(string originalDefine) { - SourceLineNumber sourceLineNumbers = this.currentLineNumber; - string name = this.core.PreprocessString(sourceLineNumbers, originalDefine.Trim()); + var name = this.Helper.PreprocessString(this.Context, originalDefine.Trim()); if (name.StartsWith("var.", StringComparison.Ordinal)) { - this.core.RemoveVariable(sourceLineNumbers, name.Substring(4)); + this.Helper.RemoveVariable(this.Context, name.Substring(4)); } else { - this.core.RemoveVariable(sourceLineNumbers, name); + this.Helper.RemoveVariable(this.Context, name); } } @@ -780,12 +636,12 @@ namespace WixToolset.Core /// Parent container for included content. private void PreprocessInclude(string includePath, XContainer parent) { - SourceLineNumber sourceLineNumbers = this.currentLineNumber; + var sourceLineNumbers = this.Context.CurrentSourceLineNumber; - // preprocess variables in the path - includePath = this.core.PreprocessString(sourceLineNumbers, includePath); + // Preprocess variables in the path. + includePath = this.Helper.PreprocessString(this.Context, includePath); - string includeFile = this.GetIncludeFile(includePath); + var includeFile = this.GetIncludeFile(includePath); if (null == includeFile) { @@ -807,7 +663,7 @@ namespace WixToolset.Core throw new WixException(WixErrors.InvalidXml(sourceLineNumbers, "source", e.Message)); } - this.OnIncludedFile(new IncludedFileEventArgs(sourceLineNumbers, includeFile)); + this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile)); this.PopInclude(); } @@ -821,11 +677,11 @@ namespace WixToolset.Core /// Offset for the line numbers. private void PreprocessForeach(XmlReader reader, XContainer container, int offset) { - // find the "in" token - int indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); + // Find the "in" token. + var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); if (0 > indexOfInToken) { - throw new WixException(WixErrors.IllegalForeach(this.currentLineNumber, reader.Value)); + throw new WixException(WixErrors.IllegalForeach(this.Context.CurrentSourceLineNumber, reader.Value)); } // parse out the variable name @@ -833,7 +689,7 @@ namespace WixToolset.Core string varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); // preprocess the variable values string because it might be a variable itself - varValuesString = this.core.PreprocessString(this.currentLineNumber, varValuesString); + varValuesString = this.Helper.PreprocessString(this.Context, varValuesString); string[] varValues = varValuesString.Split(';'); @@ -895,20 +751,20 @@ namespace WixToolset.Core } else if (reader.NodeType == XmlNodeType.None) { - throw new WixException(WixErrors.ExpectedEndforeach(this.currentLineNumber)); + throw new WixException(WixErrors.ExpectedEndforeach(this.Context.CurrentSourceLineNumber)); } reader.Read(); } - using (MemoryStream fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) - using (XmlReader loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) + using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) + using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) { // process each iteration, updating the variable's value each time foreach (string varValue in varValues) { // Always overwrite foreach variables. - this.core.AddVariable(this.currentLineNumber, varName, varValue, false); + this.Helper.AddVariable(this.Context, varName, varValue, false); try { @@ -917,7 +773,7 @@ namespace WixToolset.Core catch (XmlException e) { this.UpdateCurrentLineNumber(loopReader, offset); - throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message)); + throw new WixException(WixErrors.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message)); } fragmentStream.Position = 0; // seek back to the beginning for the next loop. @@ -931,24 +787,23 @@ namespace WixToolset.Core /// Text from source. private void PreprocessPragma(string pragmaText, XContainer parent) { - Match match = pragmaRegex.Match(pragmaText); - SourceLineNumber sourceLineNumbers = this.currentLineNumber; + var match = pragmaRegex.Match(pragmaText); if (!match.Success) { - throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaText)); + throw new WixException(WixErrors.InvalidPreprocessorPragma(this.Context.CurrentSourceLineNumber, pragmaText)); } // resolve other variables in the pragma argument(s) - string pragmaArgs = this.core.PreprocessString(sourceLineNumbers, match.Groups["pragmaValue"].Value).Trim(); + string pragmaArgs = this.Helper.PreprocessString(this.Context, match.Groups["pragmaValue"].Value).Trim(); try { - this.core.PreprocessPragma(sourceLineNumbers, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); + this.Helper.PreprocessPragma(this.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); } catch (Exception e) { - throw new WixException(WixErrors.PreprocessorExtensionPragmaFailed(sourceLineNumbers, pragmaText, e.Message)); + throw new WixException(WixErrors.PreprocessorExtensionPragmaFailed(this.Context.CurrentSourceLineNumber, pragmaText, e.Message)); } } @@ -975,11 +830,11 @@ namespace WixToolset.Core int endingQuotes = expression.IndexOf('\"', 1); if (-1 == endingQuotes) { - throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } // cut the quotes off the string - token = this.core.PreprocessString(this.currentLineNumber, expression.Substring(1, endingQuotes - 1)); + token = this.Helper.PreprocessString(this.Context, expression.Substring(1, endingQuotes - 1)); // advance past this string expression = expression.Substring(endingQuotes + 1).Trim(); @@ -1009,7 +864,7 @@ namespace WixToolset.Core if (-1 == endingParen) { - throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } token = expression.Substring(0, endingParen + 1); @@ -1115,7 +970,7 @@ namespace WixToolset.Core { try { - varValue = this.core.PreprocessString(this.currentLineNumber, variable); + varValue = this.Helper.PreprocessString(this.Context, variable); } catch (ArgumentNullException) { @@ -1126,12 +981,12 @@ namespace WixToolset.Core else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1) { // make sure it doesn't contain parenthesis - throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1) { // shouldn't contain quotes - throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } return varValue; @@ -1162,7 +1017,7 @@ namespace WixToolset.Core { if (stringLiteral) { - throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } rightValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral); @@ -1213,7 +1068,7 @@ namespace WixToolset.Core { if (operation.Length > 0) { - throw new WixException(WixErrors.ExpectedVariable(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.ExpectedVariable(this.Context.CurrentSourceLineNumber, originalExpression)); } // false expression @@ -1228,7 +1083,7 @@ namespace WixToolset.Core } else { - throw new WixException(WixErrors.UnexpectedLiteral(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnexpectedLiteral(this.Context.CurrentSourceLineNumber, originalExpression)); } } else @@ -1268,11 +1123,11 @@ namespace WixToolset.Core } catch (FormatException) { - throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.IllegalIntegerInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } catch (OverflowException) { - throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.IllegalIntegerInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } // Compare the numbers @@ -1314,7 +1169,7 @@ namespace WixToolset.Core closeParenIndex = expression.IndexOf(')', closeParenIndex); if (closeParenIndex == -1) { - throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.Context.CurrentSourceLineNumber, originalExpression)); } if (InsideQuotes(expression, closeParenIndex)) @@ -1363,7 +1218,7 @@ namespace WixToolset.Core currentValue = !currentValue; break; default: - throw new WixException(WixErrors.UnexpectedPreprocessorOperator(this.currentLineNumber, operation.ToString())); + throw new WixException(WixErrors.UnexpectedPreprocessorOperator(this.Context.CurrentSourceLineNumber, operation.ToString())); } } @@ -1412,7 +1267,7 @@ namespace WixToolset.Core expression = expression.Trim(); if (expression.Length == 0) { - throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.Context.CurrentSourceLineNumber, originalExpression)); } // If the expression starts with parenthesis, evaluate it @@ -1433,7 +1288,7 @@ namespace WixToolset.Core expression = expression.Substring(3).Trim(); if (expression.Length == 0) { - throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.currentLineNumber, originalExpression)); + throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.Context.CurrentSourceLineNumber, originalExpression)); } expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Not, true); @@ -1462,7 +1317,7 @@ namespace WixToolset.Core } else { - throw new WixException(WixErrors.InvalidSubExpression(this.currentLineNumber, expression, originalExpression)); + throw new WixException(WixErrors.InvalidSubExpression(this.Context.CurrentSourceLineNumber, expression, originalExpression)); } } @@ -1481,9 +1336,9 @@ namespace WixToolset.Core { int newLine = lineInfoReader.LineNumber + offset; - if (this.currentLineNumber.LineNumber != newLine) + if (this.Context.CurrentSourceLineNumber.LineNumber != newLine) { - this.currentLineNumber = new SourceLineNumber(this.currentLineNumber.FileName, newLine); + this.Context.CurrentSourceLineNumber = new SourceLineNumber(this.Context.CurrentSourceLineNumber.FileName, newLine); } } } @@ -1494,15 +1349,15 @@ namespace WixToolset.Core /// Name to push on to the stack of included files. private void PushInclude(string fileName) { - if (1023 < this.currentFileStack.Count) + if (1023 < this.CurrentFileStack.Count) { - throw new WixException(WixErrors.TooDeeplyIncluded(this.currentLineNumber, this.currentFileStack.Count)); + throw new WixException(WixErrors.TooDeeplyIncluded(this.Context.CurrentSourceLineNumber, this.CurrentFileStack.Count)); } - this.currentFileStack.Push(fileName); - this.sourceStack.Push(this.currentLineNumber); - this.currentLineNumber = new SourceLineNumber(fileName); - this.includeNextStack.Push(true); + this.CurrentFileStack.Push(fileName); + this.SourceStack.Push(this.Context.CurrentSourceLineNumber); + this.Context.CurrentSourceLineNumber = new SourceLineNumber(fileName); + this.IncludeNextStack.Push(true); } /// @@ -1510,10 +1365,10 @@ namespace WixToolset.Core /// private void PopInclude() { - this.currentLineNumber = this.sourceStack.Pop(); + this.Context.CurrentSourceLineNumber = this.SourceStack.Pop(); - this.currentFileStack.Pop(); - this.includeNextStack.Pop(); + this.CurrentFileStack.Pop(); + this.IncludeNextStack.Pop(); } /// @@ -1548,8 +1403,8 @@ namespace WixToolset.Core else // relative path { // build a string to test the directory containing the source file first - string currentFolder = this.currentFileStack.Peek(); - string includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) ?? String.Empty, includePath); + var currentFolder = this.CurrentFileStack.Peek(); + var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) , includePath); // test the source file directory if (File.Exists(includeTestPath)) @@ -1558,7 +1413,7 @@ namespace WixToolset.Core } else // test all search paths in the order specified on the command line { - foreach (string includeSearchPath in this.IncludeSearchPaths) + foreach (var includeSearchPath in this.Context.IncludeSearchPaths) { // if the path exists, we have found the final string includeTestPath = Path.Combine(includeSearchPath, includePath); diff --git a/src/WixToolset.Core/PreprocessorCore.cs b/src/WixToolset.Core/PreprocessorCore.cs deleted file mode 100644 index b58fc80c..00000000 --- a/src/WixToolset.Core/PreprocessorCore.cs +++ /dev/null @@ -1,560 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - - /// - /// The preprocessor core. - /// - internal class PreprocessorCore : IPreprocessorCore - { - private static readonly char[] variableSplitter = new char[] { '.' }; - private static readonly char[] argumentSplitter = new char[] { ',' }; - - private Platform currentPlatform; - private Dictionary extensionsByPrefix; - private string sourceFile; - private IDictionary variables; - - /// - /// Instantiate a new PreprocessorCore. - /// - /// The extensions indexed by their prefixes. - /// The message handler. - /// The source file being preprocessed. - /// The variables defined prior to preprocessing. - internal PreprocessorCore(Dictionary extensionsByPrefix, string sourceFile, IDictionary variables) - { - this.extensionsByPrefix = extensionsByPrefix; - this.sourceFile = String.IsNullOrEmpty(sourceFile) ? null : Path.GetFullPath(sourceFile); - - this.variables = new Dictionary(); - foreach (var entry in variables) - { - this.AddVariable(null, entry.Key, entry.Value); - } - } - - /// - /// Event for resolved variables. - /// - private event ResolvedVariableEventHandler ResolvedVariable; - - /// - /// Sets event for ResolvedVariableEventHandler. - /// - public ResolvedVariableEventHandler ResolvedVariableHandler - { - set { this.ResolvedVariable = value; } - } - - /// - /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements. - /// - /// The platform which the compiler will use when defaulting 64-bit attributes and elements. - public Platform CurrentPlatform - { - get { return this.currentPlatform; } - set { this.currentPlatform = value; } - } - - /// - /// Gets whether the core encountered an error while processing. - /// - /// Flag if core encountered an error during processing. - public bool EncounteredError - { - get { return Messaging.Instance.EncounteredError; } - } - - /// - /// Replaces parameters in the source text. - /// - /// The source line information for the function. - /// Text that may contain parameters to replace. - /// Text after parameters have been replaced. - public string PreprocessString(SourceLineNumber sourceLineNumbers, string value) - { - StringBuilder sb = new StringBuilder(); - int currentPosition = 0; - int end = 0; - - while (-1 != (currentPosition = value.IndexOf('$', end))) - { - if (end < currentPosition) - { - sb.Append(value, end, currentPosition - end); - } - - end = currentPosition + 1; - string remainder = value.Substring(end); - if (remainder.StartsWith("$", StringComparison.Ordinal)) - { - sb.Append("$"); - end++; - } - else if (remainder.StartsWith("(loc.", StringComparison.Ordinal)) - { - currentPosition = remainder.IndexOf(')'); - if (-1 == currentPosition) - { - this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder)); - break; - } - - sb.Append("$"); // just put the resource reference back as was - sb.Append(remainder, 0, currentPosition + 1); - - end += currentPosition + 1; - } - else if (remainder.StartsWith("(", StringComparison.Ordinal)) - { - int openParenCount = 1; - int closingParenCount = 0; - bool isFunction = false; - bool foundClosingParen = false; - - // find the closing paren - int closingParenPosition; - for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++) - { - switch (remainder[closingParenPosition]) - { - case '(': - openParenCount++; - isFunction = true; - break; - case ')': - closingParenCount++; - break; - } - if (openParenCount == closingParenCount) - { - foundClosingParen = true; - break; - } - } - - // move the currentPosition to the closing paren - currentPosition += closingParenPosition; - - if (!foundClosingParen) - { - if (isFunction) - { - this.OnMessage(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, remainder)); - break; - } - else - { - this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder)); - break; - } - } - - string subString = remainder.Substring(1, closingParenPosition - 1); - string result = null; - if (isFunction) - { - result = this.EvaluateFunction(sourceLineNumbers, subString); - } - else - { - result = this.GetVariableValue(sourceLineNumbers, subString, false); - } - - if (null == result) - { - if (isFunction) - { - this.OnMessage(WixErrors.UndefinedPreprocessorFunction(sourceLineNumbers, subString)); - break; - } - else - { - this.OnMessage(WixErrors.UndefinedPreprocessorVariable(sourceLineNumbers, subString)); - break; - } - } - else - { - if (!isFunction) - { - this.OnResolvedVariable(new ResolvedVariableEventArgs(sourceLineNumbers, subString, result)); - } - } - sb.Append(result); - end += closingParenPosition + 1; - } - else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing - { - sb.Append('$'); - } - } - - if (end < value.Length) - { - sb.Append(value.Substring(end)); - } - - return sb.ToString(); - } - - /// - /// Evaluate a Pragma. - /// - /// The source line information for the function. - /// The pragma's full name (.). - /// The arguments to the pragma. - /// The parent element of the pragma. - public void PreprocessPragma(SourceLineNumber sourceLineNumbers, string pragmaName, string args, XContainer parent) - { - string[] prefixParts = pragmaName.Split(variableSplitter, 2); - // Check to make sure there are 2 parts and neither is an empty string. - if (2 != prefixParts.Length) - { - throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName)); - } - string prefix = prefixParts[0]; - string pragma = prefixParts[1]; - - if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma)) - { - throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName)); - } - - switch (prefix) - { - case "wix": - switch (pragma) - { - // Add any core defined pragmas here - default: - this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName)); - break; - } - break; - default: - PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix]; - if (null == extension || !extension.ProcessPragma(sourceLineNumbers, prefix, pragma, args, parent)) - { - this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName)); - } - break; - } - } - - /// - /// Evaluate a function. - /// - /// The source line information for the function. - /// The function expression including the prefix and name. - /// The function value. - public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string function) - { - string[] prefixParts = function.Split(variableSplitter, 2); - // Check to make sure there are 2 parts and neither is an empty string. - if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length) - { - throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function)); - } - string prefix = prefixParts[0]; - - string[] functionParts = prefixParts[1].Split(new char[] { '(' }, 2); - // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren. - if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal)) - { - throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function)); - } - string functionName = functionParts[0]; - - // Remove the trailing closing paren. - string allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1); - - // Parse the arguments and preprocess them. - string[] args = allArgs.Split(argumentSplitter); - for (int i = 0; i < args.Length; i++) - { - args[i] = this.PreprocessString(sourceLineNumbers, args[i].Trim()); - } - - string result = this.EvaluateFunction(sourceLineNumbers, prefix, functionName, args); - - // If the function didn't evaluate, try to evaluate the original value as a variable to support - // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve. - if (null == result) - { - result = this.GetVariableValue(sourceLineNumbers, function, false); - } - - return result; - } - - /// - /// Evaluate a function. - /// - /// The source line information for the function. - /// The function prefix. - /// The function name. - /// The arguments for the function. - /// The function value or null if the function is not defined. - public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string prefix, string function, string[] args) - { - if (String.IsNullOrEmpty(prefix)) - { - throw new ArgumentNullException("prefix"); - } - - if (String.IsNullOrEmpty(function)) - { - throw new ArgumentNullException("function"); - } - - switch (prefix) - { - case "fun": - switch (function) - { - case "AutoVersion": - // Make sure the base version is specified - if (args.Length == 0 || String.IsNullOrEmpty(args[0])) - { - throw new WixException(WixErrors.InvalidPreprocessorFunctionAutoVersion(sourceLineNumbers)); - } - - // Build = days since 1/1/2000; Revision = seconds since midnight / 2 - DateTime now = DateTime.UtcNow; - TimeSpan build = now - new DateTime(2000, 1, 1); - TimeSpan revision = now - new DateTime(now.Year, now.Month, now.Day); - - return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2)); - - default: - return null; - } - default: - PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix]; - if (null != extension) - { - try - { - return extension.EvaluateFunction(prefix, function, args); - } - catch (Exception e) - { - throw new WixException(WixErrors.PreprocessorExtensionEvaluateFunctionFailed(sourceLineNumbers, prefix, function, String.Join(",", args), e.Message)); - } - } - else - { - return null; - } - } - } - - /// - /// Get the value of a variable expression like var.name. - /// - /// The source line information for the variable. - /// The variable expression including the optional prefix and name. - /// true to allow the variable prefix to be missing. - /// The variable value. - public string GetVariableValue(SourceLineNumber sourceLineNumbers, string variable, bool allowMissingPrefix) - { - // Strip the "$(" off the front. - if (variable.StartsWith("$(", StringComparison.Ordinal)) - { - variable = variable.Substring(2); - } - - string[] parts = variable.Split(variableSplitter, 2); - - if (1 == parts.Length) // missing prefix - { - if (allowMissingPrefix) - { - return this.GetVariableValue(sourceLineNumbers, "var", parts[0]); - } - else - { - throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable)); - } - } - else - { - // check for empty variable name - if (0 < parts[1].Length) - { - string result = this.GetVariableValue(sourceLineNumbers, parts[0], parts[1]); - - // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix - if (null == result && allowMissingPrefix && variable.Contains(".")) - { - result = this.GetVariableValue(sourceLineNumbers, "var", variable); - } - - return result; - } - else - { - throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable)); - } - } - } - - /// - /// Get the value of a variable. - /// - /// The source line information for the function. - /// The variable prefix. - /// The variable name. - /// The variable value or null if the variable is not set. - public string GetVariableValue(SourceLineNumber sourceLineNumbers, string prefix, string name) - { - if (String.IsNullOrEmpty(prefix)) - { - throw new ArgumentNullException("prefix"); - } - - if (String.IsNullOrEmpty(name)) - { - throw new ArgumentNullException("name"); - } - - switch (prefix) - { - case "env": - return Environment.GetEnvironmentVariable(name); - case "sys": - switch (name) - { - case "CURRENTDIR": - return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar); - case "SOURCEFILEDIR": - return String.Concat(Path.GetDirectoryName(sourceLineNumbers.FileName), Path.DirectorySeparatorChar); - case "SOURCEFILEPATH": - return sourceLineNumbers.FileName; - case "PLATFORM": - this.OnMessage(WixWarnings.DeprecatedPreProcVariable(sourceLineNumbers, "$(sys.PLATFORM)", "$(sys.BUILDARCH)")); - - goto case "BUILDARCH"; - - case "BUILDARCH": - switch (this.currentPlatform) - { - case Platform.X86: - return "x86"; - case Platform.X64: - return "x64"; - case Platform.IA64: - return "ia64"; - case Platform.ARM: - return "arm"; - default: - throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, this.currentPlatform.ToString()); - } - default: - return null; - } - case "var": - string result = null; - return this.variables.TryGetValue(name, out result) ? result : null; - default: - PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix]; - if (null != extension) - { - try - { - return extension.GetVariableValue(prefix, name); - } - catch (Exception e) - { - throw new WixException(WixErrors.PreprocessorExtensionGetVariableValueFailed(sourceLineNumbers, prefix, name, e.Message)); - } - } - else - { - return null; - } - } - } - - /// - /// Sends a message to the message delegate if there is one. - /// - /// Message event arguments. - public void OnMessage(MessageEventArgs e) - { - Messaging.Instance.OnMessage(e); - } - - /// - /// Sends resolved variable to delegate if there is one. - /// - /// Message event arguments. - public void OnResolvedVariable(ResolvedVariableEventArgs mea) - { - if (null != this.ResolvedVariable) - { - this.ResolvedVariable(this, mea); - } - } - - /// - /// Add a variable. - /// - /// The source line information of the variable. - /// The variable name. - /// The variable value. - internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value) - { - this.AddVariable(sourceLineNumbers, name, value, true); - } - - /// - /// Add a variable. - /// - /// The source line information of the variable. - /// The variable name. - /// The variable value. - /// Set to true to show variable overwrite warning. - internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value, bool showWarning) - { - string currentValue = this.GetVariableValue(sourceLineNumbers, "var", name); - - if (null == currentValue) - { - this.variables.Add(name, value); - } - else - { - if (showWarning) - { - this.OnMessage(WixWarnings.VariableDeclarationCollision(sourceLineNumbers, name, value, currentValue)); - } - - this.variables[name] = value; - } - } - - /// - /// Remove a variable. - /// - /// The source line information of the variable. - /// The variable name. - internal void RemoveVariable(SourceLineNumber sourceLineNumbers, string name) - { - if (!this.variables.Remove(name)) - { - this.OnMessage(WixErrors.CannotReundefineVariable(sourceLineNumbers, name)); - } - } - } -} diff --git a/src/WixToolset.Core/WixToolsetServiceProvider.cs b/src/WixToolset.Core/WixToolsetServiceProvider.cs index 8693461b..d31d7355 100644 --- a/src/WixToolset.Core/WixToolsetServiceProvider.cs +++ b/src/WixToolset.Core/WixToolsetServiceProvider.cs @@ -12,6 +12,7 @@ namespace WixToolset.Core { private ExtensionManager extensionManager; private ParseHelper parseHelper; + private PreprocessHelper preprocessHelper; private TupleDefinitionCreator tupleDefinitionCreator; public object GetService(Type serviceType) @@ -19,6 +20,11 @@ namespace WixToolset.Core if (serviceType == null) throw new ArgumentNullException(nameof(serviceType)); // Transients. + if (serviceType == typeof(IPreprocessContext)) + { + return new PreprocessContext(this); + } + if (serviceType == typeof(ICompileContext)) { return new CompileContext(this); @@ -65,6 +71,11 @@ namespace WixToolset.Core return this.parseHelper = this.parseHelper ?? new ParseHelper(this); } + if (serviceType == typeof(IPreprocessHelper)) + { + return this.preprocessHelper = this.preprocessHelper ?? new PreprocessHelper(this); + } + throw new ArgumentException($"Unknown service type: {serviceType.Name}", nameof(serviceType)); } } diff --git a/src/test/Example.Extension/ExampleExtensionFactory.cs b/src/test/Example.Extension/ExampleExtensionFactory.cs index 9539ee85..b91d06e9 100644 --- a/src/test/Example.Extension/ExampleExtensionFactory.cs +++ b/src/test/Example.Extension/ExampleExtensionFactory.cs @@ -7,11 +7,18 @@ namespace Example.Extension public class ExampleExtensionFactory : IExtensionFactory { + private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension; + public bool TryCreateExtension(Type extensionType, out object extension) { - if (extensionType == typeof(IPreprocessorExtension)) + if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension)) { - extension = new ExamplePreprocessorExtension(); + if (preprocessorExtension == null) + { + preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine(); + } + + extension = preprocessorExtension; } else if (extensionType == typeof(ICompilerExtension)) { diff --git a/src/test/Example.Extension/ExamplePreprocessorExtension.cs b/src/test/Example.Extension/ExamplePreprocessorExtension.cs deleted file mode 100644 index c16c8b5a..00000000 --- a/src/test/Example.Extension/ExamplePreprocessorExtension.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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 Example.Extension -{ - using System; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - - internal class ExamplePreprocessorExtension : IPreprocessorExtension - { - public ExamplePreprocessorExtension() - { - } - - public IPreprocessorCore Core { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - public string[] Prefixes => throw new NotImplementedException(); - - public string EvaluateFunction(string prefix, string function, string[] args) - { - throw new NotImplementedException(); - } - - public void Finish() - { - throw new NotImplementedException(); - } - - public string GetVariableValue(string prefix, string name) - { - throw new NotImplementedException(); - } - - public void Initialize() - { - throw new NotImplementedException(); - } - - public void PreprocessDocument(XDocument document) - { - throw new NotImplementedException(); - } - - public string PreprocessParameter(string name) - { - throw new NotImplementedException(); - } - - public bool ProcessPragma(SourceLineNumber sourceLineNumbers, string prefix, string pragma, string args, XContainer parent) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs b/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs new file mode 100644 index 00000000..53394ea3 --- /dev/null +++ b/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs @@ -0,0 +1,46 @@ +// 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 Example.Extension +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine + { + private string exampleValueFromCommandLine; + + public IEnumerable CommandLineSwitches => throw new NotImplementedException(); + + public ExamplePreprocessorExtensionAndCommandLine() + { + this.Prefixes = new[] { "ex" }; + } + + public void PreParse(ICommandLineContext context) + { + } + + public bool TryParseArgument(IParseCommandLine parseCommandLine, string arg) + { + if (parseCommandLine.IsSwitch(arg) && arg.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase)) + { + parseCommandLine.GetNextArgumentOrError(ref this.exampleValueFromCommandLine); + return true; + } + + return false; + } + + public override string GetVariableValue(string prefix, string name) + { + if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase)) + { + return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs index 5181c748..6acf3472 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs @@ -56,5 +56,48 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal("Bar", example[1].AsString()); } } + + [Fact] + public void CanParseCommandLineWithExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var program = new Program(); + var result = program.Run(new WixToolsetServiceProvider(), new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-example", "test", + "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") + }); + + Assert.Equal(0, result); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\example.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wir")); + var section = intermediate.Sections.Single(); + + var wixFile = section.Tuples.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + + var property = section.Tuples.OfType().Where(p => p.Id.Id == "ExampleProperty").Single(); + Assert.Equal("ExampleProperty", property.Property); + Assert.Equal("test", property.Value); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs index cdc323ec..9fd42214 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs @@ -6,6 +6,8 @@ + + -- cgit v1.2.3-55-g6feb From e53afb01c6e01bb9e6521fa77d31e575abc73f9c Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 4 Dec 2017 18:33:11 -0500 Subject: Add CanBuildSingleFileCompressed test (failing variety). --- .../ProgramFixture.cs | 36 ++++++++++++++++++++++ .../SingleFileCompressed/Package.en-us.wxl | 11 +++++++ .../TestData/SingleFileCompressed/Package.wxs | 22 +++++++++++++ .../SingleFileCompressed/PackageComponents.wxs | 10 ++++++ .../TestData/SingleFileCompressed/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 +++ 6 files changed, 84 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs index e9e5c62f..6644fd33 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs @@ -48,6 +48,42 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildSingleFileCompressed() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var program = new Program(); + var result = program.Run(new WixToolsetServiceProvider(), new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + Assert.Equal(0, result); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wir")); + Assert.Single(intermediate.Sections); + + var wixFile = intermediate.Sections.SelectMany(s => s.Tuples).OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + } + } + [Fact] public void CanBuildSimpleModule() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs new file mode 100644 index 00000000..3f47d01d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index ede5967f..ed0d5f5e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -23,6 +23,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 5ba862bfa618c89a563d555e8ce7b44a904df406 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 6 Dec 2017 11:39:26 -0800 Subject: Add support for loading Intermediates from extensions --- src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | 7 +- src/WixToolset.Core/CommandLine/BuildCommand.cs | 4 +- src/WixToolset.Core/LinkContext.cs | 4 + src/WixToolset.Core/Linker.cs | 52 +++---------- .../Example.Extension/Example.Extension.csproj | 18 ----- .../Example.Extension/ExampleCompilerExtension.cs | 84 --------------------- src/test/Example.Extension/ExampleExtensionData.cs | 33 -------- .../Example.Extension/ExampleExtensionFactory.cs | 39 ---------- .../ExamplePreprocessorExtensionAndCommandLine.cs | 46 ----------- src/test/Example.Extension/ExampleTuple.cs | 31 -------- src/test/Example.Extension/TupleDefinitions.cs | 18 ----- .../TestData/Example.Extension/Data/example.txt | 1 + .../TestData/Example.Extension/Data/example.wir | Bin 0 -> 534 bytes .../TestData/Example.Extension/Data/example.wxs | 8 ++ .../Example.Extension/Example.Extension.csproj | 22 ++++++ .../Example.Extension/ExampleCompilerExtension.cs | 84 +++++++++++++++++++++ .../Example.Extension/ExampleExtensionData.cs | 33 ++++++++ .../Example.Extension/ExampleExtensionFactory.cs | 39 ++++++++++ .../ExamplePreprocessorExtensionAndCommandLine.cs | 46 +++++++++++ .../TestData/Example.Extension/ExampleTuple.cs | 31 ++++++++ .../TestData/Example.Extension/TupleDefinitions.cs | 18 +++++ .../ExtensionFixture.cs | 6 +- .../TestData/ExampleExtension/Package.wxs | 2 + .../WixToolsetTest.CoreIntegration.csproj | 2 +- 24 files changed, 308 insertions(+), 320 deletions(-) delete mode 100644 src/test/Example.Extension/Example.Extension.csproj delete mode 100644 src/test/Example.Extension/ExampleCompilerExtension.cs delete mode 100644 src/test/Example.Extension/ExampleExtensionData.cs delete mode 100644 src/test/Example.Extension/ExampleExtensionFactory.cs delete mode 100644 src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs delete mode 100644 src/test/Example.Extension/ExampleTuple.cs delete mode 100644 src/test/Example.Extension/TupleDefinitions.cs create mode 100644 src/test/TestData/Example.Extension/Data/example.txt create mode 100644 src/test/TestData/Example.Extension/Data/example.wir create mode 100644 src/test/TestData/Example.Extension/Data/example.wxs create mode 100644 src/test/TestData/Example.Extension/Example.Extension.csproj create mode 100644 src/test/TestData/Example.Extension/ExampleCompilerExtension.cs create mode 100644 src/test/TestData/Example.Extension/ExampleExtensionData.cs create mode 100644 src/test/TestData/Example.Extension/ExampleExtensionFactory.cs create mode 100644 src/test/TestData/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs create mode 100644 src/test/TestData/Example.Extension/ExampleTuple.cs create mode 100644 src/test/TestData/Example.Extension/TupleDefinitions.cs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs index 28fc4817..c6e21973 100644 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs +++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs @@ -28,19 +28,16 @@ namespace WixToolset.Core.Bind /// The extract path for the embedded file. public string AddEmbeddedFileIndex(Uri uri, int embeddedFileIndex, string tempPath) { - string extractPath; - SortedList extracts; - // If the uri to the file that contains the embedded file does not already have embedded files // being extracted, create the dictionary to track that. - if (!filesWithEmbeddedFiles.TryGetValue(uri, out extracts)) + if (!filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) { extracts = new SortedList(); filesWithEmbeddedFiles.Add(uri, extracts); } // If the embedded file is not already tracked in the dictionary of extracts, add it. - if (!extracts.TryGetValue(embeddedFileIndex, out extractPath)) + if (!extracts.TryGetValue(embeddedFileIndex, out var extractPath)) { string localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath); string unique = this.HashUri(uri.AbsoluteUri); diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index 79bacd22..7a63b869 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -168,8 +168,10 @@ namespace WixToolset.Core var context = this.ServiceProvider.GetService(); context.Messaging = Messaging.Instance; context.Extensions = this.ExtensionManager.Create(); - context.Intermediates = intermediates.Union(libraries).ToList(); + context.ExtensionData = this.ExtensionManager.Create(); context.ExpectedOutputType = this.OutputType; + context.Intermediates = intermediates.Union(libraries).ToList(); + context.TupleDefinitionCreator = creator; var linker = new Linker(); var output = linker.Link(context); diff --git a/src/WixToolset.Core/LinkContext.cs b/src/WixToolset.Core/LinkContext.cs index c3631f72..1384cf98 100644 --- a/src/WixToolset.Core/LinkContext.cs +++ b/src/WixToolset.Core/LinkContext.cs @@ -21,8 +21,12 @@ namespace WixToolset.Core public IEnumerable Extensions { get; set; } + public IEnumerable ExtensionData { get; set; } + public OutputType ExpectedOutputType { get; set; } public IEnumerable Intermediates { get; set; } + + public ITupleDefinitionCreator TupleDefinitionCreator { get; set; } } } diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index 7faf69ba..c893a01d 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -55,32 +55,6 @@ namespace WixToolset.Core /// The Wix variable resolver. //internal IBindVariableResolver WixVariableResolver { get; set; } - /// - /// Adds an extension. - /// - /// The extension to add. - //public void AddExtensionData(IExtensionData extension) - //{ - // if (null != extension.TableDefinitions) - // { - // foreach (TableDefinition tableDefinition in extension.TableDefinitions) - // { - // if (!this.tableDefinitions.Contains(tableDefinition.Name)) - // { - // this.tableDefinitions.Add(tableDefinition); - // } - // else - // { - // throw new WixException(WixErrors.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name)); - // } - // } - // } - - // // keep track of extension data so the libraries can be loaded from these later once all the table definitions - // // are loaded; this will allow extensions to have cross table definition dependencies - // this.extensionData.Add(extension); - //} - /// /// Links a collection of sections into an output. /// @@ -91,10 +65,19 @@ namespace WixToolset.Core { this.Context = context ?? throw new ArgumentNullException(nameof(context)); - //IEnumerable
inputs, OutputType expectedOutputType - var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList(); + // Add sections from the extensions with data. + foreach (var data in context.ExtensionData) + { + var library = data.GetLibrary(context.TupleDefinitionCreator); + + if (library != null) + { + sections.AddRange(library.Sections); + } + } + #if MOVE_TO_BACKEND bool containsModuleSubstitution = false; bool containsModuleConfiguration = false; @@ -144,19 +127,6 @@ namespace WixToolset.Core } #endif -#if TODO - // Add sections from the extensions with data. - foreach (IExtensionData data in this.extensionData) - { - Library library = data.GetLibrary(this.tableDefinitions); - - if (null != library) - { - sections.AddRange(library.Sections); - } - } -#endif - // First find the entry section and while processing all sections load all the symbols from all of the sections. // sections.FindEntrySectionAndLoadSymbols(false, this, expectedOutputType, out entrySection, out allSymbols); var find = new FindEntrySectionAndLoadSymbolsCommand(sections); diff --git a/src/test/Example.Extension/Example.Extension.csproj b/src/test/Example.Extension/Example.Extension.csproj deleted file mode 100644 index 80c64b25..00000000 --- a/src/test/Example.Extension/Example.Extension.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - netstandard2.0 - false - - - - - - - - - - - diff --git a/src/test/Example.Extension/ExampleCompilerExtension.cs b/src/test/Example.Extension/ExampleCompilerExtension.cs deleted file mode 100644 index 5b20e48f..00000000 --- a/src/test/Example.Extension/ExampleCompilerExtension.cs +++ /dev/null @@ -1,84 +0,0 @@ -// 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 Example.Extension -{ - using System; - using System.Collections.Generic; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - - internal class ExampleCompilerExtension : BaseCompilerExtension - { - public ExampleCompilerExtension() - { - this.Namespace = "http://www.example.com/scheams/v1/wxs"; - } - - public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) - { - var processed = false; - - switch (parentElement.Name.LocalName) - { - case "Component": - switch (element.Name.LocalName) - { - case "Example": - this.ParseExampleElement(intermediate, section, element); - processed = true; - break; - } - break; - } - - if (!processed) - { - base.ParseElement(intermediate, section, parentElement, element, context); - } - } - - private void ParseExampleElement(Intermediate intermediate, IntermediateSection section, XElement element) - { - var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); - Identifier id = null; - string value = null; - - foreach (var attrib in element.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - - case "Value": - value = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - - default: - this.ParseHelper.UnexpectedAttribute(element, attrib); - break; - } - } - else - { - this.ParseAttribute(intermediate, section, element, attrib, null); - } - } - - if (null == id) - { - //this.Messaging(WixErrors.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); - } - - if (!this.Messaging.EncounteredError) - { - var tuple = this.ParseHelper.CreateRow(section, sourceLineNumbers, "Example", id); - tuple.Set(1, value); - } - } - } -} diff --git a/src/test/Example.Extension/ExampleExtensionData.cs b/src/test/Example.Extension/ExampleExtensionData.cs deleted file mode 100644 index c3cb0473..00000000 --- a/src/test/Example.Extension/ExampleExtensionData.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 Example.Extension -{ - using WixToolset.Data; - using WixToolset.Extensibility; - - internal class ExampleExtensionData : IExtensionData - { - public string DefaultCulture => null; - - public Intermediate GetLibrary(ITupleDefinitionCreator tupleDefinitions) - { - return null; - } - - public bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition) - { - switch (name) - { - case "Example": - tupleDefinition = TupleDefinitions.Example; - break; - - default: - tupleDefinition = null; - break; - } - - return tupleDefinition != null; - } - } -} \ No newline at end of file diff --git a/src/test/Example.Extension/ExampleExtensionFactory.cs b/src/test/Example.Extension/ExampleExtensionFactory.cs deleted file mode 100644 index b91d06e9..00000000 --- a/src/test/Example.Extension/ExampleExtensionFactory.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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 Example.Extension -{ - using System; - using WixToolset.Extensibility; - - public class ExampleExtensionFactory : IExtensionFactory - { - private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension; - - public bool TryCreateExtension(Type extensionType, out object extension) - { - if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension)) - { - if (preprocessorExtension == null) - { - preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine(); - } - - extension = preprocessorExtension; - } - else if (extensionType == typeof(ICompilerExtension)) - { - extension = new ExampleCompilerExtension(); - } - else if (extensionType == typeof(IExtensionData)) - { - extension = new ExampleExtensionData(); - } - else - { - extension = null; - } - - return extension != null; - } - } -} diff --git a/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs b/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs deleted file mode 100644 index 53394ea3..00000000 --- a/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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 Example.Extension -{ - using System; - using System.Collections.Generic; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine - { - private string exampleValueFromCommandLine; - - public IEnumerable CommandLineSwitches => throw new NotImplementedException(); - - public ExamplePreprocessorExtensionAndCommandLine() - { - this.Prefixes = new[] { "ex" }; - } - - public void PreParse(ICommandLineContext context) - { - } - - public bool TryParseArgument(IParseCommandLine parseCommandLine, string arg) - { - if (parseCommandLine.IsSwitch(arg) && arg.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase)) - { - parseCommandLine.GetNextArgumentOrError(ref this.exampleValueFromCommandLine); - return true; - } - - return false; - } - - public override string GetVariableValue(string prefix, string name) - { - if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase)) - { - return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine; - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/test/Example.Extension/ExampleTuple.cs b/src/test/Example.Extension/ExampleTuple.cs deleted file mode 100644 index f280a5c8..00000000 --- a/src/test/Example.Extension/ExampleTuple.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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 Example.Extension -{ - using WixToolset.Data; - - public enum ExampleTupleFields - { - Example, - Value, - } - - public class ExampleTuple : IntermediateTuple - { - public ExampleTuple() : base(TupleDefinitions.Example, null, null) - { - } - - public ExampleTuple(SourceLineNumber sourceLineNumber, Identifier id = null) : base(TupleDefinitions.Example, sourceLineNumber, id) - { - } - - public IntermediateField this[ExampleTupleFields index] => this.Fields[(int)index]; - - public string Value - { - get => this.Fields[(int)ExampleTupleFields.Value]?.AsString(); - set => this.Set((int)ExampleTupleFields.Value, value); - } - } -} diff --git a/src/test/Example.Extension/TupleDefinitions.cs b/src/test/Example.Extension/TupleDefinitions.cs deleted file mode 100644 index 2c320fbc..00000000 --- a/src/test/Example.Extension/TupleDefinitions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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 Example.Extension -{ - using WixToolset.Data; - - public static class TupleDefinitions - { - public static readonly IntermediateTupleDefinition Example = new IntermediateTupleDefinition( - "Example", - new[] - { - new IntermediateFieldDefinition(nameof(ExampleTupleFields.Example), IntermediateFieldType.String), - new IntermediateFieldDefinition(nameof(ExampleTupleFields.Value), IntermediateFieldType.String), - }, - typeof(ExampleTuple)); - } -} diff --git a/src/test/TestData/Example.Extension/Data/example.txt b/src/test/TestData/Example.Extension/Data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/test/TestData/Example.Extension/Data/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/test/TestData/Example.Extension/Data/example.wir b/src/test/TestData/Example.Extension/Data/example.wir new file mode 100644 index 00000000..674f63fc Binary files /dev/null and b/src/test/TestData/Example.Extension/Data/example.wir differ diff --git a/src/test/TestData/Example.Extension/Data/example.wxs b/src/test/TestData/Example.Extension/Data/example.wxs new file mode 100644 index 00000000..53531e99 --- /dev/null +++ b/src/test/TestData/Example.Extension/Data/example.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/TestData/Example.Extension/Example.Extension.csproj b/src/test/TestData/Example.Extension/Example.Extension.csproj new file mode 100644 index 00000000..d04ce553 --- /dev/null +++ b/src/test/TestData/Example.Extension/Example.Extension.csproj @@ -0,0 +1,22 @@ + + + + + + netstandard2.0 + false + + + + + + + + + + + + + + + diff --git a/src/test/TestData/Example.Extension/ExampleCompilerExtension.cs b/src/test/TestData/Example.Extension/ExampleCompilerExtension.cs new file mode 100644 index 00000000..5b20e48f --- /dev/null +++ b/src/test/TestData/Example.Extension/ExampleCompilerExtension.cs @@ -0,0 +1,84 @@ +// 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 Example.Extension +{ + using System; + using System.Collections.Generic; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class ExampleCompilerExtension : BaseCompilerExtension + { + public ExampleCompilerExtension() + { + this.Namespace = "http://www.example.com/scheams/v1/wxs"; + } + + public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) + { + var processed = false; + + switch (parentElement.Name.LocalName) + { + case "Component": + switch (element.Name.LocalName) + { + case "Example": + this.ParseExampleElement(intermediate, section, element); + processed = true; + break; + } + break; + } + + if (!processed) + { + base.ParseElement(intermediate, section, parentElement, element, context); + } + } + + private void ParseExampleElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + string value = null; + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + + case "Value": + value = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseAttribute(intermediate, section, element, attrib, null); + } + } + + if (null == id) + { + //this.Messaging(WixErrors.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + var tuple = this.ParseHelper.CreateRow(section, sourceLineNumbers, "Example", id); + tuple.Set(1, value); + } + } + } +} diff --git a/src/test/TestData/Example.Extension/ExampleExtensionData.cs b/src/test/TestData/Example.Extension/ExampleExtensionData.cs new file mode 100644 index 00000000..6b179ea6 --- /dev/null +++ b/src/test/TestData/Example.Extension/ExampleExtensionData.cs @@ -0,0 +1,33 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class ExampleExtensionData : IExtensionData + { + public string DefaultCulture => null; + + public Intermediate GetLibrary(ITupleDefinitionCreator tupleDefinitions) + { + return Intermediate.Load(typeof(ExampleExtensionData).Assembly, "Example.Extension.Data.Example.wir", tupleDefinitions); + } + + public bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition) + { + switch (name) + { + case "Example": + tupleDefinition = TupleDefinitions.Example; + break; + + default: + tupleDefinition = null; + break; + } + + return tupleDefinition != null; + } + } +} \ No newline at end of file diff --git a/src/test/TestData/Example.Extension/ExampleExtensionFactory.cs b/src/test/TestData/Example.Extension/ExampleExtensionFactory.cs new file mode 100644 index 00000000..b91d06e9 --- /dev/null +++ b/src/test/TestData/Example.Extension/ExampleExtensionFactory.cs @@ -0,0 +1,39 @@ +// 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 Example.Extension +{ + using System; + using WixToolset.Extensibility; + + public class ExampleExtensionFactory : IExtensionFactory + { + private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension; + + public bool TryCreateExtension(Type extensionType, out object extension) + { + if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension)) + { + if (preprocessorExtension == null) + { + preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine(); + } + + extension = preprocessorExtension; + } + else if (extensionType == typeof(ICompilerExtension)) + { + extension = new ExampleCompilerExtension(); + } + else if (extensionType == typeof(IExtensionData)) + { + extension = new ExampleExtensionData(); + } + else + { + extension = null; + } + + return extension != null; + } + } +} diff --git a/src/test/TestData/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs b/src/test/TestData/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs new file mode 100644 index 00000000..53394ea3 --- /dev/null +++ b/src/test/TestData/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs @@ -0,0 +1,46 @@ +// 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 Example.Extension +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine + { + private string exampleValueFromCommandLine; + + public IEnumerable CommandLineSwitches => throw new NotImplementedException(); + + public ExamplePreprocessorExtensionAndCommandLine() + { + this.Prefixes = new[] { "ex" }; + } + + public void PreParse(ICommandLineContext context) + { + } + + public bool TryParseArgument(IParseCommandLine parseCommandLine, string arg) + { + if (parseCommandLine.IsSwitch(arg) && arg.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase)) + { + parseCommandLine.GetNextArgumentOrError(ref this.exampleValueFromCommandLine); + return true; + } + + return false; + } + + public override string GetVariableValue(string prefix, string name) + { + if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase)) + { + return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/test/TestData/Example.Extension/ExampleTuple.cs b/src/test/TestData/Example.Extension/ExampleTuple.cs new file mode 100644 index 00000000..f280a5c8 --- /dev/null +++ b/src/test/TestData/Example.Extension/ExampleTuple.cs @@ -0,0 +1,31 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + + public enum ExampleTupleFields + { + Example, + Value, + } + + public class ExampleTuple : IntermediateTuple + { + public ExampleTuple() : base(TupleDefinitions.Example, null, null) + { + } + + public ExampleTuple(SourceLineNumber sourceLineNumber, Identifier id = null) : base(TupleDefinitions.Example, sourceLineNumber, id) + { + } + + public IntermediateField this[ExampleTupleFields index] => this.Fields[(int)index]; + + public string Value + { + get => this.Fields[(int)ExampleTupleFields.Value]?.AsString(); + set => this.Set((int)ExampleTupleFields.Value, value); + } + } +} diff --git a/src/test/TestData/Example.Extension/TupleDefinitions.cs b/src/test/TestData/Example.Extension/TupleDefinitions.cs new file mode 100644 index 00000000..2c320fbc --- /dev/null +++ b/src/test/TestData/Example.Extension/TupleDefinitions.cs @@ -0,0 +1,18 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + + public static class TupleDefinitions + { + public static readonly IntermediateTupleDefinition Example = new IntermediateTupleDefinition( + "Example", + new[] + { + new IntermediateFieldDefinition(nameof(ExampleTupleFields.Example), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(ExampleTupleFields.Value), IntermediateFieldType.String), + }, + typeof(ExampleTuple)); + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs index 6acf3472..bd4b70da 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs @@ -44,13 +44,13 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\example.txt"))); var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wir")); - Assert.Single(intermediate.Sections); + var section = intermediate.Sections.Single(); - var wixFile = intermediate.Sections.SelectMany(s => s.Tuples).OfType().Single(); + var wixFile = section.Tuples.OfType().Single(); Assert.Equal(Path.Combine(folder, @"data\example.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); Assert.Equal(@"example.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); - var example = intermediate.Sections.SelectMany(s => s.Tuples).Where(t => t.Definition.Type == TupleDefinitionType.MustBeFromAnExtension).Single(); + var example = section.Tuples.Where(t => t.Definition.Type == TupleDefinitionType.MustBeFromAnExtension).Single(); Assert.Equal("Foo", example.Id.Id); Assert.Equal("Foo", example[0].AsString()); Assert.Equal("Bar", example[1].AsString()); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs index 9fd42214..bff5f609 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs @@ -8,6 +8,8 @@ + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index ed0d5f5e..af520116 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -31,7 +31,7 @@ - + -- cgit v1.2.3-55-g6feb From b1e662bd480241ea914f0f3d6bd174d9ffd03f5f Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 6 Dec 2017 11:40:44 -0800 Subject: Fix cab creation using explicit Media element --- .../Bind/AssignMediaCommand.cs | 8 ++++---- src/WixToolset.Core/Compiler.cs | 3 +-- src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs | 14 +++++++------- .../TestData/SingleFileCompressed/Package.wxs | 3 +-- 4 files changed, 13 insertions(+), 15 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs index f426b96d..1f2cee74 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs @@ -58,7 +58,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var mediaTemplateTable = this.Section.Tuples.OfType().ToList(); // If both tables are authored, it is an error. - if ((mediaTemplateTable != null && mediaTemplateTable.Count > 0) && (mediaTable != null && mediaTable.Count > 1)) + if (mediaTemplateTable.Count > 0 && mediaTable.Count > 1) { throw new WixException(WixErrors.MediaTableCollision(null)); } @@ -73,7 +73,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind filesByCabinetMedia.Add(mergeModuleMediaRow, new List(this.FileFacades)); } - else if (null == mediaTemplateTable) + else if (mediaTemplateTable.Count == 0) { this.ManuallyAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); } @@ -266,8 +266,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind // When building a product, if the current file is not to be compressed or if // the package set not to be compressed, don't cab it. if (SectionType.Product == this.Section.Type && - (!facade.File.Compressed.Value || - (!facade.File.Compressed.HasValue && !this.FilesCompressed))) + ((!facade.File.Compressed.HasValue && !this.FilesCompressed) || + (facade.File.Compressed.HasValue && !facade.File.Compressed.Value))) { uncompressedFiles.Add(facade); } diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 1c1c2f0a..ac3f3fe1 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -7361,8 +7361,7 @@ namespace WixToolset.Core // add the row to the section if (!this.Core.EncounteredError) { - var mediaRow = (MediaTuple)this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Media); - mediaRow.DiskId = id; + var mediaRow = (MediaTuple)this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Media, new Identifier(id, AccessModifier.Public)); mediaRow.LastSequence = 0; // this is set in the binder mediaRow.DiskPrompt = diskPrompt; mediaRow.Cabinet = cabinet; diff --git a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs index 6644fd33..d99f53ec 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs @@ -40,9 +40,9 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\test.txt"))); var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wir")); - Assert.Single(intermediate.Sections); + var section = intermediate.Sections.Single(); - var wixFile = intermediate.Sections.SelectMany(s => s.Tuples).OfType().Single(); + var wixFile = section.Tuples.OfType().Single(); Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); } @@ -72,13 +72,13 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(0, result); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab"))); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\test.txt"))); var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wir")); - Assert.Single(intermediate.Sections); + var section = intermediate.Sections.Single(); - var wixFile = intermediate.Sections.SelectMany(s => s.Tuples).OfType().Single(); + var wixFile = section.Tuples.OfType().Single(); Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); } @@ -110,9 +110,9 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wir")); - Assert.Single(intermediate.Sections); + var section = intermediate.Sections.Single(); - var wixFile = intermediate.Sections.SelectMany(s => s.Tuples).OfType().Single(); + var wixFile = section.Tuples.OfType().Single(); Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs index 3f47d01d..87db9851 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs @@ -4,8 +4,7 @@ - - + -- cgit v1.2.3-55-g6feb From 4b701efdbacaddb2cb119a7a44e106f5909cb04b Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Thu, 7 Dec 2017 18:27:16 -0500 Subject: MediaTemplate problems. With no CompressionLevel: Test Name: WixToolsetTest.CoreIntegration.ProgramFixture.CanBuildSingleFileCompressed Test FullName: WixToolsetTest.CoreIntegration.ProgramFixture.CanBuildSingleFileCompressed (aa3e0331855885108b655e12826bff636c4a5760) Test Source: Z:\wix\Core\src\test\WixToolsetTest.CoreIntegration\ProgramFixture.cs : line 54 Test Outcome: Failed Test Duration: 0:00:00.186 Result StackTrace: at System.Enum.EnumResult.SetFailure(ParseFailureKind failure, String failureParameter) at System.Enum.TryParseEnum(Type enumType, String value, Boolean ignoreCase, EnumResult& parseResult) at System.Enum.Parse(Type enumType, String value, Boolean ignoreCase) at WixToolset.Data.Tuples.WixMediaTemplateTuple.get_CompressionLevel() in Z:\wix\Data\src\WixToolset.Data\Tuples\WixMediaTemplateTuple.cs:line 58 at WixToolset.Core.WindowsInstaller.Bind.AssignMediaCommand.AddMediaRow(WixMediaTemplateTuple mediaTemplateRow, Int32 cabIndex) in Z:\wix\Core\src\WixToolset.Core.WindowsInstaller\Bind\AssignMediaCommand.cs:line 304 at WixToolset.Core.WindowsInstaller.Bind.AssignMediaCommand.AutoAssignFiles(List`1 mediaTable, IEnumerable`1 fileFacades, Dictionary`2 filesByCabinetMedia, Dictionary`2 mediaRows, List`1 uncompressedFiles) in Z:\wix\Core\src\WixToolset.Core.WindowsInstaller\Bind\AssignMediaCommand.cs:line 197 at WixToolset.Core.WindowsInstaller.Bind.AssignMediaCommand.Execute() in Z:\wix\Core\src\WixToolset.Core.WindowsInstaller\Bind\AssignMediaCommand.cs:line 82 at WixToolset.Core.WindowsInstaller.Bind.BindDatabaseCommand.Execute() in Z:\wix\Core\src\WixToolset.Core.WindowsInstaller\Bind\BindDatabaseCommand.cs:line 286 at WixToolset.Core.WindowsInstaller.MsiBackend.Bind(IBindContext context) in Z:\wix\Core\src\WixToolset.Core.WindowsInstaller\MsiBackend.cs:line 27 at WixToolset.Core.Binder.BackendBind() in Z:\wix\Core\src\WixToolset.Core\Binder.cs:line 281 at WixToolset.Core.Binder.Bind(IBindContext context) in Z:\wix\Core\src\WixToolset.Core\Binder.cs:line 202 at WixToolset.Core.BuildCommand.BindPhase(Intermediate output) in Z:\wix\Core\src\WixToolset.Core\CommandLine\BuildCommand.cs:line 218 at WixToolset.Core.BuildCommand.Execute() in Z:\wix\Core\src\WixToolset.Core\CommandLine\BuildCommand.cs:line 96 at WixToolset.Core.Program.Run(IServiceProvider serviceProvider, String[] args) in Z:\wix\Core\src\wix\Program.cs:line 45 at WixToolsetTest.CoreIntegration.ProgramFixture.CanBuildSingleFileCompressed() in Z:\wix\Core\src\test\WixToolsetTest.CoreIntegration\ProgramFixture.cs:line 62 Result Message: System.ArgumentNullException : Value cannot be null. Parameter name: value With CompressionLevel="low": Test Name: WixToolsetTest.CoreIntegration.ProgramFixture.CanBuildSingleFileCompressed Test FullName: WixToolsetTest.CoreIntegration.ProgramFixture.CanBuildSingleFileCompressed (aa3e0331855885108b655e12826bff636c4a5760) Test Source: Z:\wix\Core\src\test\WixToolsetTest.CoreIntegration\ProgramFixture.cs : line 54 Test Outcome: Failed Test Duration: 0:00:00.072 Result StackTrace: at WixToolset.Data.IntermediateFieldExtensions.Set(IntermediateField field, Object value) in Z:\wix\Data\src\WixToolset.Data\IntermediateFieldExtensions.cs:line 49 at WixToolset.Data.IntermediateFieldExtensions.Set(IntermediateField field, IntermediateFieldDefinition definition, Object value) in Z:\wix\Data\src\WixToolset.Data\IntermediateFieldExtensions.cs:line 120 at WixToolset.Data.IntermediateTupleExtensions.Set(IntermediateTuple tuple, Int32 index, Object value) in Z:\wix\Data\src\WixToolset.Data\IntermediateTupleExtensions.cs:line 11 at WixToolset.Data.Tuples.WixMediaTemplateTuple.set_CompressionLevel(CompressionLevel value) in Z:\wix\Data\src\WixToolset.Data\Tuples\WixMediaTemplateTuple.cs:line 59 at WixToolset.Core.Compiler.ParseMediaTemplateElement(XElement node, String patchId) in Z:\wix\Core\src\WixToolset.Core\Compiler.cs:line 7520 at WixToolset.Core.Compiler.ParseProductElement(XElement node) in Z:\wix\Core\src\WixToolset.Core\Compiler.cs:line 11949 at WixToolset.Core.Compiler.ParseWixElement(XElement node) in Z:\wix\Core\src\WixToolset.Core\Compiler.cs:line 20533 at WixToolset.Core.Compiler.Compile(ICompileContext context) in Z:\wix\Core\src\WixToolset.Core\Compiler.cs:line 137 at WixToolset.Core.BuildCommand.CompilePhase() in Z:\wix\Core\src\WixToolset.Core\CommandLine\BuildCommand.cs:line 129 at WixToolset.Core.BuildCommand.Execute() in Z:\wix\Core\src\WixToolset.Core\CommandLine\BuildCommand.cs:line 77 at WixToolset.Core.Program.Run(IServiceProvider serviceProvider, String[] args) in Z:\wix\Core\src\wix\Program.cs:line 45 at WixToolsetTest.CoreIntegration.ProgramFixture.CanBuildSingleFileCompressed() in Z:\wix\Core\src\test\WixToolsetTest.CoreIntegration\ProgramFixture.cs:line 62 Result Message: System.ArgumentException : value --- .../TestData/SingleFileCompressed/Package.wxs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs index 87db9851..2f37a237 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs @@ -4,7 +4,9 @@ - + + + -- cgit v1.2.3-55-g6feb From c99ba1a05efffd7c43edd87380bc1442942de7a9 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 8 Dec 2017 10:10:26 -0800 Subject: Support and test preprocessor variables without "var." prefix --- .../CommandLine/CommandLineOption.cs | 27 ---------- .../ExtensibilityServices/PreprocessHelper.cs | 4 +- .../ProgramFixture.cs | 62 +++++++++++++++++++++- .../TestData/SingleFileCompressed/Package.wxs | 11 ++-- 4 files changed, 71 insertions(+), 33 deletions(-) delete mode 100644 src/WixToolset.Core/CommandLine/CommandLineOption.cs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/CommandLine/CommandLineOption.cs b/src/WixToolset.Core/CommandLine/CommandLineOption.cs deleted file mode 100644 index 85a654bf..00000000 --- a/src/WixToolset.Core/CommandLine/CommandLineOption.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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 -{ - /// - /// A command line option. - /// - public struct CommandLineOption - { - public string Option; - public string Description; - public int AdditionalArguments; - - /// - /// Instantiates a new BuilderCommandLineOption. - /// - /// The option name. - /// The description of the option. - /// Count of additional arguments to require after this switch. - public CommandLineOption(string option, string description, int additionalArguments) - { - this.Option = option; - this.Description = description; - this.AdditionalArguments = additionalArguments; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs index 3b8011c4..bcbd6a67 100644 --- a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs @@ -86,7 +86,7 @@ namespace WixToolset.Core.ExtensibilityServices // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve. if (result == null) { - result = this.GetVariableValue(context, function, false); + result = this.GetVariableValue(context, function, true); } return result; @@ -403,7 +403,7 @@ namespace WixToolset.Core.ExtensibilityServices } else { - result = this.GetVariableValue(context, subString, false); + result = this.GetVariableValue(context, subString, true); } if (null == result) diff --git a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs index 62920142..614107b0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs @@ -73,7 +73,7 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(0, result); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab"))); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wir")); @@ -85,6 +85,66 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildSingleFileCompressedWithMediaTemplate() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var program = new Program(); + var result = program.Run(new WixToolsetServiceProvider(), new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + Assert.Equal(0, result); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var program = new Program(); + var result = program.Run(new WixToolsetServiceProvider(), new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel=low", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + Assert.Equal(0, result); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\lowcab1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + [Fact] public void CanBuildSimpleModule() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs index 2f37a237..0b743c81 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs @@ -4,10 +4,15 @@ - - - + + + + + + + + -- cgit v1.2.3-55-g6feb From 6008384f4ff8be1fec86861014fc392a6ddd4632 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Fri, 8 Dec 2017 14:41:40 -0500 Subject: Add broken test for PE payloads. --- .../ProgramFixture.cs | 30 ++++++++++++++++++++++ .../TestData/MultiFileCompressed/Package.en-us.wxl | 11 ++++++++ .../TestData/MultiFileCompressed/Package.wxs | 28 ++++++++++++++++++++ .../MultiFileCompressed/PackageComponents.wxs | 13 ++++++++++ .../TestData/MultiFileCompressed/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 +++ 6 files changed, 87 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs index 614107b0..1ce445e8 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs @@ -145,6 +145,36 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildMultipleFilesCompressed() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var program = new Program(); + var result = program.Run(new WixToolsetServiceProvider(), new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + Assert.Equal(0, result); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + [Fact] public void CanBuildSimpleModule() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs new file mode 100644 index 00000000..0b743c81 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs new file mode 100644 index 00000000..d65a07df --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index af520116..d33589a2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -27,6 +27,10 @@ + + + + -- cgit v1.2.3-55-g6feb From b71c36c6fdc44268de75540474c639b64e53f1b7 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 11 Dec 2017 19:16:46 -0500 Subject: Add failing ManualUpgrade test for issue resolving RemoveExistingProducts. --- .../ProgramFixture.cs | 36 ++++++++++++++++++++++ .../TestData/ManualUpgrade/Package.en-us.wxl | 11 +++++++ .../TestData/ManualUpgrade/Package.wxs | 28 +++++++++++++++++ .../TestData/ManualUpgrade/PackageComponents.wxs | 10 ++++++ .../TestData/ManualUpgrade/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 +++ 6 files changed, 90 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs index 1ce445e8..1b7a18cf 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs @@ -209,6 +209,42 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildManualUpgrade() + { + var folder = TestData.Get(@"TestData\ManualUpgrade"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var program = new Program(); + var result = program.Run(new WixToolsetServiceProvider(), new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + Assert.Equal(0, result); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wir")); + var section = intermediate.Sections.Single(); + + var wixFile = section.Tuples.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + } + } + [Fact(Skip = "Not implemented yet.")] public void CanBuildInstanceTransform() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs new file mode 100644 index 00000000..d674eb59 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index d33589a2..e4c2713c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -31,6 +31,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 59004832767115df136c553169e992577e5981d6 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Fri, 29 Dec 2017 15:12:24 -0500 Subject: Pass along include search paths to those that need it. --- src/WixToolset.Core/CommandLine/BuildCommand.cs | 3 +- .../CommandLine/CommandLineParser.cs | 6 +++- .../ProgramFixture.cs | 39 ++++++++++++++++++++++ .../TestData/IncludePath/Package.en-us.wxl | 11 ++++++ .../TestData/IncludePath/Package.wxs | 22 ++++++++++++ .../TestData/IncludePath/PackageComponents.wxs | 10 ++++++ .../TestData/IncludePath/data/Package.wxi | 4 +++ .../TestData/IncludePath/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 5 +++ 9 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index 1731caaf..fb258179 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -13,7 +13,7 @@ namespace WixToolset.Core.CommandLine internal class BuildCommand : ICommandLineCommand { - public BuildCommand(IServiceProvider serviceProvider, IEnumerable sources, IDictionary preprocessorVariables, IEnumerable locFiles, IEnumerable libraryFiles, string outputPath, OutputType outputType, string cabCachePath, IEnumerable cultures, bool bindFiles, IEnumerable bindPaths, string intermediateFolder, string contentsFile, string outputsFile, string builtOutputsFile) + public BuildCommand(IServiceProvider serviceProvider, IEnumerable sources, IDictionary preprocessorVariables, IEnumerable locFiles, IEnumerable libraryFiles, string outputPath, OutputType outputType, string cabCachePath, IEnumerable cultures, bool bindFiles, IEnumerable bindPaths, IEnumerable includeSearchPaths, string intermediateFolder, string contentsFile, string outputsFile, string builtOutputsFile) { this.ServiceProvider = serviceProvider; this.Messaging = serviceProvider.GetService(); @@ -29,6 +29,7 @@ namespace WixToolset.Core.CommandLine this.Cultures = cultures; this.BindFiles = bindFiles; this.BindPaths = bindPaths; + this.IncludeSearchPaths = includeSearchPaths; this.IntermediateFolder = intermediateFolder ?? Path.GetTempPath(); this.ContentsFile = contentsFile; diff --git a/src/WixToolset.Core/CommandLine/CommandLineParser.cs b/src/WixToolset.Core/CommandLine/CommandLineParser.cs index 3c7f3d1e..500bed08 100644 --- a/src/WixToolset.Core/CommandLine/CommandLineParser.cs +++ b/src/WixToolset.Core/CommandLine/CommandLineParser.cs @@ -171,6 +171,10 @@ namespace WixToolset.Core.CommandLine case "-version": showVersion = true; return true; + + case "sval": + // todo: implement + return true; } return false; @@ -207,7 +211,7 @@ namespace WixToolset.Core.CommandLine var variables = this.GatherPreprocessorVariables(defines); var bindPathList = this.GatherBindPaths(bindPaths); var type = CalculateOutputType(outputType, outputFile); - return new BuildCommand(this.ServiceProvider, sourceFiles, variables, locFiles, libraryFiles, outputFile, type, cabCachePath, cultures, bindFiles, bindPathList, intermediateFolder, contentsFile, outputsFile, builtOutputsFile); + return new BuildCommand(this.ServiceProvider, sourceFiles, variables, locFiles, libraryFiles, outputFile, type, cabCachePath, cultures, bindFiles, bindPathList, includePaths, intermediateFolder, contentsFile, outputsFile, builtOutputsFile); } case Commands.Compile: diff --git a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs index 7e54174d..a171981a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs @@ -278,6 +278,45 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildWithIncludePath() + { + var folder = TestData.Get(@"TestData\IncludePath"); + var bindpath = Path.Combine(folder, "data"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var program = new Program(); + var result = program.Run(new WixToolsetServiceProvider(), null, new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", bindpath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi"), + "-i", bindpath, + }); + + Assert.Equal(0, result); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wir")); + var section = intermediate.Sections.Single(); + + var wixFile = section.Tuples.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + } + } + [Fact(Skip = "Not implemented yet.")] public void CanBuildInstanceTransform() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs new file mode 100644 index 00000000..8deab961 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi new file mode 100644 index 00000000..f2df3b86 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi @@ -0,0 +1,4 @@ + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index d406a0da..d1901b38 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -27,6 +27,11 @@ + + + + + -- cgit v1.2.3-55-g6feb From 301388abc7bfe230630e33bfd96ae4af43d59fb0 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 8 Jan 2018 13:12:02 -0500 Subject: Add failing unit test for .NET assemblies --- .../ProgramFixture.cs | 40 +++++++++++++++++++++ .../TestData/Assembly/Package.en-us.wxl | 11 ++++++ .../TestData/Assembly/Package.wxs | 21 +++++++++++ .../TestData/Assembly/PackageComponents.wxs | 10 ++++++ .../TestData/Assembly/data/candle.exe | Bin 0 -> 28672 bytes .../WixToolsetTest.CoreIntegration.csproj | 4 +++ 6 files changed, 86 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs index a171981a..4af7fc44 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs @@ -317,6 +317,46 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildWithAssembly() + { + var folder = TestData.Get(@"TestData\Assembly"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var program = new Program(); + var result = program.Run(new WixToolsetServiceProvider(), null, new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + Assert.Equal(0, result); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\AssemblyMsiPackage\candle.exe"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wir")); + var section = intermediate.Sections.Single(); + + var wixFile = section.Tuples.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\candle.exe"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"candle.exe", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + + var msiAssemblyNameTuples = section.Tuples.OfType(); + Assert.NotEmpty(msiAssemblyNameTuples); + } + } + [Fact(Skip = "Not implemented yet.")] public void CanBuildInstanceTransform() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs new file mode 100644 index 00000000..d9a2a34e --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs new file mode 100644 index 00000000..e0c84c63 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe new file mode 100644 index 00000000..18129b73 Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe differ diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index d1901b38..6d11b2d6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -44,6 +44,10 @@ + + + + -- cgit v1.2.3-55-g6feb From a2614e6e11108abba52b9eca8f460d9ab57513f5 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 3 Sep 2018 18:19:08 -0400 Subject: Warn on preprocessor variable redefinition only when the values are different. Fixes wixtoolset/issues#5853 --- .../ExtensibilityServices/PreprocessHelper.cs | 2 +- .../PreprocessorFixture.cs | 44 ++++++++++++++++++++++ .../TestData/Variables/Package.en-us.wxl | 11 ++++++ .../TestData/Variables/Package.wxs | 28 ++++++++++++++ .../TestData/Variables/PackageComponents.wxs | 10 +++++ .../TestData/Variables/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 ++ 7 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs index 0e4bba51..562f094f 100644 --- a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs @@ -45,7 +45,7 @@ namespace WixToolset.Core.ExtensibilityServices } else { - if (showWarning) + if (showWarning && value != currentValue) { this.Messaging.Write(WarningMessages.VariableDeclarationCollision(context.CurrentSourceLineNumber, name, value, currentValue)); } diff --git a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs new file mode 100644 index 00000000..1193b685 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs @@ -0,0 +1,44 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class PreprocessorFixture + { + [Fact] + public void VariableRedefinitionIsAWarning() + { + var folder = TestData.Get(@"TestData\Variables"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }, out var messages); + Assert.Equal(0, result); + + var warnings = messages.Where(message => message.Id == 1118); + Assert.Single(warnings); + } + } + } +} + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs new file mode 100644 index 00000000..896b7e3f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 516bd95c..f52d368b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -49,6 +49,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 38b6fe65e36ed9442e3fc714dea1793d457f0b20 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Thu, 6 Sep 2018 20:33:40 -0400 Subject: Fix codepage handling to cover both Framework- and Core- supported code pages. --- src/WixToolset.Core/Common.cs | 9 ++++++++- .../TestData/SingleFile/Package.wxs | 2 +- .../TestData/SingleFileCompressed/Package.wxs | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs index ebcbd36f..2d8f9509 100644 --- a/src/WixToolset.Core/Common.cs +++ b/src/WixToolset.Core/Common.cs @@ -172,6 +172,8 @@ namespace WixToolset.Core /// The code page is invalid for summary information. public static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + try { Encoding encoding; @@ -190,7 +192,7 @@ namespace WixToolset.Core return -1; } - encoding = CodePagesEncodingProvider.Instance.GetEncoding(codePage); + encoding = Encoding.GetEncoding(codePage); } else { @@ -208,6 +210,11 @@ namespace WixToolset.Core } } + if (encoding == null) + { + throw new WixException(ErrorMessages.IllegalCodepage(sourceLineNumbers, codePage)); + } + return encoding.CodePage; } catch (ArgumentException ex) diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs index cdc323ec..6da3dcbe 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs @@ -1,6 +1,6 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs index 0b743c81..8bb1f6af 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs @@ -1,6 +1,6 @@ - + -- cgit v1.2.3-55-g6feb From cd1709996876ac1d6ff5750cd573b9be78313a9e Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Tue, 18 Sep 2018 17:52:39 -0400 Subject: Fix ?foreach? so all loop values get "executed." --- src/WixToolset.Core/Preprocessor.cs | 28 ++++++++++++---------- .../PreprocessorFixture.cs | 24 +++++++++++++++++++ .../TestData/ForEach/Package.en-us.wxl | 11 +++++++++ .../TestData/ForEach/Package.wxs | 23 ++++++++++++++++++ .../TestData/ForEach/PackageComponents.wxs | 12 ++++++++++ .../TestData/ForEach/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 ++++ 7 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs index acba0b5f..9f0ab1bb 100644 --- a/src/WixToolset.Core/Preprocessor.cs +++ b/src/WixToolset.Core/Preprocessor.cs @@ -762,25 +762,27 @@ namespace WixToolset.Core } using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) - using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) { // process each iteration, updating the variable's value each time foreach (string varValue in varValues) { - // Always overwrite foreach variables. - this.Helper.AddVariable(this.Context, varName, varValue, false); - - try - { - this.PreprocessReader(false, loopReader, container, offset); - } - catch (XmlException e) + using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) { - this.UpdateCurrentLineNumber(loopReader, offset); - throw new WixException(ErrorMessages.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message)); - } + // Always overwrite foreach variables. + this.Helper.AddVariable(this.Context, varName, varValue, false); - fragmentStream.Position = 0; // seek back to the beginning for the next loop. + try + { + this.PreprocessReader(false, loopReader, container, offset); + } + catch (XmlException e) + { + this.UpdateCurrentLineNumber(loopReader, offset); + throw new WixException(ErrorMessages.InvalidXml(this.Context.CurrentSourceLineNumber, "source", e.Message)); + } + + fragmentStream.Position = 0; // seek back to the beginning for the next loop. + } } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs index 1193b685..9c60c902 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs @@ -39,6 +39,30 @@ namespace WixToolsetTest.CoreIntegration Assert.Single(warnings); } } + + [Fact] + public void ForEachLoopsWork() + { + var folder = TestData.Get(@"TestData\ForEach"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }, out var messages); + Assert.Equal(0, result); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs new file mode 100644 index 00000000..4bc7e2a4 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs new file mode 100644 index 00000000..2a75e3d7 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index f52d368b..7c4578d9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -24,6 +24,10 @@ + + + + -- cgit v1.2.3-55-g6feb From aa33b92c7a2e6b699a11532056485143b0edf4a3 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Thu, 27 Sep 2018 20:10:30 -0400 Subject: Report preprocessor exceptions as errors. Fixes wixtoolset/issues#5881. --- src/WixToolset.Core/CommandLine/BuildCommand.cs | 12 +++++++++- src/WixToolset.Core/CommandLine/CompileCommand.cs | 25 ++++++++++++++++++--- .../PreprocessorFixture.cs | 26 ++++++++++++++++++++++ .../TestData/BadIf/Package.en-us.wxl | 11 +++++++++ .../TestData/BadIf/Package.wxs | 26 ++++++++++++++++++++++ .../TestData/BadIf/PackageComponents.wxs | 12 ++++++++++ .../TestData/BadIf/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 ++++ 8 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index b460e48f..16c98c83 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -6,6 +6,7 @@ namespace WixToolset.Core.CommandLine using System.Collections.Generic; using System.IO; using System.Linq; + using System.Xml.Linq; using WixToolset.Data; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; @@ -173,7 +174,16 @@ namespace WixToolset.Core.CommandLine preprocessor.Platform = Platform.X86; // TODO: set this correctly preprocessor.SourcePath = sourceFile.SourcePath; preprocessor.Variables = this.PreprocessorVariables; - var document = preprocessor.Execute(); + + XDocument document = null; + try + { + document = preprocessor.Execute(); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } if (this.Messaging.EncounteredError) { diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs index 6bd0f25a..ec1ce602 100644 --- a/src/WixToolset.Core/CommandLine/CompileCommand.cs +++ b/src/WixToolset.Core/CommandLine/CompileCommand.cs @@ -4,26 +4,31 @@ namespace WixToolset.Core.CommandLine { using System; using System.Collections.Generic; + using System.Xml.Linq; using WixToolset.Data; using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; internal class CompileCommand : ICommandLineCommand { public CompileCommand(IServiceProvider serviceProvider, IEnumerable sources, IDictionary preprocessorVariables) { - this.PreprocessorVariables = preprocessorVariables; this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); this.SourceFiles = sources; + this.PreprocessorVariables = preprocessorVariables; } private IServiceProvider ServiceProvider { get; } - public IEnumerable IncludeSearchPaths { get; } + public IMessaging Messaging { get; } private IEnumerable SourceFiles { get; } private IDictionary PreprocessorVariables { get; } + public IEnumerable IncludeSearchPaths { get; } + public int Execute() { foreach (var sourceFile in this.SourceFiles) @@ -33,7 +38,21 @@ namespace WixToolset.Core.CommandLine preprocessor.Platform = Platform.X86; // TODO: set this correctly preprocessor.SourcePath = sourceFile.SourcePath; preprocessor.Variables = new Dictionary(this.PreprocessorVariables); - var document = preprocessor.Execute(); + + XDocument document = null; + try + { + document = preprocessor.Execute(); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + if (this.Messaging.EncounteredError) + { + continue; + } var compiler = new Compiler(this.ServiceProvider); compiler.OutputPath = sourceFile.OutputPath; diff --git a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs index 9c60c902..ebc713ed 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs @@ -63,6 +63,32 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(0, result); } } + + [Fact] + public void NonterminatedPreprocessorInstructionShowsSourceLineNumber() + { + var folder = TestData.Get(@"TestData\BadIf"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }, out var messages); + + Assert.Equal(147, result); + Assert.StartsWith("Found a ", messages.Single().ToString()); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs new file mode 100644 index 00000000..73577ce5 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs new file mode 100644 index 00000000..2a75e3d7 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 7c4578d9..38b7dc81 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -28,6 +28,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 822d917960cbd35f506598af4baa6a20ad4b447e Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 24 Oct 2018 21:06:51 -0700 Subject: Re-introduce "decompile" to backend --- src/WixToolset.Core.Burn/BundleBackend.cs | 4 +- .../Decompile/DecompileMsiOrMsmCommand.cs | 75 + .../Decompile/Decompiler.cs | 9229 +++++++++++++++++++ .../Decompile/DecompilerCore.cs | 110 + src/WixToolset.Core.WindowsInstaller/Decompiler.cs | 9356 -------------------- .../DecompilerCore.cs | 154 - src/WixToolset.Core.WindowsInstaller/MsiBackend.cs | 23 +- src/WixToolset.Core.WindowsInstaller/MsmBackend.cs | 23 +- src/WixToolset.Core.WindowsInstaller/MspBackend.cs | 4 +- src/WixToolset.Core.WindowsInstaller/MstBackend.cs | 4 +- .../CommandLine/DecompileCommand.cs | 212 + src/WixToolset.Core/DecompileContext.cs | 20 +- src/WixToolset.Core/Decompiler.cs | 12 +- src/WixToolset.Core/IDecompiler.cs | 4 +- src/WixToolset.Core/OptimizeCA.cs | 4 +- .../DecompileFixture.cs | 41 + .../DecompileSingleFileCompressed/Expected.wxs | 21 + .../DecompileSingleFileCompressed/example.cab | Bin 0 -> 137 bytes .../DecompileSingleFileCompressed/example.msi | Bin 0 -> 32768 bytes .../WixToolsetTest.CoreIntegration.csproj | 3 + 20 files changed, 9766 insertions(+), 9533 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/DecompilerCore.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Decompiler.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/DecompilerCore.cs create mode 100644 src/WixToolset.Core/CommandLine/DecompileCommand.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/BundleBackend.cs b/src/WixToolset.Core.Burn/BundleBackend.cs index 3baa526e..1d833b93 100644 --- a/src/WixToolset.Core.Burn/BundleBackend.cs +++ b/src/WixToolset.Core.Burn/BundleBackend.cs @@ -1,4 +1,4 @@ -// 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. +// 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.Burn { @@ -27,7 +27,7 @@ namespace WixToolset.Core.Burn return new BindResult { FileTransfers = command.FileTransfers, TrackedFiles = command.TrackedFiles }; } - public BindResult Decompile(IDecompileContext context) + public DecompileResult Decompile(IDecompileContext context) { throw new NotImplementedException(); } diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs new file mode 100644 index 00000000..130f5ea8 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs @@ -0,0 +1,75 @@ +// 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.Unbind +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Xml.Linq; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + using WixToolset.Msi; + + internal class DecompileMsiOrMsmCommand + { + public DecompileMsiOrMsmCommand(IDecompileContext context, IEnumerable backendExtensions) + { + this.Context = context; + this.Extensions = backendExtensions; + this.Messaging = context.ServiceProvider.GetService(); + } + + private IDecompileContext Context { get; } + + private IEnumerable Extensions { get; } + + private IMessaging Messaging { get; } + + public DecompileResult Execute() + { + var result = new DecompileResult(); + + try + { + using (var database = new Database(this.Context.DecompilePath, OpenDatabase.ReadOnly)) + { + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, database, this.Context.DecompilePath, this.Context.DecompileType, this.Context.ExtractFolder, this.Context.IntermediateFolder, this.Context.IsAdminImage, false, skipSummaryInfo: false); + var output = unbindCommand.Execute(); + + var decompiler = new Decompiler(this.Messaging, this.Extensions, this.Context.BaseSourcePath, this.Context.SuppressCustomTables, this.Context.SuppressDroppingEmptyTables, this.Context.SuppressUI, this.Context.TreatProductAsModule); + var wxs = decompiler.Decompile(output); + + wxs.Save(this.Context.OutputPath, SaveOptions.OmitDuplicateNamespaces); + result.SourceDocumentPath = this.Context.OutputPath; + + // extract the files from the cabinets + if (!String.IsNullOrEmpty(this.Context.ExtractFolder) && !this.Context.SuppressExtractCabinets) + { + var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.DecompilePath, this.Context.ExtractFolder, this.Context.IntermediateFolder); + extractCommand.Execute(); + + result.ExtractedFilePaths = extractCommand.ExtractedFiles; + } + else + { + result.ExtractedFilePaths = new string[0]; + } + } + } + catch (Win32Exception e) + { + if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED + { + throw new WixException(ErrorMessages.OpenDatabaseFailed(this.Context.DecompilePath)); + } + + throw; + } + + return result; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs new file mode 100644 index 00000000..26e1f399 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -0,0 +1,9229 @@ +// 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 +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using System.Xml.Linq; + using WixToolset.Core; + using WixToolset.Core.Native; + using WixToolset.Core.WindowsInstaller.Rows; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + using Wix = WixToolset.Data.Serialize; + + /// + /// Decompiles an msi database into WiX source. + /// + internal class Decompiler + { + private static readonly Regex NullSplitter = new Regex(@"\[~]"); + + private bool compressed; + private bool shortNames; + private DecompilerCore core; + private string modularizationGuid; + private readonly Hashtable patchTargetFiles; + private readonly Hashtable sequenceElements; + private readonly TableDefinitionCollection tableDefinitions; + + /// + /// Creates a new decompiler object with a default set of table definitions. + /// + public Decompiler(IMessaging messaging, IEnumerable extensions, string baseSourcePath, bool suppressCustomTables, bool suppressDroppingEmptyTables, bool suppressUI, bool treatProductAsModule) + { + this.Messaging = messaging; + this.Extensions = extensions; + this.BaseSourcePath = String.IsNullOrEmpty(baseSourcePath) ? "SourceDir" : baseSourcePath; + this.SuppressCustomTables = suppressCustomTables; + this.SuppressDroppingEmptyTables = suppressDroppingEmptyTables; + this.SuppressUI = suppressUI; + this.TreatProductAsModule = treatProductAsModule; + + this.ExtensionsByTableName = new Dictionary(); + this.StandardActions = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id); + + this.patchTargetFiles = new Hashtable(); + this.sequenceElements = new Hashtable(); + this.tableDefinitions = new TableDefinitionCollection(); + } + + private IMessaging Messaging { get; } + + private IEnumerable Extensions { get; } + + private Dictionary ExtensionsByTableName { get; } + + private string BaseSourcePath { get; } + + private bool SuppressCustomTables { get; } + + private bool SuppressDroppingEmptyTables { get; } + + private bool SuppressRelativeActionSequencing { get; } + + private bool SuppressUI { get; } + + private bool TreatProductAsModule { get; } + + private OutputType OutputType { get; set; } + + private Dictionary StandardActions { get; } + + /// + /// Decompile the database file. + /// + /// The output to decompile. + /// The serialized WiX source code. + public XDocument Decompile(Output output) + { + if (null == output) + { + throw new ArgumentNullException("output"); + } + + this.OutputType = output.Type; + + // collect the table definitions from the output + this.tableDefinitions.Clear(); + foreach (var table in output.Tables) + { + this.tableDefinitions.Add(table.Definition); + } + + // add any missing standard and wix-specific table definitions + foreach (var tableDefinition in WindowsInstallerStandardInternal.GetTableDefinitions()) + { + if (!this.tableDefinitions.Contains(tableDefinition.Name)) + { + this.tableDefinitions.Add(tableDefinition); + } + } + + // add any missing extension table definitions +#if TODO_DECOMPILER_EXTENSIONS + foreach (var extension in this.Extensions) + { + this.AddExtension(extension); + } +#endif + + var wixElement = new Wix.Wix(); + Wix.IParentElement rootElement; + + switch (this.OutputType) + { + case OutputType.Module: + rootElement = new Wix.Module(); + break; + case OutputType.PatchCreation: + rootElement = new Wix.PatchCreation(); + break; + case OutputType.Product: + rootElement = new Wix.Product(); + break; + default: + throw new InvalidOperationException("Unknown output type."); + } + wixElement.AddChild((Wix.ISchemaElement)rootElement); + + // try to decompile the database file + try + { + this.core = new DecompilerCore(rootElement); + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + this.InitializeDecompile(output.Tables, output.Codepage); + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // decompile the tables + this.DecompileTables(output); + + // finalize the decompiler and its extensions + this.FinalizeDecompile(output.Tables); + } + finally + { + this.core = null; + } + + var document = new XDocument(); + using (var writer = document.CreateWriter()) + { + wixElement.OutputXml(writer); + } + + // return the XML document only if decompilation completed successfully + return this.Messaging.EncounteredError ? null : document; + } + +#if TODO_DECOMPILER_EXTENSIONS + private void AddExtension(IWindowsInstallerBackendDecompilerExtension extension) + { + if (null != extension.TableDefinitions) + { + foreach (TableDefinition tableDefinition in extension.TableDefinitions) + { + if (!this.ExtensionsByTableName.ContainsKey(tableDefinition.Name)) + { + this.ExtensionsByTableName.Add(tableDefinition.Name, extension); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name)); + } + } + } + } +#endif + + /// + /// Set the common control attributes in a control element. + /// + /// The control attributes. + /// The control element. + private static void SetControlAttributes(int attributes, Wix.Control control) + { + if (0 == (attributes & MsiInterop.MsidbControlAttributesEnabled)) + { + control.Disabled = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbControlAttributesIndirect == (attributes & MsiInterop.MsidbControlAttributesIndirect)) + { + control.Indirect = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbControlAttributesInteger == (attributes & MsiInterop.MsidbControlAttributesInteger)) + { + control.Integer = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbControlAttributesLeftScroll == (attributes & MsiInterop.MsidbControlAttributesLeftScroll)) + { + control.LeftScroll = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbControlAttributesRightAligned == (attributes & MsiInterop.MsidbControlAttributesRightAligned)) + { + control.RightAligned = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbControlAttributesRTLRO == (attributes & MsiInterop.MsidbControlAttributesRTLRO)) + { + control.RightToLeft = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbControlAttributesSunken == (attributes & MsiInterop.MsidbControlAttributesSunken)) + { + control.Sunken = Wix.YesNoType.yes; + } + + if (0 == (attributes & MsiInterop.MsidbControlAttributesVisible)) + { + control.Hidden = Wix.YesNoType.yes; + } + } + + /// + /// Creates an action element. + /// + /// The action row from which the element should be created. + private void CreateActionElement(WixActionRow actionRow) + { + Wix.ISchemaElement actionElement = null; + + if (null != this.core.GetIndexedElement("CustomAction", actionRow.Action)) // custom action + { + var custom = new Wix.Custom(); + + custom.Action = actionRow.Action; + + if (null != actionRow.Condition) + { + custom.Content = actionRow.Condition; + } + + switch (actionRow.Sequence) + { + case (-4): + custom.OnExit = Wix.ExitType.suspend; + break; + case (-3): + custom.OnExit = Wix.ExitType.error; + break; + case (-2): + custom.OnExit = Wix.ExitType.cancel; + break; + case (-1): + custom.OnExit = Wix.ExitType.success; + break; + default: + if (null != actionRow.Before) + { + custom.Before = actionRow.Before; + } + else if (null != actionRow.After) + { + custom.After = actionRow.After; + } + else if (0 < actionRow.Sequence) + { + custom.Sequence = actionRow.Sequence; + } + break; + } + + actionElement = custom; + } + else if (null != this.core.GetIndexedElement("Dialog", actionRow.Action)) // dialog + { + var show = new Wix.Show(); + + show.Dialog = actionRow.Action; + + if (null != actionRow.Condition) + { + show.Content = actionRow.Condition; + } + + switch (actionRow.Sequence) + { + case (-4): + show.OnExit = Wix.ExitType.suspend; + break; + case (-3): + show.OnExit = Wix.ExitType.error; + break; + case (-2): + show.OnExit = Wix.ExitType.cancel; + break; + case (-1): + show.OnExit = Wix.ExitType.success; + break; + default: + if (null != actionRow.Before) + { + show.Before = actionRow.Before; + } + else if (null != actionRow.After) + { + show.After = actionRow.After; + } + else if (0 < actionRow.Sequence) + { + show.Sequence = actionRow.Sequence; + } + break; + } + + actionElement = show; + } + else // possibly a standard action without suggested sequence information + { + actionElement = this.CreateStandardActionElement(actionRow); + } + + // add the action element to the appropriate sequence element + if (null != actionElement) + { + var sequenceTable = actionRow.SequenceTable.ToString(); + var sequenceElement = (Wix.IParentElement)this.sequenceElements[sequenceTable]; + + if (null == sequenceElement) + { + switch (actionRow.SequenceTable) + { + case SequenceTable.AdminExecuteSequence: + sequenceElement = new Wix.AdminExecuteSequence(); + break; + case SequenceTable.AdminUISequence: + sequenceElement = new Wix.AdminUISequence(); + break; + case SequenceTable.AdvtExecuteSequence: + sequenceElement = new Wix.AdvertiseExecuteSequence(); + break; + case SequenceTable.InstallExecuteSequence: + sequenceElement = new Wix.InstallExecuteSequence(); + break; + case SequenceTable.InstallUISequence: + sequenceElement = new Wix.InstallUISequence(); + break; + default: + throw new InvalidOperationException("Unknown sequence table."); + } + + this.core.RootElement.AddChild((Wix.ISchemaElement)sequenceElement); + this.sequenceElements.Add(sequenceTable, sequenceElement); + } + + try + { + sequenceElement.AddChild(actionElement); + } + catch (System.ArgumentException) // action/dialog is not valid for this sequence + { + this.Messaging.Write(WarningMessages.IllegalActionInSequence(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); + } + } + } + + /// + /// Creates a standard action element. + /// + /// The action row from which the element should be created. + /// The created element. + private Wix.ISchemaElement CreateStandardActionElement(WixActionRow actionRow) + { + Wix.ActionSequenceType actionElement = null; + + switch (actionRow.Action) + { + case "AllocateRegistrySpace": + actionElement = new Wix.AllocateRegistrySpace(); + break; + case "AppSearch": + this.StandardActions.TryGetValue(actionRow.GetPrimaryKey(), out var appSearchActionRow); + + if (null != actionRow.Before || null != actionRow.After || (null != appSearchActionRow && actionRow.Sequence != appSearchActionRow.Sequence)) + { + var appSearch = new Wix.AppSearch(); + + if (null != actionRow.Condition) + { + appSearch.Content = actionRow.Condition; + } + + if (null != actionRow.Before) + { + appSearch.Before = actionRow.Before; + } + else if (null != actionRow.After) + { + appSearch.After = actionRow.After; + } + else if (0 < actionRow.Sequence) + { + appSearch.Sequence = actionRow.Sequence; + } + + return appSearch; + } + break; + case "BindImage": + actionElement = new Wix.BindImage(); + break; + case "CCPSearch": + var ccpSearch = new Wix.CCPSearch(); + Decompiler.SequenceRelativeAction(actionRow, ccpSearch); + return ccpSearch; + case "CostFinalize": + actionElement = new Wix.CostFinalize(); + break; + case "CostInitialize": + actionElement = new Wix.CostInitialize(); + break; + case "CreateFolders": + actionElement = new Wix.CreateFolders(); + break; + case "CreateShortcuts": + actionElement = new Wix.CreateShortcuts(); + break; + case "DeleteServices": + actionElement = new Wix.DeleteServices(); + break; + case "DisableRollback": + var disableRollback = new Wix.DisableRollback(); + Decompiler.SequenceRelativeAction(actionRow, disableRollback); + return disableRollback; + case "DuplicateFiles": + actionElement = new Wix.DuplicateFiles(); + break; + case "ExecuteAction": + actionElement = new Wix.ExecuteAction(); + break; + case "FileCost": + actionElement = new Wix.FileCost(); + break; + case "FindRelatedProducts": + var findRelatedProducts = new Wix.FindRelatedProducts(); + Decompiler.SequenceRelativeAction(actionRow, findRelatedProducts); + return findRelatedProducts; + case "ForceReboot": + var forceReboot = new Wix.ForceReboot(); + Decompiler.SequenceRelativeAction(actionRow, forceReboot); + return forceReboot; + case "InstallAdminPackage": + actionElement = new Wix.InstallAdminPackage(); + break; + case "InstallExecute": + var installExecute = new Wix.InstallExecute(); + Decompiler.SequenceRelativeAction(actionRow, installExecute); + return installExecute; + case "InstallExecuteAgain": + var installExecuteAgain = new Wix.InstallExecuteAgain(); + Decompiler.SequenceRelativeAction(actionRow, installExecuteAgain); + return installExecuteAgain; + case "InstallFiles": + actionElement = new Wix.InstallFiles(); + break; + case "InstallFinalize": + actionElement = new Wix.InstallFinalize(); + break; + case "InstallInitialize": + actionElement = new Wix.InstallInitialize(); + break; + case "InstallODBC": + actionElement = new Wix.InstallODBC(); + break; + case "InstallServices": + actionElement = new Wix.InstallServices(); + break; + case "InstallValidate": + actionElement = new Wix.InstallValidate(); + break; + case "IsolateComponents": + actionElement = new Wix.IsolateComponents(); + break; + case "LaunchConditions": + var launchConditions = new Wix.LaunchConditions(); + Decompiler.SequenceRelativeAction(actionRow, launchConditions); + return launchConditions; + case "MigrateFeatureStates": + actionElement = new Wix.MigrateFeatureStates(); + break; + case "MoveFiles": + actionElement = new Wix.MoveFiles(); + break; + case "MsiPublishAssemblies": + actionElement = new Wix.MsiPublishAssemblies(); + break; + case "MsiUnpublishAssemblies": + actionElement = new Wix.MsiUnpublishAssemblies(); + break; + case "PatchFiles": + actionElement = new Wix.PatchFiles(); + break; + case "ProcessComponents": + actionElement = new Wix.ProcessComponents(); + break; + case "PublishComponents": + actionElement = new Wix.PublishComponents(); + break; + case "PublishFeatures": + actionElement = new Wix.PublishFeatures(); + break; + case "PublishProduct": + actionElement = new Wix.PublishProduct(); + break; + case "RegisterClassInfo": + actionElement = new Wix.RegisterClassInfo(); + break; + case "RegisterComPlus": + actionElement = new Wix.RegisterComPlus(); + break; + case "RegisterExtensionInfo": + actionElement = new Wix.RegisterExtensionInfo(); + break; + case "RegisterFonts": + actionElement = new Wix.RegisterFonts(); + break; + case "RegisterMIMEInfo": + actionElement = new Wix.RegisterMIMEInfo(); + break; + case "RegisterProduct": + actionElement = new Wix.RegisterProduct(); + break; + case "RegisterProgIdInfo": + actionElement = new Wix.RegisterProgIdInfo(); + break; + case "RegisterTypeLibraries": + actionElement = new Wix.RegisterTypeLibraries(); + break; + case "RegisterUser": + actionElement = new Wix.RegisterUser(); + break; + case "RemoveDuplicateFiles": + actionElement = new Wix.RemoveDuplicateFiles(); + break; + case "RemoveEnvironmentStrings": + actionElement = new Wix.RemoveEnvironmentStrings(); + break; + case "RemoveExistingProducts": + var removeExistingProducts = new Wix.RemoveExistingProducts(); + Decompiler.SequenceRelativeAction(actionRow, removeExistingProducts); + return removeExistingProducts; + case "RemoveFiles": + actionElement = new Wix.RemoveFiles(); + break; + case "RemoveFolders": + actionElement = new Wix.RemoveFolders(); + break; + case "RemoveIniValues": + actionElement = new Wix.RemoveIniValues(); + break; + case "RemoveODBC": + actionElement = new Wix.RemoveODBC(); + break; + case "RemoveRegistryValues": + actionElement = new Wix.RemoveRegistryValues(); + break; + case "RemoveShortcuts": + actionElement = new Wix.RemoveShortcuts(); + break; + case "ResolveSource": + var resolveSource = new Wix.ResolveSource(); + Decompiler.SequenceRelativeAction(actionRow, resolveSource); + return resolveSource; + case "RMCCPSearch": + var rmccpSearch = new Wix.RMCCPSearch(); + Decompiler.SequenceRelativeAction(actionRow, rmccpSearch); + return rmccpSearch; + case "ScheduleReboot": + var scheduleReboot = new Wix.ScheduleReboot(); + Decompiler.SequenceRelativeAction(actionRow, scheduleReboot); + return scheduleReboot; + case "SelfRegModules": + actionElement = new Wix.SelfRegModules(); + break; + case "SelfUnregModules": + actionElement = new Wix.SelfUnregModules(); + break; + case "SetODBCFolders": + actionElement = new Wix.SetODBCFolders(); + break; + case "StartServices": + actionElement = new Wix.StartServices(); + break; + case "StopServices": + actionElement = new Wix.StopServices(); + break; + case "UnpublishComponents": + actionElement = new Wix.UnpublishComponents(); + break; + case "UnpublishFeatures": + actionElement = new Wix.UnpublishFeatures(); + break; + case "UnregisterClassInfo": + actionElement = new Wix.UnregisterClassInfo(); + break; + case "UnregisterComPlus": + actionElement = new Wix.UnregisterComPlus(); + break; + case "UnregisterExtensionInfo": + actionElement = new Wix.UnregisterExtensionInfo(); + break; + case "UnregisterFonts": + actionElement = new Wix.UnregisterFonts(); + break; + case "UnregisterMIMEInfo": + actionElement = new Wix.UnregisterMIMEInfo(); + break; + case "UnregisterProgIdInfo": + actionElement = new Wix.UnregisterProgIdInfo(); + break; + case "UnregisterTypeLibraries": + actionElement = new Wix.UnregisterTypeLibraries(); + break; + case "ValidateProductID": + actionElement = new Wix.ValidateProductID(); + break; + case "WriteEnvironmentStrings": + actionElement = new Wix.WriteEnvironmentStrings(); + break; + case "WriteIniValues": + actionElement = new Wix.WriteIniValues(); + break; + case "WriteRegistryValues": + actionElement = new Wix.WriteRegistryValues(); + break; + default: + this.Messaging.Write(WarningMessages.UnknownAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); + return null; + } + + if (actionElement != null) + { + this.SequenceStandardAction(actionRow, actionElement); + } + + return actionElement; + } + + /// + /// Applies the condition and sequence to a standard action element based on the action row data. + /// + /// Action row data from the database. + /// Element to be sequenced. + private void SequenceStandardAction(WixActionRow actionRow, Wix.ActionSequenceType actionElement) + { + if (null != actionRow.Condition) + { + actionElement.Content = actionRow.Condition; + } + + if ((null != actionRow.Before || null != actionRow.After) && 0 == actionRow.Sequence) + { + this.Messaging.Write(WarningMessages.DecompiledStandardActionRelativelyScheduledInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); + } + else if (0 < actionRow.Sequence) + { + actionElement.Sequence = actionRow.Sequence; + } + } + + /// + /// Applies the condition and relative sequence to an action element based on the action row data. + /// + /// Action row data from the database. + /// Element to be sequenced. + private static void SequenceRelativeAction(WixActionRow actionRow, Wix.ActionModuleSequenceType actionElement) + { + if (null != actionRow.Condition) + { + actionElement.Content = actionRow.Condition; + } + + if (null != actionRow.Before) + { + actionElement.Before = actionRow.Before; + } + else if (null != actionRow.After) + { + actionElement.After = actionRow.After; + } + else if (0 < actionRow.Sequence) + { + actionElement.Sequence = actionRow.Sequence; + } + } + + /// + /// Ensure that a particular property exists in the decompiled output. + /// + /// The identifier of the property. + /// The property element. + private Wix.Property EnsureProperty(string id) + { + var property = (Wix.Property)this.core.GetIndexedElement("Property", id); + + if (null == property) + { + property = new Wix.Property(); + property.Id = id; + + // create a dummy row for indexing + var row = new Row(null, this.tableDefinitions["Property"]); + row[0] = id; + + this.core.RootElement.AddChild(property); + this.core.IndexElement(row, property); + } + + return property; + } + + /// + /// Finalize decompilation. + /// + /// The collection of all tables. + private void FinalizeDecompile(TableIndexedCollection tables) + { + if (OutputType.PatchCreation == this.OutputType) + { + this.FinalizeFamilyFileRangesTable(tables); + } + else + { + this.FinalizeCheckBoxTable(tables); + this.FinalizeComponentTable(tables); + this.FinalizeDialogTable(tables); + this.FinalizeDuplicateMoveFileTables(tables); + this.FinalizeFeatureComponentsTable(tables); + this.FinalizeFileTable(tables); + this.FinalizeMIMETable(tables); + this.FinalizeMsiLockPermissionsExTable(tables); + this.FinalizeLockPermissionsTable(tables); + this.FinalizeProgIdTable(tables); + this.FinalizePropertyTable(tables); + this.FinalizeRemoveFileTable(tables); + this.FinalizeSearchTables(tables); + this.FinalizeUpgradeTable(tables); + this.FinalizeSequenceTables(tables); + this.FinalizeVerbTable(tables); + } + } + + /// + /// Finalize the CheckBox table. + /// + /// The collection of all tables. + /// + /// Enumerates through all the Control rows, looking for controls of type "CheckBox" with + /// a value in the Property column. This is then possibly matched up with a CheckBox row + /// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table. + /// + private void FinalizeCheckBoxTable(TableIndexedCollection tables) + { + // if the user has requested to suppress the UI elements, we have nothing to do + if (this.SuppressUI) + { + return; + } + + var checkBoxTable = tables["CheckBox"]; + var controlTable = tables["Control"]; + + var checkBoxes = new Hashtable(); + var checkBoxProperties = new Hashtable(); + + // index the CheckBox table + if (null != checkBoxTable) + { + foreach (var row in checkBoxTable.Rows) + { + checkBoxes.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row); + checkBoxProperties.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), false); + } + } + + // enumerate through the Control table, adding CheckBox values where appropriate + if (null != controlTable) + { + foreach (var row in controlTable.Rows) + { + var control = (Wix.Control)this.core.GetIndexedElement(row); + + if ("CheckBox" == Convert.ToString(row[2]) && null != row[8]) + { + var checkBoxRow = (Row)checkBoxes[row[8]]; + + if (null == checkBoxRow) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", Convert.ToString(row[8]), "CheckBox")); + } + else + { + // if we've seen this property already, create a reference to it + if (Convert.ToBoolean(checkBoxProperties[row[8]])) + { + control.CheckBoxPropertyRef = Convert.ToString(row[8]); + } + else + { + control.Property = Convert.ToString(row[8]); + checkBoxProperties[row[8]] = true; + } + + if (null != checkBoxRow[1]) + { + control.CheckBoxValue = Convert.ToString(checkBoxRow[1]); + } + } + } + } + } + } + + /// + /// Finalize the Component table. + /// + /// The collection of all tables. + /// + /// Set the keypaths for each component. + /// + private void FinalizeComponentTable(TableIndexedCollection tables) + { + var componentTable = tables["Component"]; + var fileTable = tables["File"]; + var odbcDataSourceTable = tables["ODBCDataSource"]; + var registryTable = tables["Registry"]; + + // set the component keypaths + if (null != componentTable) + { + foreach (var row in componentTable.Rows) + { + var attributes = Convert.ToInt32(row[3]); + + if (null == row[5]) + { + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0])); + + component.KeyPath = Wix.YesNoType.yes; + } + else if (MsiInterop.MsidbComponentAttributesRegistryKeyPath == (attributes & MsiInterop.MsidbComponentAttributesRegistryKeyPath)) + { + object registryObject = this.core.GetIndexedElement("Registry", Convert.ToString(row[5])); + + if (null != registryObject) + { + var registryValue = registryObject as Wix.RegistryValue; + + if (null != registryValue) + { + registryValue.KeyPath = Wix.YesNoType.yes; + } + else + { + this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", Convert.ToString(row[5]))); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "Registry")); + } + } + else if (MsiInterop.MsidbComponentAttributesODBCDataSource == (attributes & MsiInterop.MsidbComponentAttributesODBCDataSource)) + { + var odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement("ODBCDataSource", Convert.ToString(row[5])); + + if (null != odbcDataSource) + { + odbcDataSource.KeyPath = Wix.YesNoType.yes; + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "ODBCDataSource")); + } + } + else + { + var file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[5])); + + if (null != file) + { + file.KeyPath = Wix.YesNoType.yes; + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "File")); + } + } + } + } + + // add the File children elements + if (null != fileTable) + { + foreach (FileRow fileRow in fileTable.Rows) + { + var component = (Wix.Component)this.core.GetIndexedElement("Component", fileRow.Component); + var file = (Wix.File)this.core.GetIndexedElement(fileRow); + + if (null != component) + { + component.AddChild(file); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component")); + } + } + } + + // add the ODBCDataSource children elements + if (null != odbcDataSourceTable) + { + foreach (var row in odbcDataSourceTable.Rows) + { + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + var odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement(row); + + if (null != component) + { + component.AddChild(odbcDataSource); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + } + } + + // add the Registry children elements + if (null != registryTable) + { + foreach (var row in registryTable.Rows) + { + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[5])); + var registryElement = this.core.GetIndexedElement(row); + + if (null != component) + { + component.AddChild(registryElement); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[5]), "Component")); + } + } + } + } + + /// + /// Finalize the Dialog table. + /// + /// The collection of all tables. + /// + /// Sets the first, default, and cancel control for each dialog and adds all child control + /// elements to the dialog. + /// + private void FinalizeDialogTable(TableIndexedCollection tables) + { + // if the user has requested to suppress the UI elements, we have nothing to do + if (this.SuppressUI) + { + return; + } + + var controlTable = tables["Control"]; + var dialogTable = tables["Dialog"]; + + var addedControls = new Hashtable(); + var controlRows = new Hashtable(); + + // index the rows in the control rows (because we need the Control_Next value) + if (null != controlTable) + { + foreach (var row in controlTable.Rows) + { + controlRows.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row); + } + } + + if (null != dialogTable) + { + foreach (var row in dialogTable.Rows) + { + var dialog = (Wix.Dialog)this.core.GetIndexedElement(row); + var dialogId = Convert.ToString(row[0]); + + var control = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[7])); + if (null == control) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", Convert.ToString(row[7]), "Control")); + } + + // add tabbable controls + while (null != control) + { + var controlRow = (Row)controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, control.Id)]; + + control.TabSkip = Wix.YesNoType.no; + dialog.AddChild(control); + addedControls.Add(control, null); + + if (null != controlRow[10]) + { + control = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(controlRow[10])); + if (null != control) + { + // looped back to the first control in the dialog + if (addedControls.Contains(control)) + { + control = null; + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", Convert.ToString(controlRow[10]), "Control")); + } + } + else + { + control = null; + } + } + + // set default control + if (null != row[8]) + { + var defaultControl = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[8])); + + if (null != defaultControl) + { + defaultControl.Default = Wix.YesNoType.yes; + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(row[8]), "Control")); + } + } + + // set cancel control + if (null != row[9]) + { + var cancelControl = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[9])); + + if (null != cancelControl) + { + cancelControl.Cancel = Wix.YesNoType.yes; + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(row[9]), "Control")); + } + } + } + } + + // add the non-tabbable controls to the dialog + if (null != controlTable) + { + foreach (var row in controlTable.Rows) + { + var control = (Wix.Control)this.core.GetIndexedElement(row); + var dialog = (Wix.Dialog)this.core.GetIndexedElement("Dialog", Convert.ToString(row[0])); + + if (null == dialog) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Dialog")); + continue; + } + + if (!addedControls.Contains(control)) + { + control.TabSkip = Wix.YesNoType.yes; + dialog.AddChild(control); + } + } + } + } + + /// + /// Finalize the DuplicateFile and MoveFile tables. + /// + /// The collection of all tables. + /// + /// Sets the source/destination property/directory for each DuplicateFile or + /// MoveFile row. + /// + private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables) + { + var duplicateFileTable = tables["DuplicateFile"]; + var moveFileTable = tables["MoveFile"]; + + if (null != duplicateFileTable) + { + foreach (var row in duplicateFileTable.Rows) + { + var copyFile = (Wix.CopyFile)this.core.GetIndexedElement(row); + + if (null != row[4]) + { + if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[4]))) + { + copyFile.DestinationDirectory = Convert.ToString(row[4]); + } + else + { + copyFile.DestinationProperty = Convert.ToString(row[4]); + } + } + } + } + + if (null != moveFileTable) + { + foreach (var row in moveFileTable.Rows) + { + var copyFile = (Wix.CopyFile)this.core.GetIndexedElement(row); + + if (null != row[4]) + { + if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[4]))) + { + copyFile.SourceDirectory = Convert.ToString(row[4]); + } + else + { + copyFile.SourceProperty = Convert.ToString(row[4]); + } + } + + if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[5]))) + { + copyFile.DestinationDirectory = Convert.ToString(row[5]); + } + else + { + copyFile.DestinationProperty = Convert.ToString(row[5]); + } + } + } + } + + /// + /// Finalize the FamilyFileRanges table. + /// + /// The collection of all tables. + private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables) + { + var externalFilesTable = tables["ExternalFiles"]; + var familyFileRangesTable = tables["FamilyFileRanges"]; + var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"]; + + var usedProtectRanges = new Hashtable(); + + if (null != familyFileRangesTable) + { + foreach (var row in familyFileRangesTable.Rows) + { + var protectRange = new Wix.ProtectRange(); + + if (null != row[2] && null != row[3]) + { + var retainOffsets = (Convert.ToString(row[2])).Split(','); + var retainLengths = (Convert.ToString(row[3])).Split(','); + + if (retainOffsets.Length == retainLengths.Length) + { + for (var i = 0; i < retainOffsets.Length; i++) + { + if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal)) + { + protectRange.Offset = Convert.ToInt32(retainOffsets[i].Substring(2), 16); + } + else + { + protectRange.Offset = Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture); + } + + if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal)) + { + protectRange.Length = Convert.ToInt32(retainLengths[i].Substring(2), 16); + } + else + { + protectRange.Length = Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture); + } + } + } + else + { + // TODO: warn + } + } + else if (null != row[2] || null != row[3]) + { + // TODO: warn about mismatch between columns + } + + this.core.IndexElement(row, protectRange); + } + } + + if (null != externalFilesTable) + { + foreach (var row in externalFilesTable.Rows) + { + var externalFile = (Wix.ExternalFile)this.core.GetIndexedElement(row); + + var protectRange = (Wix.ProtectRange)this.core.GetIndexedElement("FamilyFileRanges", Convert.ToString(row[0]), Convert.ToString(row[1])); + if (null != protectRange) + { + externalFile.AddChild(protectRange); + usedProtectRanges[protectRange] = null; + } + } + } + + if (null != targetFiles_OptionalDataTable) + { + var targetImagesTable = tables["TargetImages"]; + var upgradedImagesTable = tables["UpgradedImages"]; + + var targetImageRows = new Hashtable(); + var upgradedImagesRows = new Hashtable(); + + // index the TargetImages table + if (null != targetImagesTable) + { + foreach (var row in targetImagesTable.Rows) + { + targetImageRows.Add(row[0], row); + } + } + + // index the UpgradedImages table + if (null != upgradedImagesTable) + { + foreach (var row in upgradedImagesTable.Rows) + { + upgradedImagesRows.Add(row[0], row); + } + } + + foreach (var row in targetFiles_OptionalDataTable.Rows) + { + var targetFile = (Wix.TargetFile)this.patchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)]; + + var targetImageRow = (Row)targetImageRows[row[0]]; + if (null == targetImageRow) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", Convert.ToString(row[0]), "TargetImages")); + continue; + } + + var upgradedImagesRow = (Row)upgradedImagesRows[targetImageRow[3]]; + if (null == upgradedImagesRow) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[3]), "UpgradedImages")); + continue; + } + + var protectRange = (Wix.ProtectRange)this.core.GetIndexedElement("FamilyFileRanges", Convert.ToString(upgradedImagesRow[4]), Convert.ToString(row[1])); + if (null != protectRange) + { + targetFile.AddChild(protectRange); + usedProtectRanges[protectRange] = null; + } + } + } + + if (null != familyFileRangesTable) + { + foreach (var row in familyFileRangesTable.Rows) + { + var protectRange = (Wix.ProtectRange)this.core.GetIndexedElement(row); + + if (!usedProtectRanges.Contains(protectRange)) + { + var protectFile = new Wix.ProtectFile(); + + protectFile.File = Convert.ToString(row[1]); + + protectFile.AddChild(protectRange); + + var family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[0])); + if (null != family) + { + family.AddChild(protectFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, familyFileRangesTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[0]), "ImageFamilies")); + } + } + } + } + } + + /// + /// Finalize the FeatureComponents table. + /// + /// The collection of all tables. + /// + /// Since tables specifying references to the FeatureComponents table have references to + /// the Feature and Component table separately, but not the FeatureComponents table specifically, + /// the FeatureComponents table and primary features must be decompiled during finalization. + /// + private void FinalizeFeatureComponentsTable(TableIndexedCollection tables) + { + var classTable = tables["Class"]; + var extensionTable = tables["Extension"]; + var msiAssemblyTable = tables["MsiAssembly"]; + var publishComponentTable = tables["PublishComponent"]; + var shortcutTable = tables["Shortcut"]; + var typeLibTable = tables["TypeLib"]; + + if (null != classTable) + { + foreach (var row in classTable.Rows) + { + this.SetPrimaryFeature(row, 11, 2); + } + } + + if (null != extensionTable) + { + foreach (var row in extensionTable.Rows) + { + this.SetPrimaryFeature(row, 4, 1); + } + } + + if (null != msiAssemblyTable) + { + foreach (var row in msiAssemblyTable.Rows) + { + this.SetPrimaryFeature(row, 1, 0); + } + } + + if (null != publishComponentTable) + { + foreach (var row in publishComponentTable.Rows) + { + this.SetPrimaryFeature(row, 4, 2); + } + } + + if (null != shortcutTable) + { + foreach (var row in shortcutTable.Rows) + { + var target = Convert.ToString(row[4]); + + if (!target.StartsWith("[", StringComparison.Ordinal) && !target.EndsWith("]", StringComparison.Ordinal)) + { + this.SetPrimaryFeature(row, 4, 3); + } + } + } + + if (null != typeLibTable) + { + foreach (var row in typeLibTable.Rows) + { + this.SetPrimaryFeature(row, 6, 2); + } + } + } + + /// + /// Finalize the File table. + /// + /// The collection of all tables. + /// + /// Sets the source, diskId, and assembly information for each file. + /// + private void FinalizeFileTable(TableIndexedCollection tables) + { + var fileTable = tables["File"]; + var mediaTable = tables["Media"]; + var msiAssemblyTable = tables["MsiAssembly"]; + var typeLibTable = tables["TypeLib"]; + + // index the media table by media id + RowDictionary mediaRows; + if (null != mediaTable) + { + mediaRows = new RowDictionary(mediaTable); + } + + // set the disk identifiers and sources for files + if (null != fileTable) + { + foreach (FileRow fileRow in fileTable.Rows) + { + var file = (Wix.File)this.core.GetIndexedElement("File", fileRow.File); + + // Don't bother processing files that are orphaned (and won't show up in the output anyway) + if (null != file.ParentElement) + { + // set the diskid + if (null != mediaTable) + { + foreach (MediaRow mediaRow in mediaTable.Rows) + { + if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1) + { + file.DiskId = Convert.ToString(mediaRow.DiskId); + break; + } + } + } + + // set the source (done here because it requires information from the Directory table) + if (OutputType.Module == this.OutputType) + { + file.Source = String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, file.Id, '.', this.modularizationGuid.Substring(1, 36).Replace('-', '_')); + } + else if (Wix.YesNoDefaultType.yes == file.Compressed || (Wix.YesNoDefaultType.no != file.Compressed && this.compressed)) + { + file.Source = String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, file.Id); + } + else // uncompressed + { + var fileName = (null != file.ShortName ? file.ShortName : file.Name); + + if (!this.shortNames && null != file.Name) + { + fileName = file.Name; + } + + if (this.compressed) // uncompressed at the root of the source image + { + file.Source = String.Concat("SourceDir", Path.DirectorySeparatorChar, fileName); + } + else + { + var sourcePath = this.GetSourcePath(file); + + file.Source = Path.Combine(sourcePath, fileName); + } + } + } + } + } + + // set the file assemblies and manifests + if (null != msiAssemblyTable) + { + foreach (var row in msiAssemblyTable.Rows) + { + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0])); + + if (null == component) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[0]), "Component")); + } + else + { + foreach (Wix.ISchemaElement element in component.Children) + { + var file = element as Wix.File; + + if (null != file && Wix.YesNoType.yes == file.KeyPath) + { + if (null != row[2]) + { + file.AssemblyManifest = Convert.ToString(row[2]); + } + + if (null != row[3]) + { + file.AssemblyApplication = Convert.ToString(row[3]); + } + + if (null == row[4] || 0 == Convert.ToInt32(row[4])) + { + file.Assembly = Wix.File.AssemblyType.net; + } + else + { + file.Assembly = Wix.File.AssemblyType.win32; + } + } + } + } + } + } + + // nest the TypeLib elements + if (null != typeLibTable) + { + foreach (var row in typeLibTable.Rows) + { + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2])); + var typeLib = (Wix.TypeLib)this.core.GetIndexedElement(row); + + foreach (Wix.ISchemaElement element in component.Children) + { + var file = element as Wix.File; + + if (null != file && Wix.YesNoType.yes == file.KeyPath) + { + file.AddChild(typeLib); + } + } + } + } + } + + /// + /// Finalize the MIME table. + /// + /// The collection of all tables. + /// + /// There is a foreign key shared between the MIME and Extension + /// tables so either one would be valid to be decompiled first, so + /// the only safe way to nest the MIME elements is to do it during finalize. + /// + private void FinalizeMIMETable(TableIndexedCollection tables) + { + var extensionTable = tables["Extension"]; + var mimeTable = tables["MIME"]; + + var comExtensions = new Hashtable(); + + if (null != extensionTable) + { + foreach (var row in extensionTable.Rows) + { + var extension = (Wix.Extension)this.core.GetIndexedElement(row); + + // index the extension + if (!comExtensions.Contains(row[0])) + { + comExtensions.Add(row[0], new ArrayList()); + } + ((ArrayList)comExtensions[row[0]]).Add(extension); + + // set the default MIME element for this extension + if (null != row[3]) + { + var mime = (Wix.MIME)this.core.GetIndexedElement("MIME", Convert.ToString(row[3])); + + if (null != mime) + { + mime.Default = Wix.YesNoType.yes; + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", Convert.ToString(row[3]), "MIME")); + } + } + } + } + + if (null != mimeTable) + { + foreach (var row in mimeTable.Rows) + { + var mime = (Wix.MIME)this.core.GetIndexedElement(row); + + if (comExtensions.Contains(row[1])) + { + var extensionElements = (ArrayList)comExtensions[row[1]]; + + foreach (Wix.Extension extension in extensionElements) + { + extension.AddChild(mime); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", Convert.ToString(row[1]), "Extension")); + } + } + } + } + + /// + /// Finalize the ProgId table. + /// + /// The collection of all tables. + /// + /// Enumerates through all the Class rows, looking for child ProgIds (these are the + /// default ProgIds for a given Class). Then go through the ProgId table and add any + /// remaining ProgIds for each Class. This happens during finalize because there is + /// a circular dependency between the Class and ProgId tables. + /// + private void FinalizeProgIdTable(TableIndexedCollection tables) + { + var classTable = tables["Class"]; + var progIdTable = tables["ProgId"]; + var extensionTable = tables["Extension"]; + var componentTable = tables["Component"]; + + var addedProgIds = new Hashtable(); + var classes = new Hashtable(); + var components = new Hashtable(); + + // add the default ProgIds for each class (and index the class table) + if (null != classTable) + { + foreach (var row in classTable.Rows) + { + var wixClass = (Wix.Class)this.core.GetIndexedElement(row); + + if (null != row[3]) + { + var progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[3])); + + if (null != progId) + { + if (addedProgIds.Contains(progId)) + { + this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, Convert.ToString(row[0]), Convert.ToString(row[3]), Convert.ToString(addedProgIds[progId]))); + } + else + { + wixClass.AddChild(progId); + addedProgIds.Add(progId, wixClass.Id); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", Convert.ToString(row[3]), "ProgId")); + } + } + + // index the Class elements for nesting of ProgId elements (which don't use the full Class primary key) + if (!classes.Contains(wixClass.Id)) + { + classes.Add(wixClass.Id, new ArrayList()); + } + ((ArrayList)classes[wixClass.Id]).Add(wixClass); + } + } + + // add the remaining non-default ProgId entries for each class + if (null != progIdTable) + { + foreach (var row in progIdTable.Rows) + { + var progId = (Wix.ProgId)this.core.GetIndexedElement(row); + + if (!addedProgIds.Contains(progId) && null != row[2] && null == progId.ParentElement) + { + var classElements = (ArrayList)classes[row[2]]; + + if (null != classElements) + { + foreach (Wix.Class wixClass in classElements) + { + wixClass.AddChild(progId); + addedProgIds.Add(progId, wixClass.Id); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", Convert.ToString(row[2]), "Class")); + } + } + } + } + + if (null != componentTable) + { + foreach (var row in componentTable.Rows) + { + var wixComponent = (Wix.Component)this.core.GetIndexedElement(row); + + // index the Class elements for nesting of ProgId elements (which don't use the full Class primary key) + if (!components.Contains(wixComponent.Id)) + { + components.Add(wixComponent.Id, new ArrayList()); + } + ((ArrayList)components[wixComponent.Id]).Add(wixComponent); + } + } + + // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension + if (null != extensionTable) + { + foreach (var row in extensionTable.Rows) + { + // ignore the extension if it isn't associated with a progId + if (null == row[2]) + { + continue; + } + + var progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[2])); + + // Haven't added the progId yet and it doesn't have a parent progId + if (!addedProgIds.Contains(progId) && null == progId.ParentElement) + { + var componentElements = (ArrayList)components[row[1]]; + + if (null != componentElements) + { + foreach (Wix.Component wixComponent in componentElements) + { + wixComponent.AddChild(progId); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + } + } + } + } + + /// + /// Finalize the Property table. + /// + /// The collection of all tables. + /// + /// Removes properties that are generated from other entries. + /// + private void FinalizePropertyTable(TableIndexedCollection tables) + { + var propertyTable = tables["Property"]; + var customActionTable = tables["CustomAction"]; + + if (null != propertyTable && null != customActionTable) + { + foreach (var row in customActionTable.Rows) + { + var bits = Convert.ToInt32(row[1]); + if (MsiInterop.MsidbCustomActionTypeHideTarget == (bits & MsiInterop.MsidbCustomActionTypeHideTarget) && + MsiInterop.MsidbCustomActionTypeInScript == (bits & MsiInterop.MsidbCustomActionTypeInScript)) + { + var property = (Wix.Property)this.core.GetIndexedElement("Property", Convert.ToString(row[0])); + + // If no other fields on the property are set we must have created it during link + if (null != property && null == property.Value && Wix.YesNoType.yes != property.Secure && Wix.YesNoType.yes != property.SuppressModularization) + { + this.core.RootElement.RemoveChild(property); + } + } + } + } + } + + /// + /// Finalize the RemoveFile table. + /// + /// The collection of all tables. + /// + /// Sets the directory/property for each RemoveFile row. + /// + private void FinalizeRemoveFileTable(TableIndexedCollection tables) + { + var removeFileTable = tables["RemoveFile"]; + + if (null != removeFileTable) + { + foreach (var row in removeFileTable.Rows) + { + var isDirectory = false; + var property = Convert.ToString(row[3]); + + // determine if the property is actually authored as a directory + if (null != this.core.GetIndexedElement("Directory", property)) + { + isDirectory = true; + } + + var element = this.core.GetIndexedElement(row); + + var removeFile = element as Wix.RemoveFile; + if (null != removeFile) + { + if (isDirectory) + { + removeFile.Directory = property; + } + else + { + removeFile.Property = property; + } + } + else + { + var removeFolder = (Wix.RemoveFolder)element; + + if (isDirectory) + { + removeFolder.Directory = property; + } + else + { + removeFolder.Property = property; + } + } + } + } + } + + /// + /// Finalize the LockPermissions table. + /// + /// The collection of all tables. + /// + /// Nests the Permission elements below their parent elements. There are no declared foreign + /// keys for the parents of the LockPermissions table. + /// + private void FinalizeLockPermissionsTable(TableIndexedCollection tables) + { + var createFolderTable = tables["CreateFolder"]; + var lockPermissionsTable = tables["LockPermissions"]; + + var createFolders = new Hashtable(); + + // index the CreateFolder table because the foreign key to this table from the + // LockPermissions table is only part of the primary key of this table + if (null != createFolderTable) + { + foreach (var row in createFolderTable.Rows) + { + var createFolder = (Wix.CreateFolder)this.core.GetIndexedElement(row); + var directoryId = Convert.ToString(row[0]); + + if (!createFolders.Contains(directoryId)) + { + createFolders.Add(directoryId, new ArrayList()); + } + ((ArrayList)createFolders[directoryId]).Add(createFolder); + } + } + + if (null != lockPermissionsTable) + { + foreach (var row in lockPermissionsTable.Rows) + { + var id = Convert.ToString(row[0]); + var table = Convert.ToString(row[1]); + + var permission = (Wix.Permission)this.core.GetIndexedElement(row); + + if ("CreateFolder" == table) + { + var createFolderElements = (ArrayList)createFolders[id]; + + if (null != createFolderElements) + { + foreach (Wix.CreateFolder createFolder in createFolderElements) + { + createFolder.AddChild(permission); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "LockPermissions", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); + } + } + else + { + var parentElement = (Wix.IParentElement)this.core.GetIndexedElement(table, id); + + if (null != parentElement) + { + parentElement.AddChild(permission); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "LockPermissions", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); + } + } + } + } + } + + /// + /// Finalize the MsiLockPermissionsEx table. + /// + /// The collection of all tables. + /// + /// Nests the PermissionEx elements below their parent elements. There are no declared foreign + /// keys for the parents of the MsiLockPermissionsEx table. + /// + private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) + { + var createFolderTable = tables["CreateFolder"]; + var msiLockPermissionsExTable = tables["MsiLockPermissionsEx"]; + + var createFolders = new Hashtable(); + + // index the CreateFolder table because the foreign key to this table from the + // MsiLockPermissionsEx table is only part of the primary key of this table + if (null != createFolderTable) + { + foreach (var row in createFolderTable.Rows) + { + var createFolder = (Wix.CreateFolder)this.core.GetIndexedElement(row); + var directoryId = Convert.ToString(row[0]); + + if (!createFolders.Contains(directoryId)) + { + createFolders.Add(directoryId, new ArrayList()); + } + ((ArrayList)createFolders[directoryId]).Add(createFolder); + } + } + + if (null != msiLockPermissionsExTable) + { + foreach (var row in msiLockPermissionsExTable.Rows) + { + var id = Convert.ToString(row[1]); + var table = Convert.ToString(row[2]); + + var permissionEx = (Wix.PermissionEx)this.core.GetIndexedElement(row); + + if ("CreateFolder" == table) + { + var createFolderElements = (ArrayList)createFolders[id]; + + if (null != createFolderElements) + { + foreach (Wix.CreateFolder createFolder in createFolderElements) + { + createFolder.AddChild(permissionEx); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiLockPermissionsEx", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); + } + } + else + { + var parentElement = (Wix.IParentElement)this.core.GetIndexedElement(table, id); + + if (null != parentElement) + { + parentElement.AddChild(permissionEx); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiLockPermissionsEx", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); + } + } + } + } + } + + /// + /// Finalize the search tables. + /// + /// The collection of all tables. + /// Does all the complex linking required for the search tables. + private void FinalizeSearchTables(TableIndexedCollection tables) + { + var appSearchTable = tables["AppSearch"]; + var ccpSearchTable = tables["CCPSearch"]; + var drLocatorTable = tables["DrLocator"]; + + var appSearches = new Hashtable(); + var ccpSearches = new Hashtable(); + var drLocators = new Hashtable(); + var locators = new Hashtable(); + var usedSearchElements = new Hashtable(); + var unusedSearchElements = new ArrayList(); + + Wix.ComplianceCheck complianceCheck = null; + + // index the AppSearch table by signatures + if (null != appSearchTable) + { + foreach (var row in appSearchTable.Rows) + { + var property = Convert.ToString(row[0]); + var signature = Convert.ToString(row[1]); + + if (!appSearches.Contains(signature)) + { + appSearches.Add(signature, new StringCollection()); + } + + ((StringCollection)appSearches[signature]).Add(property); + } + } + + // index the CCPSearch table by signatures + if (null != ccpSearchTable) + { + foreach (var row in ccpSearchTable.Rows) + { + var signature = Convert.ToString(row[0]); + + if (!ccpSearches.Contains(signature)) + { + ccpSearches.Add(signature, new StringCollection()); + } + + ((StringCollection)ccpSearches[signature]).Add(null); + + if (null == complianceCheck && !appSearches.Contains(signature)) + { + complianceCheck = new Wix.ComplianceCheck(); + this.core.RootElement.AddChild(complianceCheck); + } + } + } + + // index the directory searches by their search elements (to get back the original row) + if (null != drLocatorTable) + { + foreach (var row in drLocatorTable.Rows) + { + drLocators.Add(this.core.GetIndexedElement(row), row); + } + } + + // index the locator tables by their signatures + var locatorTableNames = new string[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" }; + foreach (var locatorTableName in locatorTableNames) + { + var locatorTable = tables[locatorTableName]; + + if (null != locatorTable) + { + foreach (var row in locatorTable.Rows) + { + var signature = Convert.ToString(row[0]); + + if (!locators.Contains(signature)) + { + locators.Add(signature, new ArrayList()); + } + + ((ArrayList)locators[signature]).Add(row); + } + } + } + + // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef) + foreach (ArrayList locatorRows in locators.Values) + { + var firstDrLocator = -1; + + for (var i = 0; i < locatorRows.Count; i++) + { + var locatorRow = (Row)locatorRows[i]; + + if ("DrLocator" == locatorRow.TableDefinition.Name) + { + if (-1 == firstDrLocator) + { + firstDrLocator = i; + } + + if ("CCP_DRIVE" == Convert.ToString(locatorRow[1])) + { + locatorRows.RemoveAt(i); + locatorRows.Insert(firstDrLocator, locatorRow); + break; + } + } + } + } + + foreach (string signature in locators.Keys) + { + var locatorRows = (ArrayList)locators[signature]; + var signatureSearchElements = new ArrayList(); + + foreach (Row locatorRow in locatorRows) + { + var used = true; + var searchElement = this.core.GetIndexedElement(locatorRow); + + if ("Signature" == locatorRow.TableDefinition.Name && 0 < signatureSearchElements.Count) + { + foreach (Wix.IParentElement searchParentElement in signatureSearchElements) + { + if (!usedSearchElements.Contains(searchElement)) + { + searchParentElement.AddChild(searchElement); + usedSearchElements[searchElement] = null; + } + else + { + var fileSearchRef = new Wix.FileSearchRef(); + + fileSearchRef.Id = signature; + + searchParentElement.AddChild(fileSearchRef); + } + } + } + else if ("DrLocator" == locatorRow.TableDefinition.Name && null != locatorRow[1]) + { + var parentSignature = Convert.ToString(locatorRow[1]); + + if ("CCP_DRIVE" == parentSignature) + { + if (appSearches.Contains(signature)) + { + var appSearchPropertyIds = (StringCollection)appSearches[signature]; + + foreach (var propertyId in appSearchPropertyIds) + { + var property = this.EnsureProperty(propertyId); + Wix.ComplianceDrive complianceDrive = null; + + if (ccpSearches.Contains(signature)) + { + property.ComplianceCheck = Wix.YesNoType.yes; + } + + foreach (Wix.ISchemaElement element in property.Children) + { + complianceDrive = element as Wix.ComplianceDrive; + if (null != complianceDrive) + { + break; + } + } + + if (null == complianceDrive) + { + complianceDrive = new Wix.ComplianceDrive(); + property.AddChild(complianceDrive); + } + + if (!usedSearchElements.Contains(searchElement)) + { + complianceDrive.AddChild(searchElement); + usedSearchElements[searchElement] = null; + } + else + { + var directorySearchRef = new Wix.DirectorySearchRef(); + + directorySearchRef.Id = signature; + + if (null != locatorRow[1]) + { + directorySearchRef.Parent = Convert.ToString(locatorRow[1]); + } + + if (null != locatorRow[2]) + { + directorySearchRef.Path = Convert.ToString(locatorRow[2]); + } + + complianceDrive.AddChild(directorySearchRef); + signatureSearchElements.Add(directorySearchRef); + } + } + } + else if (ccpSearches.Contains(signature)) + { + Wix.ComplianceDrive complianceDrive = null; + + foreach (Wix.ISchemaElement element in complianceCheck.Children) + { + complianceDrive = element as Wix.ComplianceDrive; + if (null != complianceDrive) + { + break; + } + } + + if (null == complianceDrive) + { + complianceDrive = new Wix.ComplianceDrive(); + complianceCheck.AddChild(complianceDrive); + } + + if (!usedSearchElements.Contains(searchElement)) + { + complianceDrive.AddChild(searchElement); + usedSearchElements[searchElement] = null; + } + else + { + var directorySearchRef = new Wix.DirectorySearchRef(); + + directorySearchRef.Id = signature; + + if (null != locatorRow[1]) + { + directorySearchRef.Parent = Convert.ToString(locatorRow[1]); + } + + if (null != locatorRow[2]) + { + directorySearchRef.Path = Convert.ToString(locatorRow[2]); + } + + complianceDrive.AddChild(directorySearchRef); + signatureSearchElements.Add(directorySearchRef); + } + } + } + else + { + var usedDrLocator = false; + var parentLocatorRows = (ArrayList)locators[parentSignature]; + + if (null != parentLocatorRows) + { + foreach (Row parentLocatorRow in parentLocatorRows) + { + if ("DrLocator" == parentLocatorRow.TableDefinition.Name) + { + var parentSearchElement = (Wix.IParentElement)this.core.GetIndexedElement(parentLocatorRow); + + if (parentSearchElement.Children.GetEnumerator().MoveNext()) + { + var parentDrLocatorRow = (Row)drLocators[parentSearchElement]; + var directorySeachRef = new Wix.DirectorySearchRef(); + + directorySeachRef.Id = parentSignature; + + if (null != parentDrLocatorRow[1]) + { + directorySeachRef.Parent = Convert.ToString(parentDrLocatorRow[1]); + } + + if (null != parentDrLocatorRow[2]) + { + directorySeachRef.Path = Convert.ToString(parentDrLocatorRow[2]); + } + + parentSearchElement = directorySeachRef; + unusedSearchElements.Add(directorySeachRef); + } + + if (!usedSearchElements.Contains(searchElement)) + { + parentSearchElement.AddChild(searchElement); + usedSearchElements[searchElement] = null; + usedDrLocator = true; + } + else + { + var directorySearchRef = new Wix.DirectorySearchRef(); + + directorySearchRef.Id = signature; + + directorySearchRef.Parent = parentSignature; + + if (null != locatorRow[2]) + { + directorySearchRef.Path = Convert.ToString(locatorRow[2]); + } + + parentSearchElement.AddChild(searchElement); + usedDrLocator = true; + } + } + } + + // keep track of unused DrLocator rows + if (!usedDrLocator) + { + unusedSearchElements.Add(searchElement); + } + } + else + { + // TODO: warn + } + } + } + else if (appSearches.Contains(signature)) + { + var appSearchPropertyIds = (StringCollection)appSearches[signature]; + + foreach (var propertyId in appSearchPropertyIds) + { + var property = this.EnsureProperty(propertyId); + + if (ccpSearches.Contains(signature)) + { + property.ComplianceCheck = Wix.YesNoType.yes; + } + + if (!usedSearchElements.Contains(searchElement)) + { + property.AddChild(searchElement); + usedSearchElements[searchElement] = null; + } + else if ("RegLocator" == locatorRow.TableDefinition.Name) + { + var registrySearchRef = new Wix.RegistrySearchRef(); + + registrySearchRef.Id = signature; + + property.AddChild(registrySearchRef); + signatureSearchElements.Add(registrySearchRef); + } + else + { + // TODO: warn about unavailable Ref element + } + } + } + else if (ccpSearches.Contains(signature)) + { + if (!usedSearchElements.Contains(searchElement)) + { + complianceCheck.AddChild(searchElement); + usedSearchElements[searchElement] = null; + } + else if ("RegLocator" == locatorRow.TableDefinition.Name) + { + var registrySearchRef = new Wix.RegistrySearchRef(); + + registrySearchRef.Id = signature; + + complianceCheck.AddChild(registrySearchRef); + signatureSearchElements.Add(registrySearchRef); + } + else + { + // TODO: warn about unavailable Ref element + } + } + else + { + if ("DrLocator" == locatorRow.TableDefinition.Name) + { + unusedSearchElements.Add(searchElement); + } + else + { + // TODO: warn + used = false; + } + } + + // keep track of the search elements for this signature so that nested searches go in the proper parents + if (used) + { + signatureSearchElements.Add(searchElement); + } + } + } + + foreach (Wix.IParentElement unusedSearchElement in unusedSearchElements) + { + var used = false; + + foreach (Wix.ISchemaElement schemaElement in unusedSearchElement.Children) + { + var directorySearch = schemaElement as Wix.DirectorySearch; + if (null != directorySearch) + { + var appSearchProperties = (StringCollection)appSearches[directorySearch.Id]; + + var unusedSearchSchemaElement = unusedSearchElement as Wix.ISchemaElement; + if (null != appSearchProperties) + { + var property = this.EnsureProperty(appSearchProperties[0]); + + property.AddChild(unusedSearchSchemaElement); + used = true; + break; + } + else if (ccpSearches.Contains(directorySearch.Id)) + { + complianceCheck.AddChild(unusedSearchSchemaElement); + used = true; + break; + } + else + { + // TODO: warn + } + } + } + + if (!used) + { + // TODO: warn + } + } + } + + /// + /// Finalize the sequence tables. + /// + /// The collection of all tables. + /// + /// Creates the sequence elements. Occurs during finalization because its + /// not known if sequences refer to custom actions or dialogs during decompilation. + /// + private void FinalizeSequenceTables(TableIndexedCollection tables) + { + // finalize the normal sequence tables + if (OutputType.Product == this.OutputType && !this.TreatProductAsModule) + { + foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) + { + // if suppressing UI elements, skip UI-related sequence tables + if (this.SuppressUI && ("AdminUISequence" == sequenceTable.ToString() || "InstallUISequence" == sequenceTable.ToString())) + { + continue; + } + + var actionsTable = new Table(this.tableDefinitions["WixAction"]); + var table = tables[sequenceTable.ToString()]; + + if (null != table) + { + var actionRows = new ArrayList(); + var needAbsoluteScheduling = this.SuppressRelativeActionSequencing; + var nonSequencedActionRows = new WixActionRowCollection(); + var suppressedRelativeActionRows = new WixActionRowCollection(); + + // create a sorted array of actions in this table + foreach (var row in table.Rows) + { + var actionRow = (WixActionRow)actionsTable.CreateRow(null); + + actionRow.Action = Convert.ToString(row[0]); + + if (null != row[1]) + { + actionRow.Condition = Convert.ToString(row[1]); + } + + actionRow.Sequence = Convert.ToInt32(row[2]); + + actionRow.SequenceTable = sequenceTable; + + actionRows.Add(actionRow); + } + actionRows.Sort(); + + for (var i = 0; i < actionRows.Count && !needAbsoluteScheduling; i++) + { + var actionRow = (WixActionRow)actionRows[i]; + this.StandardActions.TryGetValue(actionRow.GetPrimaryKey(), out var standardActionRow); + + // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions + if ("AppSearch" == actionRow.Action || null == standardActionRow || actionRow.Condition != standardActionRow.Condition) + { + WixActionRow previousActionRow = null; + WixActionRow nextActionRow = null; + + // find the previous action row if there is one + if (0 <= i - 1) + { + previousActionRow = (WixActionRow)actionRows[i - 1]; + } + + // find the next action row if there is one + if (actionRows.Count > i + 1) + { + nextActionRow = (WixActionRow)actionRows[i + 1]; + } + + // the logic for setting the before or after attribute for an action: + // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced. + // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it. + // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it. + // 4. If this action is not standard and the previous action is 1 sequence number lower and does not occur before this action, this action occurs after it. + // 5. If this action is not standard and the previous action does not have the same sequence number and the next action is 1 sequence number higher, this action occurs before it. + // 6. If this action is AppSearch and has all standard information, ignore it. + // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information. + // 8. Everything must be absolutely sequenced. + if ((null != previousActionRow && actionRow.Sequence == previousActionRow.Sequence) || (null != nextActionRow && actionRow.Sequence == nextActionRow.Sequence)) + { + needAbsoluteScheduling = true; + } + else if (null != nextActionRow && this.StandardActions.ContainsKey(nextActionRow.GetPrimaryKey()) && actionRow.Sequence + 1 == nextActionRow.Sequence) + { + actionRow.Before = nextActionRow.Action; + } + else if (null != previousActionRow && this.StandardActions.ContainsKey(previousActionRow.GetPrimaryKey()) && actionRow.Sequence - 1 == previousActionRow.Sequence) + { + actionRow.After = previousActionRow.Action; + } + else if (null == standardActionRow && null != previousActionRow && actionRow.Sequence - 1 == previousActionRow.Sequence && previousActionRow.Before != actionRow.Action) + { + actionRow.After = previousActionRow.Action; + } + else if (null == standardActionRow && null != previousActionRow && actionRow.Sequence != previousActionRow.Sequence && null != nextActionRow && actionRow.Sequence + 1 == nextActionRow.Sequence) + { + actionRow.Before = nextActionRow.Action; + } + else if ("AppSearch" == actionRow.Action && null != standardActionRow && actionRow.Sequence == standardActionRow.Sequence && actionRow.Condition == standardActionRow.Condition) + { + // ignore an AppSearch row which has the WiX standard sequence and a standard condition + } + else if (null != standardActionRow && actionRow.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers + { + nonSequencedActionRows.Add(actionRow); + } + else if (0 < actionRow.Sequence) + { + needAbsoluteScheduling = true; + } + } + else + { + suppressedRelativeActionRows.Add(actionRow); + } + } + + // create the actions now that we know if they must be absolutely or relatively scheduled + foreach (WixActionRow actionRow in actionRows) + { + if (needAbsoluteScheduling) + { + // remove any before/after information to ensure this is absolutely sequenced + actionRow.Before = null; + actionRow.After = null; + } + else if (nonSequencedActionRows.Contains(actionRow.SequenceTable, actionRow.Action)) + { + // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after) + actionRow.Sequence = 0; + } + else if (suppressedRelativeActionRows.Contains(actionRow.SequenceTable, actionRow.Action)) + { + // skip the suppressed relatively scheduled action rows + continue; + } + + // create the action element + this.CreateActionElement(actionRow); + } + } + } + } + else if (OutputType.Module == this.OutputType || this.TreatProductAsModule) // finalize the Module sequence tables + { + foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) + { + // if suppressing UI elements, skip UI-related sequence tables + if (this.SuppressUI && ("AdminUISequence" == sequenceTable.ToString() || "InstallUISequence" == sequenceTable.ToString())) + { + continue; + } + + var actionsTable = new Table(this.tableDefinitions["WixAction"]); + var table = tables[String.Concat("Module", sequenceTable.ToString())]; + + if (null != table) + { + foreach (var row in table.Rows) + { + var actionRow = (WixActionRow)actionsTable.CreateRow(null); + + actionRow.Action = Convert.ToString(row[0]); + + if (null != row[1]) + { + actionRow.Sequence = Convert.ToInt32(row[1]); + } + + if (null != row[2] && null != row[3]) + { + switch (Convert.ToInt32(row[3])) + { + case 0: + actionRow.Before = Convert.ToString(row[2]); + break; + case 1: + actionRow.After = Convert.ToString(row[2]); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3])); + break; + } + } + + if (null != row[4]) + { + actionRow.Condition = Convert.ToString(row[4]); + } + + actionRow.SequenceTable = sequenceTable; + + // create action elements for non-standard actions + if (!this.StandardActions.ContainsKey(actionRow.GetPrimaryKey()) || null != actionRow.After || null != actionRow.Before) + { + this.CreateActionElement(actionRow); + } + } + } + } + } + } + + /// + /// Finalize the Upgrade table. + /// + /// The collection of all tables. + /// + /// Decompile the rows from the Upgrade and LaunchCondition tables + /// created by the MajorUpgrade element. + /// + private void FinalizeUpgradeTable(TableIndexedCollection tables) + { + var launchConditionTable = tables["LaunchCondition"]; + var upgradeTable = tables["Upgrade"]; + string downgradeErrorMessage = null; + string disallowUpgradeErrorMessage = null; + var majorUpgrade = new Wix.MajorUpgrade(); + + // find the DowngradePreventedCondition launch condition message + if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count) + { + foreach (var launchRow in launchConditionTable.Rows) + { + if (Common.DowngradePreventedCondition == Convert.ToString(launchRow[0])) + { + downgradeErrorMessage = Convert.ToString(launchRow[1]); + } + else if (Common.UpgradePreventedCondition == Convert.ToString(launchRow[0])) + { + disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]); + } + } + } + + if (null != upgradeTable && 0 < upgradeTable.Rows.Count) + { + var hasMajorUpgrade = false; + + foreach (var row in upgradeTable.Rows) + { + var upgradeRow = (UpgradeRow)row; + + if (Common.UpgradeDetectedProperty == upgradeRow.ActionProperty) + { + hasMajorUpgrade = true; + var attr = upgradeRow.Attributes; + var removeFeatures = upgradeRow.Remove; + + if (MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive == (attr & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive)) + { + majorUpgrade.AllowSameVersionUpgrades = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbUpgradeAttributesMigrateFeatures != (attr & MsiInterop.MsidbUpgradeAttributesMigrateFeatures)) + { + majorUpgrade.MigrateFeatures = Wix.YesNoType.no; + } + + if (MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure)) + { + majorUpgrade.IgnoreRemoveFailure = Wix.YesNoType.yes; + } + + if (!String.IsNullOrEmpty(removeFeatures)) + { + majorUpgrade.RemoveFeatures = removeFeatures; + } + } + else if (Common.DowngradeDetectedProperty == upgradeRow.ActionProperty) + { + hasMajorUpgrade = true; + majorUpgrade.DowngradeErrorMessage = downgradeErrorMessage; + } + } + + if (hasMajorUpgrade) + { + if (String.IsNullOrEmpty(downgradeErrorMessage)) + { + majorUpgrade.AllowDowngrades = Wix.YesNoType.yes; + } + + if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage)) + { + majorUpgrade.Disallow = Wix.YesNoType.yes; + majorUpgrade.DisallowUpgradeErrorMessage = disallowUpgradeErrorMessage; + } + + var scheduledType = DetermineMajorUpgradeScheduling(tables); + if (Wix.MajorUpgrade.ScheduleType.afterInstallValidate != scheduledType) + { + majorUpgrade.Schedule = scheduledType; + } + + this.core.RootElement.AddChild(majorUpgrade); + } + } + } + + /// + /// Finalize the Verb table. + /// + /// The collection of all tables. + /// + /// The Extension table is a foreign table for the Verb table, but the + /// foreign key is only part of the primary key of the Extension table, + /// so it needs special logic to be nested properly. + /// + private void FinalizeVerbTable(TableIndexedCollection tables) + { + var extensionTable = tables["Extension"]; + var verbTable = tables["Verb"]; + + var extensionElements = new Hashtable(); + + if (null != extensionTable) + { + foreach (var row in extensionTable.Rows) + { + var extension = (Wix.Extension)this.core.GetIndexedElement(row); + + if (!extensionElements.Contains(row[0])) + { + extensionElements.Add(row[0], new ArrayList()); + } + + ((ArrayList)extensionElements[row[0]]).Add(extension); + } + } + + if (null != verbTable) + { + foreach (var row in verbTable.Rows) + { + var verb = (Wix.Verb)this.core.GetIndexedElement(row); + + var extensionsArray = (ArrayList)extensionElements[row[0]]; + if (null != extensionsArray) + { + foreach (Wix.Extension extension in extensionsArray) + { + extension.AddChild(verb); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", Convert.ToString(row[0]), "Extension")); + } + } + } + } + + /// + /// Get the path to a file in the source image. + /// + /// The file. + /// The path to the file in the source image. + private string GetSourcePath(Wix.File file) + { + var sourcePath = new StringBuilder(); + + var component = (Wix.Component)file.ParentElement; + + for (var directory = (Wix.Directory)component.ParentElement; null != directory; directory = directory.ParentElement as Wix.Directory) + { + string name; + + if (!this.shortNames && null != directory.SourceName) + { + name = directory.SourceName; + } + else if (null != directory.ShortSourceName) + { + name = directory.ShortSourceName; + } + else if (!this.shortNames || null == directory.ShortName) + { + name = directory.Name; + } + else + { + name = directory.ShortName; + } + + if (0 == sourcePath.Length) + { + sourcePath.Append(name); + } + else + { + sourcePath.Insert(0, Path.DirectorySeparatorChar); + sourcePath.Insert(0, name); + } + } + + return sourcePath.ToString(); + } + + /// + /// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames). + /// + /// The name of the table to resolve. + /// The unsorted table names. + /// The sorted table names. + private void ResolveTableDependencies(string tableName, SortedList unsortedTableNames, StringCollection sortedTableNames) + { + unsortedTableNames.Remove(tableName); + + foreach (var columnDefinition in this.tableDefinitions[tableName].Columns) + { + // no dependency to resolve because this column doesn't reference another table + if (null == columnDefinition.KeyTable) + { + continue; + } + + foreach (var keyTable in columnDefinition.KeyTable.Split(';')) + { + if (tableName == keyTable) + { + continue; // self-referencing dependency + } + else if (sortedTableNames.Contains(keyTable)) + { + continue; // dependent table has already been sorted + } + else if (!this.tableDefinitions.Contains(keyTable)) + { + this.Messaging.Write(ErrorMessages.MissingTableDefinition(keyTable)); + } + else if (unsortedTableNames.Contains(keyTable)) + { + this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames); + } + else + { + // found a circular dependency, so ignore it (this assumes that the tables will + // use a finalize method to nest their elements since the ordering will not be + // deterministic + } + } + } + + sortedTableNames.Add(tableName); + } + + /// + /// Get the names of the tables to process in the order they should be processed, according to their dependencies. + /// + /// A StringCollection containing the ordered table names. + private StringCollection GetSortedTableNames() + { + var sortedTableNames = new StringCollection(); + var unsortedTableNames = new SortedList(); + + // index the table names + foreach (var tableDefinition in this.tableDefinitions) + { + unsortedTableNames.Add(tableDefinition.Name, tableDefinition.Name); + } + + // resolve the dependencies for each table + while (0 < unsortedTableNames.Count) + { + this.ResolveTableDependencies(Convert.ToString(unsortedTableNames.GetByIndex(0)), unsortedTableNames, sortedTableNames); + } + + return sortedTableNames; + } + + /// + /// Initialize decompilation. + /// + /// The collection of all tables. + private void InitializeDecompile(TableIndexedCollection tables, int codepage) + { + // reset all the state information + this.compressed = false; + this.patchTargetFiles.Clear(); + this.sequenceElements.Clear(); + this.shortNames = false; + + // set the codepage if its not neutral (0) + if (0 != codepage) + { + switch (this.OutputType) + { + case OutputType.Module: + ((Wix.Module)this.core.RootElement).Codepage = codepage.ToString(CultureInfo.InvariantCulture); + break; + case OutputType.PatchCreation: + ((Wix.PatchCreation)this.core.RootElement).Codepage = codepage.ToString(CultureInfo.InvariantCulture); + break; + case OutputType.Product: + ((Wix.Product)this.core.RootElement).Codepage = codepage.ToString(CultureInfo.InvariantCulture); + break; + } + } + + // index the rows from the extension libraries + var indexedExtensionTables = new Dictionary>(); +#if TODO_DECOMPILER_EXTENSIONS + foreach (IDecompilerExtension extension in this.extensions) + { + // Get the optional library from the extension with the rows to be removed. + Library library = extension.GetLibraryToRemove(this.tableDefinitions); + if (null != library) + { + foreach (var section in library.Sections) + { + foreach (Table table in section.Tables) + { + foreach (Row row in table.Rows) + { + string primaryKey; + string tableName; + + // the Actions table needs to be handled specially + if ("WixAction" == table.Name) + { + primaryKey = Convert.ToString(row[1]); + + if (OutputType.Module == this.outputType) + { + tableName = String.Concat("Module", Convert.ToString(row[0])); + } + else + { + tableName = Convert.ToString(row[0]); + } + } + else + { + primaryKey = row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter); + tableName = table.Name; + } + + if (null != primaryKey) + { + HashSet indexedExtensionRows; + if (!indexedExtensionTables.TryGetValue(tableName, out indexedExtensionRows)) + { + indexedExtensionRows = new HashSet(); + indexedExtensionTables.Add(tableName, indexedExtensionRows); + } + + indexedExtensionRows.Add(primaryKey); + } + } + } + } + } + } +#endif + + // remove the rows from the extension libraries (to allow full round-tripping) + foreach (var kvp in indexedExtensionTables) + { + var tableName = kvp.Key; + var indexedExtensionRows = kvp.Value; + + var table = tables[tableName]; + if (null != table) + { + var originalRows = new RowDictionary(table); + + // remove the original rows so that they can be added back if they should remain + table.Rows.Clear(); + + foreach (var row in originalRows.Values) + { + if (!indexedExtensionRows.Contains(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter))) + { + table.Rows.Add(row); + } + } + } + } + } + + /// + /// Decompile the tables. + /// + /// The output being decompiled. + private void DecompileTables(Output output) + { + var sortedTableNames = this.GetSortedTableNames(); + + foreach (var tableName in sortedTableNames) + { + var table = output.Tables[tableName]; + + // table does not exist in this database or should not be decompiled + if (null == table || !this.DecompilableTable(output, tableName)) + { + continue; + } + + this.Messaging.Write(VerboseMessages.DecompilingTable(table.Name)); + + // empty tables may be kept with EnsureTable if the user set the proper option + if (0 == table.Rows.Count && this.SuppressDroppingEmptyTables) + { + var ensureTable = new Wix.EnsureTable(); + ensureTable.Id = table.Name; + this.core.RootElement.AddChild(ensureTable); + } + + switch (table.Name) + { + case "_SummaryInformation": + this.Decompile_SummaryInformationTable(table); + break; + case "AdminExecuteSequence": + case "AdminUISequence": + case "AdvtExecuteSequence": + case "InstallExecuteSequence": + case "InstallUISequence": + case "ModuleAdminExecuteSequence": + case "ModuleAdminUISequence": + case "ModuleAdvtExecuteSequence": + case "ModuleInstallExecuteSequence": + case "ModuleInstallUISequence": + // handled in FinalizeSequenceTables + break; + case "ActionText": + this.DecompileActionTextTable(table); + break; + case "AdvtUISequence": + this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); + break; + case "AppId": + this.DecompileAppIdTable(table); + break; + case "AppSearch": + // handled in FinalizeSearchTables + break; + case "BBControl": + this.DecompileBBControlTable(table); + break; + case "Billboard": + this.DecompileBillboardTable(table); + break; + case "Binary": + this.DecompileBinaryTable(table); + break; + case "BindImage": + this.DecompileBindImageTable(table); + break; + case "CCPSearch": + // handled in FinalizeSearchTables + break; + case "CheckBox": + // handled in FinalizeCheckBoxTable + break; + case "Class": + this.DecompileClassTable(table); + break; + case "ComboBox": + this.DecompileComboBoxTable(table); + break; + case "Control": + this.DecompileControlTable(table); + break; + case "ControlCondition": + this.DecompileControlConditionTable(table); + break; + case "ControlEvent": + this.DecompileControlEventTable(table); + break; + case "CreateFolder": + this.DecompileCreateFolderTable(table); + break; + case "CustomAction": + this.DecompileCustomActionTable(table); + break; + case "CompLocator": + this.DecompileCompLocatorTable(table); + break; + case "Complus": + this.DecompileComplusTable(table); + break; + case "Component": + this.DecompileComponentTable(table); + break; + case "Condition": + this.DecompileConditionTable(table); + break; + case "Dialog": + this.DecompileDialogTable(table); + break; + case "Directory": + this.DecompileDirectoryTable(table); + break; + case "DrLocator": + this.DecompileDrLocatorTable(table); + break; + case "DuplicateFile": + this.DecompileDuplicateFileTable(table); + break; + case "Environment": + this.DecompileEnvironmentTable(table); + break; + case "Error": + this.DecompileErrorTable(table); + break; + case "EventMapping": + this.DecompileEventMappingTable(table); + break; + case "Extension": + this.DecompileExtensionTable(table); + break; + case "ExternalFiles": + this.DecompileExternalFilesTable(table); + break; + case "FamilyFileRanges": + // handled in FinalizeFamilyFileRangesTable + break; + case "Feature": + this.DecompileFeatureTable(table); + break; + case "FeatureComponents": + this.DecompileFeatureComponentsTable(table); + break; + case "File": + this.DecompileFileTable(table); + break; + case "FileSFPCatalog": + this.DecompileFileSFPCatalogTable(table); + break; + case "Font": + this.DecompileFontTable(table); + break; + case "Icon": + this.DecompileIconTable(table); + break; + case "ImageFamilies": + this.DecompileImageFamiliesTable(table); + break; + case "IniFile": + this.DecompileIniFileTable(table); + break; + case "IniLocator": + this.DecompileIniLocatorTable(table); + break; + case "IsolatedComponent": + this.DecompileIsolatedComponentTable(table); + break; + case "LaunchCondition": + this.DecompileLaunchConditionTable(table); + break; + case "ListBox": + this.DecompileListBoxTable(table); + break; + case "ListView": + this.DecompileListViewTable(table); + break; + case "LockPermissions": + this.DecompileLockPermissionsTable(table); + break; + case "Media": + this.DecompileMediaTable(table); + break; + case "MIME": + this.DecompileMIMETable(table); + break; + case "ModuleAdvtUISequence": + this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); + break; + case "ModuleComponents": + // handled by DecompileComponentTable (since the ModuleComponents table + // rows are created by nesting components under the Module element) + break; + case "ModuleConfiguration": + this.DecompileModuleConfigurationTable(table); + break; + case "ModuleDependency": + this.DecompileModuleDependencyTable(table); + break; + case "ModuleExclusion": + this.DecompileModuleExclusionTable(table); + break; + case "ModuleIgnoreTable": + this.DecompileModuleIgnoreTableTable(table); + break; + case "ModuleSignature": + this.DecompileModuleSignatureTable(table); + break; + case "ModuleSubstitution": + this.DecompileModuleSubstitutionTable(table); + break; + case "MoveFile": + this.DecompileMoveFileTable(table); + break; + case "MsiAssembly": + // handled in FinalizeFileTable + break; + case "MsiDigitalCertificate": + this.DecompileMsiDigitalCertificateTable(table); + break; + case "MsiDigitalSignature": + this.DecompileMsiDigitalSignatureTable(table); + break; + case "MsiEmbeddedChainer": + this.DecompileMsiEmbeddedChainerTable(table); + break; + case "MsiEmbeddedUI": + this.DecompileMsiEmbeddedUITable(table); + break; + case "MsiLockPermissionsEx": + this.DecompileMsiLockPermissionsExTable(table); + break; + case "MsiPackageCertificate": + this.DecompileMsiPackageCertificateTable(table); + break; + case "MsiPatchCertificate": + this.DecompileMsiPatchCertificateTable(table); + break; + case "MsiShortcutProperty": + this.DecompileMsiShortcutPropertyTable(table); + break; + case "ODBCAttribute": + this.DecompileODBCAttributeTable(table); + break; + case "ODBCDataSource": + this.DecompileODBCDataSourceTable(table); + break; + case "ODBCDriver": + this.DecompileODBCDriverTable(table); + break; + case "ODBCSourceAttribute": + this.DecompileODBCSourceAttributeTable(table); + break; + case "ODBCTranslator": + this.DecompileODBCTranslatorTable(table); + break; + case "PatchMetadata": + this.DecompilePatchMetadataTable(table); + break; + case "PatchSequence": + this.DecompilePatchSequenceTable(table); + break; + case "ProgId": + this.DecompileProgIdTable(table); + break; + case "Properties": + this.DecompilePropertiesTable(table); + break; + case "Property": + this.DecompilePropertyTable(table); + break; + case "PublishComponent": + this.DecompilePublishComponentTable(table); + break; + case "RadioButton": + this.DecompileRadioButtonTable(table); + break; + case "Registry": + this.DecompileRegistryTable(table); + break; + case "RegLocator": + this.DecompileRegLocatorTable(table); + break; + case "RemoveFile": + this.DecompileRemoveFileTable(table); + break; + case "RemoveIniFile": + this.DecompileRemoveIniFileTable(table); + break; + case "RemoveRegistry": + this.DecompileRemoveRegistryTable(table); + break; + case "ReserveCost": + this.DecompileReserveCostTable(table); + break; + case "SelfReg": + this.DecompileSelfRegTable(table); + break; + case "ServiceControl": + this.DecompileServiceControlTable(table); + break; + case "ServiceInstall": + this.DecompileServiceInstallTable(table); + break; + case "SFPCatalog": + this.DecompileSFPCatalogTable(table); + break; + case "Shortcut": + this.DecompileShortcutTable(table); + break; + case "Signature": + this.DecompileSignatureTable(table); + break; + case "TargetFiles_OptionalData": + this.DecompileTargetFiles_OptionalDataTable(table); + break; + case "TargetImages": + this.DecompileTargetImagesTable(table); + break; + case "TextStyle": + this.DecompileTextStyleTable(table); + break; + case "TypeLib": + this.DecompileTypeLibTable(table); + break; + case "Upgrade": + this.DecompileUpgradeTable(table); + break; + case "UpgradedFiles_OptionalData": + this.DecompileUpgradedFiles_OptionalDataTable(table); + break; + case "UpgradedFilesToIgnore": + this.DecompileUpgradedFilesToIgnoreTable(table); + break; + case "UpgradedImages": + this.DecompileUpgradedImagesTable(table); + break; + case "UIText": + this.DecompileUITextTable(table); + break; + case "Verb": + this.DecompileVerbTable(table); + break; + + default: +#if TODO_DECOMPILER_EXTENSIONS + if (this.ExtensionsByTableName.TryGetValue(table.Name, out var extension) + { + extension.DecompileTable(table); + } + else +#endif + if (!this.SuppressCustomTables) + { + this.DecompileCustomTable(table); + } + break; + } + } + } + + /// + /// Determine if a particular table should be decompiled with the current settings. + /// + /// The output being decompiled. + /// The name of a table. + /// true if the table should be decompiled; false otherwise. + private bool DecompilableTable(Output output, string tableName) + { + switch (tableName) + { + case "ActionText": + case "BBControl": + case "Billboard": + case "CheckBox": + case "Control": + case "ControlCondition": + case "ControlEvent": + case "Dialog": + case "Error": + case "EventMapping": + case "RadioButton": + case "TextStyle": + case "UIText": + return !this.SuppressUI; + case "ModuleAdminExecuteSequence": + case "ModuleAdminUISequence": + case "ModuleAdvtExecuteSequence": + case "ModuleAdvtUISequence": + case "ModuleComponents": + case "ModuleConfiguration": + case "ModuleDependency": + case "ModuleIgnoreTable": + case "ModuleInstallExecuteSequence": + case "ModuleInstallUISequence": + case "ModuleExclusion": + case "ModuleSignature": + case "ModuleSubstitution": + if (OutputType.Module != output.Type) + { + this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + case "ExternalFiles": + case "FamilyFileRanges": + case "ImageFamilies": + case "PatchMetadata": + case "PatchSequence": + case "Properties": + case "TargetFiles_OptionalData": + case "TargetImages": + case "UpgradedFiles_OptionalData": + case "UpgradedFilesToIgnore": + case "UpgradedImages": + if (OutputType.PatchCreation != output.Type) + { + this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + case "MsiPatchHeaders": + case "MsiPatchMetadata": + case "MsiPatchOldAssemblyName": + case "MsiPatchOldAssemblyFile": + case "MsiPatchSequence": + case "Patch": + case "PatchPackage": + this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName)); + return false; + case "_SummaryInformation": + return true; + case "_Validation": + case "MsiAssemblyName": + case "MsiFileHash": + return false; + default: // all other tables are allowed in any output except for a patch creation package + if (OutputType.PatchCreation == output.Type) + { + this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + } + } + + /// + /// Decompile the _SummaryInformation table. + /// + /// The table to decompile. + private void Decompile_SummaryInformationTable(Table table) + { + if (OutputType.Module == this.OutputType || OutputType.Product == this.OutputType) + { + var package = new Wix.Package(); + + foreach (var row in table.Rows) + { + var value = Convert.ToString(row[1]); + + if (null != value && 0 < value.Length) + { + switch (Convert.ToInt32(row[0])) + { + case 1: + if ("1252" != value) + { + package.SummaryCodepage = value; + } + break; + case 3: + package.Description = value; + break; + case 4: + package.Manufacturer = value; + break; + case 5: + if ("Installer" != value) + { + package.Keywords = value; + } + break; + case 6: + if (!value.StartsWith("This installer database contains the logic and data required to install ")) + { + package.Comments = value; + } + break; + case 7: + var template = value.Split(';'); + if (0 < template.Length && 0 < template[template.Length - 1].Length) + { + package.Languages = template[template.Length - 1]; + } + + if (1 < template.Length && null != template[0] && 0 < template[0].Length) + { + switch (template[0]) + { + case "Intel": + package.Platform = WixToolset.Data.Serialize.Package.PlatformType.x86; + break; + case "Intel64": + package.Platform = WixToolset.Data.Serialize.Package.PlatformType.ia64; + break; + case "x64": + package.Platform = WixToolset.Data.Serialize.Package.PlatformType.x64; + break; + } + } + break; + case 9: + if (OutputType.Module == this.OutputType) + { + this.modularizationGuid = value; + package.Id = value; + } + break; + case 14: + package.InstallerVersion = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); + break; + case 15: + var wordCount = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); + if (0x1 == (wordCount & 0x1)) + { + this.shortNames = true; + package.ShortNames = Wix.YesNoType.yes; + } + + if (0x2 == (wordCount & 0x2)) + { + this.compressed = true; + + if (OutputType.Product == this.OutputType) + { + package.Compressed = Wix.YesNoType.yes; + } + } + + if (0x4 == (wordCount & 0x4)) + { + package.AdminImage = Wix.YesNoType.yes; + } + + if (0x8 == (wordCount & 0x8)) + { + package.InstallPrivileges = Wix.Package.InstallPrivilegesType.limited; + } + + break; + case 19: + var security = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); + switch (security) + { + case 0: + package.ReadOnly = Wix.YesNoDefaultType.no; + break; + case 4: + package.ReadOnly = Wix.YesNoDefaultType.yes; + break; + } + break; + } + } + } + + this.core.RootElement.AddChild(package); + } + else + { + var patchInformation = new Wix.PatchInformation(); + + foreach (var row in table.Rows) + { + var propertyId = Convert.ToInt32(row[0]); + var value = Convert.ToString(row[1]); + + if (null != row[1] && 0 < value.Length) + { + switch (propertyId) + { + case 1: + if ("1252" != value) + { + patchInformation.SummaryCodepage = value; + } + break; + case 3: + patchInformation.Description = value; + break; + case 4: + patchInformation.Manufacturer = value; + break; + case 5: + if ("Installer,Patching,PCP,Database" != value) + { + patchInformation.Keywords = value; + } + break; + case 6: + patchInformation.Comments = value; + break; + case 7: + var template = value.Split(';'); + if (0 < template.Length && 0 < template[template.Length - 1].Length) + { + patchInformation.Languages = template[template.Length - 1]; + } + + if (1 < template.Length && null != template[0] && 0 < template[0].Length) + { + patchInformation.Platforms = template[0]; + } + break; + case 15: + var wordCount = Convert.ToInt32(value, CultureInfo.InvariantCulture); + if (0x1 == (wordCount & 0x1)) + { + patchInformation.ShortNames = Wix.YesNoType.yes; + } + + if (0x2 == (wordCount & 0x2)) + { + patchInformation.Compressed = Wix.YesNoType.yes; + } + + if (0x4 == (wordCount & 0x4)) + { + patchInformation.AdminImage = Wix.YesNoType.yes; + } + break; + case 19: + var security = Convert.ToInt32(value, CultureInfo.InvariantCulture); + switch (security) + { + case 0: + patchInformation.ReadOnly = Wix.YesNoDefaultType.no; + break; + case 4: + patchInformation.ReadOnly = Wix.YesNoDefaultType.yes; + break; + } + break; + } + } + } + + this.core.RootElement.AddChild(patchInformation); + } + } + + /// + /// Decompile the ActionText table. + /// + /// The table to decompile. + private void DecompileActionTextTable(Table table) + { + foreach (var row in table.Rows) + { + var progressText = new Wix.ProgressText(); + + progressText.Action = Convert.ToString(row[0]); + + if (null != row[1]) + { + progressText.Content = Convert.ToString(row[1]); + } + + if (null != row[2]) + { + progressText.Template = Convert.ToString(row[2]); + } + + this.core.UIElement.AddChild(progressText); + } + } + + /// + /// Decompile the AppId table. + /// + /// The table to decompile. + private void DecompileAppIdTable(Table table) + { + foreach (var row in table.Rows) + { + var appId = new Wix.AppId(); + + appId.Advertise = Wix.YesNoType.yes; + + appId.Id = Convert.ToString(row[0]); + + if (null != row[1]) + { + appId.RemoteServerName = Convert.ToString(row[1]); + } + + if (null != row[2]) + { + appId.LocalService = Convert.ToString(row[2]); + } + + if (null != row[3]) + { + appId.ServiceParameters = Convert.ToString(row[3]); + } + + if (null != row[4]) + { + appId.DllSurrogate = Convert.ToString(row[4]); + } + + if (null != row[5] && Int32.Equals(row[5], 1)) + { + appId.ActivateAtStorage = Wix.YesNoType.yes; + } + + if (null != row[6] && Int32.Equals(row[6], 1)) + { + appId.RunAsInteractiveUser = Wix.YesNoType.yes; + } + + this.core.RootElement.AddChild(appId); + this.core.IndexElement(row, appId); + } + } + + /// + /// Decompile the BBControl table. + /// + /// The table to decompile. + private void DecompileBBControlTable(Table table) + { + foreach (BBControlRow bbControlRow in table.Rows) + { + var control = new Wix.Control(); + + control.Id = bbControlRow.BBControl; + + control.Type = bbControlRow.Type; + + control.X = bbControlRow.X; + + control.Y = bbControlRow.Y; + + control.Width = bbControlRow.Width; + + control.Height = bbControlRow.Height; + + if (null != bbControlRow[7]) + { + SetControlAttributes(bbControlRow.Attributes, control); + } + + if (null != bbControlRow.Text) + { + control.Text = bbControlRow.Text; + } + + var billboard = (Wix.Billboard)this.core.GetIndexedElement("Billboard", bbControlRow.Billboard); + if (null != billboard) + { + billboard.AddChild(control); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard")); + } + } + } + + /// + /// Decompile the Billboard table. + /// + /// The table to decompile. + private void DecompileBillboardTable(Table table) + { + var billboardActions = new Hashtable(); + var billboards = new SortedList(); + + foreach (var row in table.Rows) + { + var billboard = new Wix.Billboard(); + + billboard.Id = Convert.ToString(row[0]); + + billboard.Feature = Convert.ToString(row[1]); + + this.core.IndexElement(row, billboard); + billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row); + } + + foreach (Row row in billboards.Values) + { + var billboard = (Wix.Billboard)this.core.GetIndexedElement(row); + var billboardAction = (Wix.BillboardAction)billboardActions[row[2]]; + + if (null == billboardAction) + { + billboardAction = new Wix.BillboardAction(); + + billboardAction.Id = Convert.ToString(row[2]); + + this.core.UIElement.AddChild(billboardAction); + billboardActions.Add(row[2], billboardAction); + } + + billboardAction.AddChild(billboard); + } + } + + /// + /// Decompile the Binary table. + /// + /// The table to decompile. + private void DecompileBinaryTable(Table table) + { + foreach (var row in table.Rows) + { + var binary = new Wix.Binary(); + + binary.Id = Convert.ToString(row[0]); + + binary.SourceFile = Convert.ToString(row[1]); + + this.core.RootElement.AddChild(binary); + } + } + + /// + /// Decompile the BindImage table. + /// + /// The table to decompile. + private void DecompileBindImageTable(Table table) + { + foreach (var row in table.Rows) + { + var file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); + + if (null != file) + { + file.BindPath = Convert.ToString(row[1]); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); + } + } + } + + /// + /// Decompile the Class table. + /// + /// The table to decompile. + private void DecompileClassTable(Table table) + { + foreach (var row in table.Rows) + { + var wixClass = new Wix.Class(); + + wixClass.Advertise = Wix.YesNoType.yes; + + wixClass.Id = Convert.ToString(row[0]); + + switch (Convert.ToString(row[1])) + { + case "LocalServer": + wixClass.Context = Wix.Class.ContextType.LocalServer; + break; + case "LocalServer32": + wixClass.Context = Wix.Class.ContextType.LocalServer32; + break; + case "InprocServer": + wixClass.Context = Wix.Class.ContextType.InprocServer; + break; + case "InprocServer32": + wixClass.Context = Wix.Class.ContextType.InprocServer32; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + // ProgId children are handled in FinalizeProgIdTable + + if (null != row[4]) + { + wixClass.Description = Convert.ToString(row[4]); + } + + if (null != row[5]) + { + wixClass.AppId = Convert.ToString(row[5]); + } + + if (null != row[6]) + { + var fileTypeMaskStrings = (Convert.ToString(row[6])).Split(';'); + + try + { + foreach (var fileTypeMaskString in fileTypeMaskStrings) + { + var fileTypeMaskParts = fileTypeMaskString.Split(','); + + if (4 == fileTypeMaskParts.Length) + { + var fileTypeMask = new Wix.FileTypeMask(); + + fileTypeMask.Offset = Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture); + + fileTypeMask.Mask = fileTypeMaskParts[2]; + + fileTypeMask.Value = fileTypeMaskParts[3]; + + wixClass.AddChild(fileTypeMask); + } + else + { + // TODO: warn + } + } + } + catch (FormatException) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + } + catch (OverflowException) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + } + } + + if (null != row[7]) + { + wixClass.Icon = Convert.ToString(row[7]); + } + + if (null != row[8]) + { + wixClass.IconIndex = Convert.ToInt32(row[8]); + } + + if (null != row[9]) + { + wixClass.Handler = Convert.ToString(row[9]); + } + + if (null != row[10]) + { + wixClass.Argument = Convert.ToString(row[10]); + } + + if (null != row[12]) + { + if (1 == Convert.ToInt32(row[12])) + { + wixClass.RelativePath = Wix.YesNoType.yes; + } + else + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12])); + } + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2])); + if (null != component) + { + component.AddChild(wixClass); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[2]), "Component")); + } + + this.core.IndexElement(row, wixClass); + } + } + + /// + /// Decompile the ComboBox table. + /// + /// The table to decompile. + private void DecompileComboBoxTable(Table table) + { + Wix.ComboBox comboBox = null; + var comboBoxRows = new SortedList(); + + // sort the combo boxes by their property and order + foreach (var row in table.Rows) + { + comboBoxRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); + } + + foreach (Row row in comboBoxRows.Values) + { + if (null == comboBox || Convert.ToString(row[0]) != comboBox.Property) + { + comboBox = new Wix.ComboBox(); + + comboBox.Property = Convert.ToString(row[0]); + + this.core.UIElement.AddChild(comboBox); + } + + var listItem = new Wix.ListItem(); + + listItem.Value = Convert.ToString(row[2]); + + if (null != row[3]) + { + listItem.Text = Convert.ToString(row[3]); + } + + comboBox.AddChild(listItem); + } + } + + /// + /// Decompile the Control table. + /// + /// The table to decompile. + private void DecompileControlTable(Table table) + { + foreach (ControlRow controlRow in table.Rows) + { + var control = new Wix.Control(); + + control.Id = controlRow.Control; + + control.Type = controlRow.Type; + + control.X = controlRow.X; + + control.Y = controlRow.Y; + + control.Width = controlRow.Width; + + control.Height = controlRow.Height; + + if (null != controlRow[7]) + { + string[] specialAttributes; + + // sets various common attributes like Disabled, Indirect, Integer, ... + SetControlAttributes(controlRow.Attributes, control); + + switch (control.Type) + { + case "Bitmap": + specialAttributes = MsiInterop.BitmapControlAttributes; + break; + case "CheckBox": + specialAttributes = MsiInterop.CheckboxControlAttributes; + break; + case "ComboBox": + specialAttributes = MsiInterop.ComboboxControlAttributes; + break; + case "DirectoryCombo": + specialAttributes = MsiInterop.VolumeControlAttributes; + break; + case "Edit": + specialAttributes = MsiInterop.EditControlAttributes; + break; + case "Icon": + specialAttributes = MsiInterop.IconControlAttributes; + break; + case "ListBox": + specialAttributes = MsiInterop.ListboxControlAttributes; + break; + case "ListView": + specialAttributes = MsiInterop.ListviewControlAttributes; + break; + case "MaskedEdit": + specialAttributes = MsiInterop.EditControlAttributes; + break; + case "PathEdit": + specialAttributes = MsiInterop.EditControlAttributes; + break; + case "ProgressBar": + specialAttributes = MsiInterop.ProgressControlAttributes; + break; + case "PushButton": + specialAttributes = MsiInterop.ButtonControlAttributes; + break; + case "RadioButtonGroup": + specialAttributes = MsiInterop.RadioControlAttributes; + break; + case "Text": + specialAttributes = MsiInterop.TextControlAttributes; + break; + case "VolumeCostList": + specialAttributes = MsiInterop.VolumeControlAttributes; + break; + case "VolumeSelectCombo": + specialAttributes = MsiInterop.VolumeControlAttributes; + break; + default: + specialAttributes = null; + break; + } + + if (null != specialAttributes) + { + var iconSizeSet = false; + + for (var i = 16; 32 > i; i++) + { + if (1 == ((controlRow.Attributes >> i) & 1)) + { + string attribute = null; + + if (specialAttributes.Length > (i - 16)) + { + attribute = specialAttributes[i - 16]; + } + + // unknown attribute + if (null == attribute) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); + continue; + } + + switch (attribute) + { + case "Bitmap": + control.Bitmap = Wix.YesNoType.yes; + break; + case "CDROM": + control.CDROM = Wix.YesNoType.yes; + break; + case "ComboList": + control.ComboList = Wix.YesNoType.yes; + break; + case "ElevationShield": + control.ElevationShield = Wix.YesNoType.yes; + break; + case "Fixed": + control.Fixed = Wix.YesNoType.yes; + break; + case "FixedSize": + control.FixedSize = Wix.YesNoType.yes; + break; + case "Floppy": + control.Floppy = Wix.YesNoType.yes; + break; + case "FormatSize": + control.FormatSize = Wix.YesNoType.yes; + break; + case "HasBorder": + control.HasBorder = Wix.YesNoType.yes; + break; + case "Icon": + control.Icon = Wix.YesNoType.yes; + break; + case "Icon16": + if (iconSizeSet) + { + control.IconSize = Wix.Control.IconSizeType.Item48; + } + else + { + iconSizeSet = true; + control.IconSize = Wix.Control.IconSizeType.Item16; + } + break; + case "Icon32": + if (iconSizeSet) + { + control.IconSize = Wix.Control.IconSizeType.Item48; + } + else + { + iconSizeSet = true; + control.IconSize = Wix.Control.IconSizeType.Item32; + } + break; + case "Image": + control.Image = Wix.YesNoType.yes; + break; + case "Multiline": + control.Multiline = Wix.YesNoType.yes; + break; + case "NoPrefix": + control.NoPrefix = Wix.YesNoType.yes; + break; + case "NoWrap": + control.NoWrap = Wix.YesNoType.yes; + break; + case "Password": + control.Password = Wix.YesNoType.yes; + break; + case "ProgressBlocks": + control.ProgressBlocks = Wix.YesNoType.yes; + break; + case "PushLike": + control.PushLike = Wix.YesNoType.yes; + break; + case "RAMDisk": + control.RAMDisk = Wix.YesNoType.yes; + break; + case "Remote": + control.Remote = Wix.YesNoType.yes; + break; + case "Removable": + control.Removable = Wix.YesNoType.yes; + break; + case "ShowRollbackCost": + control.ShowRollbackCost = Wix.YesNoType.yes; + break; + case "Sorted": + control.Sorted = Wix.YesNoType.yes; + break; + case "Transparent": + control.Transparent = Wix.YesNoType.yes; + break; + case "UserLanguage": + control.UserLanguage = Wix.YesNoType.yes; + break; + default: + throw new InvalidOperationException($"Unknown control attribute: '{attribute}'."); + } + } + } + } + else if (0 < (controlRow.Attributes & 0xFFFF0000)) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); + } + } + + // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef + if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", control.Type)) + { + control.Property = controlRow.Property; + } + + if (null != controlRow.Text) + { + control.Text = controlRow.Text; + } + + if (null != controlRow.Help) + { + var help = controlRow.Help.Split('|'); + + if (2 == help.Length) + { + if (0 < help[0].Length) + { + control.ToolTip = help[0]; + } + + if (0 < help[1].Length) + { + control.Help = help[1]; + } + } + } + + this.core.IndexElement(controlRow, control); + } + } + + /// + /// Decompile the ControlCondition table. + /// + /// The table to decompile. + private void DecompileControlConditionTable(Table table) + { + foreach (var row in table.Rows) + { + var condition = new Wix.Condition(); + + switch (Convert.ToString(row[2])) + { + case "Default": + condition.Action = Wix.Condition.ActionType.@default; + break; + case "Disable": + condition.Action = Wix.Condition.ActionType.disable; + break; + case "Enable": + condition.Action = Wix.Condition.ActionType.enable; + break; + case "Hide": + condition.Action = Wix.Condition.ActionType.hide; + break; + case "Show": + condition.Action = Wix.Condition.ActionType.show; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + break; + } + + condition.Content = Convert.ToString(row[3]); + + var control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1])); + if (null != control) + { + control.AddChild(condition); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control")); + } + } + } + + /// + /// Decompile the ControlEvent table. + /// + /// The table to decompile. + private void DecompileControlEventTable(Table table) + { + var controlEvents = new SortedList(); + + foreach (var row in table.Rows) + { + var publish = new Wix.Publish(); + + var publishEvent = Convert.ToString(row[2]); + if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal)) + { + publish.Property = publishEvent.Substring(1, publishEvent.Length - 2); + + if ("{}" != Convert.ToString(row[3])) + { + publish.Value = Convert.ToString(row[3]); + } + } + else + { + publish.Event = publishEvent; + publish.Value = Convert.ToString(row[3]); + } + + if (null != row[4]) + { + publish.Content = Convert.ToString(row[4]); + } + + controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row[0], row[1], (null == row[5] ? 0 : Convert.ToInt32(row[5])), row[2], row[3], row[4]), row); + + this.core.IndexElement(row, publish); + } + + foreach (Row row in controlEvents.Values) + { + var control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1])); + var publish = (Wix.Publish)this.core.GetIndexedElement(row); + + if (null != control) + { + control.AddChild(publish); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control")); + } + } + } + + /// + /// Decompile a custom table. + /// + /// The table to decompile. + private void DecompileCustomTable(Table table) + { + if (0 < table.Rows.Count || this.SuppressDroppingEmptyTables) + { + var customTable = new Wix.CustomTable(); + + this.Messaging.Write(WarningMessages.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name)); + + customTable.Id = table.Name; + + foreach (var columnDefinition in table.Definition.Columns) + { + var column = new Wix.Column(); + + column.Id = columnDefinition.Name; + + if (ColumnCategory.Unknown != columnDefinition.Category) + { + switch (columnDefinition.Category) + { + case ColumnCategory.Text: + column.Category = Wix.Column.CategoryType.Text; + break; + case ColumnCategory.UpperCase: + column.Category = Wix.Column.CategoryType.UpperCase; + break; + case ColumnCategory.LowerCase: + column.Category = Wix.Column.CategoryType.LowerCase; + break; + case ColumnCategory.Integer: + column.Category = Wix.Column.CategoryType.Integer; + break; + case ColumnCategory.DoubleInteger: + column.Category = Wix.Column.CategoryType.DoubleInteger; + break; + case ColumnCategory.TimeDate: + column.Category = Wix.Column.CategoryType.TimeDate; + break; + case ColumnCategory.Identifier: + column.Category = Wix.Column.CategoryType.Identifier; + break; + case ColumnCategory.Property: + column.Category = Wix.Column.CategoryType.Property; + break; + case ColumnCategory.Filename: + column.Category = Wix.Column.CategoryType.Filename; + break; + case ColumnCategory.WildCardFilename: + column.Category = Wix.Column.CategoryType.WildCardFilename; + break; + case ColumnCategory.Path: + column.Category = Wix.Column.CategoryType.Path; + break; + case ColumnCategory.Paths: + column.Category = Wix.Column.CategoryType.Paths; + break; + case ColumnCategory.AnyPath: + column.Category = Wix.Column.CategoryType.AnyPath; + break; + case ColumnCategory.DefaultDir: + column.Category = Wix.Column.CategoryType.DefaultDir; + break; + case ColumnCategory.RegPath: + column.Category = Wix.Column.CategoryType.RegPath; + break; + case ColumnCategory.Formatted: + column.Category = Wix.Column.CategoryType.Formatted; + break; + case ColumnCategory.FormattedSDDLText: + column.Category = Wix.Column.CategoryType.FormattedSddl; + break; + case ColumnCategory.Template: + column.Category = Wix.Column.CategoryType.Template; + break; + case ColumnCategory.Condition: + column.Category = Wix.Column.CategoryType.Condition; + break; + case ColumnCategory.Guid: + column.Category = Wix.Column.CategoryType.Guid; + break; + case ColumnCategory.Version: + column.Category = Wix.Column.CategoryType.Version; + break; + case ColumnCategory.Language: + column.Category = Wix.Column.CategoryType.Language; + break; + case ColumnCategory.Binary: + column.Category = Wix.Column.CategoryType.Binary; + break; + case ColumnCategory.CustomSource: + column.Category = Wix.Column.CategoryType.CustomSource; + break; + case ColumnCategory.Cabinet: + column.Category = Wix.Column.CategoryType.Cabinet; + break; + case ColumnCategory.Shortcut: + column.Category = Wix.Column.CategoryType.Shortcut; + break; + default: + throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'."); + } + } + + if (null != columnDefinition.Description) + { + column.Description = columnDefinition.Description; + } + + if (columnDefinition.KeyColumn.HasValue) + { + column.KeyColumn = columnDefinition.KeyColumn.Value; + } + + if (null != columnDefinition.KeyTable) + { + column.KeyTable = columnDefinition.KeyTable; + } + + if (columnDefinition.IsLocalizable) + { + column.Localizable = Wix.YesNoType.yes; + } + + if (columnDefinition.MaxValue.HasValue) + { + column.MaxValue = columnDefinition.MaxValue.Value; + } + + if (columnDefinition.MinValue.HasValue) + { + column.MinValue = columnDefinition.MinValue.Value; + } + + if (ColumnModularizeType.None != columnDefinition.ModularizeType) + { + switch (columnDefinition.ModularizeType) + { + case ColumnModularizeType.Column: + column.Modularize = Wix.Column.ModularizeType.Column; + break; + case ColumnModularizeType.Condition: + column.Modularize = Wix.Column.ModularizeType.Condition; + break; + case ColumnModularizeType.Icon: + column.Modularize = Wix.Column.ModularizeType.Icon; + break; + case ColumnModularizeType.Property: + column.Modularize = Wix.Column.ModularizeType.Property; + break; + case ColumnModularizeType.SemicolonDelimited: + column.Modularize = Wix.Column.ModularizeType.SemicolonDelimited; + break; + default: + throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'."); + } + } + + if (columnDefinition.Nullable) + { + column.Nullable = Wix.YesNoType.yes; + } + + if (columnDefinition.PrimaryKey) + { + column.PrimaryKey = Wix.YesNoType.yes; + } + + if (null != columnDefinition.Possibilities) + { + column.Set = columnDefinition.Possibilities; + } + + if (ColumnType.Unknown != columnDefinition.Type) + { + switch (columnDefinition.Type) + { + case ColumnType.Localized: + column.Localizable = Wix.YesNoType.yes; + column.Type = Wix.Column.TypeType.@string; + break; + case ColumnType.Number: + column.Type = Wix.Column.TypeType.@int; + break; + case ColumnType.Object: + column.Type = Wix.Column.TypeType.binary; + break; + case ColumnType.Preserved: + case ColumnType.String: + column.Type = Wix.Column.TypeType.@string; + break; + default: + throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type.ToString()}'."); + } + } + + column.Width = columnDefinition.Length; + + customTable.AddChild(column); + } + + foreach (var row in table.Rows) + { + var wixRow = new Wix.Row(); + + foreach (var field in row.Fields) + { + var data = new Wix.Data(); + + data.Column = field.Column.Name; + + data.Content = Convert.ToString(field.Data, CultureInfo.InvariantCulture); + + wixRow.AddChild(data); + } + + customTable.AddChild(wixRow); + } + + this.core.RootElement.AddChild(customTable); + } + } + + /// + /// Decompile the CreateFolder table. + /// + /// The table to decompile. + private void DecompileCreateFolderTable(Table table) + { + foreach (var row in table.Rows) + { + var createFolder = new Wix.CreateFolder(); + + createFolder.Directory = Convert.ToString(row[0]); + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + if (null != component) + { + component.AddChild(createFolder); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + this.core.IndexElement(row, createFolder); + } + } + + /// + /// Decompile the CustomAction table. + /// + /// The table to decompile. + private void DecompileCustomActionTable(Table table) + { + foreach (var row in table.Rows) + { + var customAction = new Wix.CustomAction(); + + customAction.Id = Convert.ToString(row[0]); + + var type = Convert.ToInt32(row[1]); + + if (MsiInterop.MsidbCustomActionTypeHideTarget == (type & MsiInterop.MsidbCustomActionTypeHideTarget)) + { + customAction.HideTarget = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbCustomActionTypeNoImpersonate == (type & MsiInterop.MsidbCustomActionTypeNoImpersonate)) + { + customAction.Impersonate = Wix.YesNoType.no; + } + + if (MsiInterop.MsidbCustomActionTypeTSAware == (type & MsiInterop.MsidbCustomActionTypeTSAware)) + { + customAction.TerminalServerAware = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbCustomActionType64BitScript == (type & MsiInterop.MsidbCustomActionType64BitScript)) + { + customAction.Win64 = Wix.YesNoType.yes; + } + + switch (type & MsiInterop.MsidbCustomActionTypeExecuteBits) + { + case 0: + // this is the default value + break; + case MsiInterop.MsidbCustomActionTypeFirstSequence: + customAction.Execute = Wix.CustomAction.ExecuteType.firstSequence; + break; + case MsiInterop.MsidbCustomActionTypeOncePerProcess: + customAction.Execute = Wix.CustomAction.ExecuteType.oncePerProcess; + break; + case MsiInterop.MsidbCustomActionTypeClientRepeat: + customAction.Execute = Wix.CustomAction.ExecuteType.secondSequence; + break; + case MsiInterop.MsidbCustomActionTypeInScript: + customAction.Execute = Wix.CustomAction.ExecuteType.deferred; + break; + case MsiInterop.MsidbCustomActionTypeInScript + MsiInterop.MsidbCustomActionTypeRollback: + customAction.Execute = Wix.CustomAction.ExecuteType.rollback; + break; + case MsiInterop.MsidbCustomActionTypeInScript + MsiInterop.MsidbCustomActionTypeCommit: + customAction.Execute = Wix.CustomAction.ExecuteType.commit; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + switch (type & MsiInterop.MsidbCustomActionTypeReturnBits) + { + case 0: + // this is the default value + break; + case MsiInterop.MsidbCustomActionTypeContinue: + customAction.Return = Wix.CustomAction.ReturnType.ignore; + break; + case MsiInterop.MsidbCustomActionTypeAsync: + customAction.Return = Wix.CustomAction.ReturnType.asyncWait; + break; + case MsiInterop.MsidbCustomActionTypeAsync + MsiInterop.MsidbCustomActionTypeContinue: + customAction.Return = Wix.CustomAction.ReturnType.asyncNoWait; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + var source = type & MsiInterop.MsidbCustomActionTypeSourceBits; + switch (source) + { + case MsiInterop.MsidbCustomActionTypeBinaryData: + customAction.BinaryKey = Convert.ToString(row[2]); + break; + case MsiInterop.MsidbCustomActionTypeSourceFile: + if (null != row[2]) + { + customAction.FileKey = Convert.ToString(row[2]); + } + break; + case MsiInterop.MsidbCustomActionTypeDirectory: + if (null != row[2]) + { + customAction.Directory = Convert.ToString(row[2]); + } + break; + case MsiInterop.MsidbCustomActionTypeProperty: + customAction.Property = Convert.ToString(row[2]); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + switch (type & MsiInterop.MsidbCustomActionTypeTargetBits) + { + case MsiInterop.MsidbCustomActionTypeDll: + customAction.DllEntry = Convert.ToString(row[3]); + break; + case MsiInterop.MsidbCustomActionTypeExe: + customAction.ExeCommand = Convert.ToString(row[3]); + break; + case MsiInterop.MsidbCustomActionTypeTextData: + if (MsiInterop.MsidbCustomActionTypeSourceFile == source) + { + customAction.Error = Convert.ToString(row[3]); + } + else + { + customAction.Value = Convert.ToString(row[3]); + } + break; + case MsiInterop.MsidbCustomActionTypeJScript: + if (MsiInterop.MsidbCustomActionTypeDirectory == source) + { + customAction.Script = Wix.CustomAction.ScriptType.jscript; + customAction.Content = Convert.ToString(row[3]); + } + else + { + customAction.JScriptCall = Convert.ToString(row[3]); + } + break; + case MsiInterop.MsidbCustomActionTypeVBScript: + if (MsiInterop.MsidbCustomActionTypeDirectory == source) + { + customAction.Script = Wix.CustomAction.ScriptType.vbscript; + customAction.Content = Convert.ToString(row[3]); + } + else + { + customAction.VBScriptCall = Convert.ToString(row[3]); + } + break; + case MsiInterop.MsidbCustomActionTypeInstall: + this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + continue; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + var extype = 4 < row.Fields.Length && null != row[4] ? Convert.ToInt32(row[4]) : 0; + if (MsiInterop.MsidbCustomActionTypePatchUninstall == (extype & MsiInterop.MsidbCustomActionTypePatchUninstall)) + { + customAction.PatchUninstall = Wix.YesNoType.yes; + } + + this.core.RootElement.AddChild(customAction); + this.core.IndexElement(row, customAction); + } + } + + /// + /// Decompile the CompLocator table. + /// + /// The table to decompile. + private void DecompileCompLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var componentSearch = new Wix.ComponentSearch(); + + componentSearch.Id = Convert.ToString(row[0]); + + componentSearch.Guid = Convert.ToString(row[1]); + + if (null != row[2]) + { + switch (Convert.ToInt32(row[2])) + { + case MsiInterop.MsidbLocatorTypeDirectory: + componentSearch.Type = Wix.ComponentSearch.TypeType.directory; + break; + case MsiInterop.MsidbLocatorTypeFileName: + // this is the default value + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + break; + } + } + + this.core.IndexElement(row, componentSearch); + } + } + + /// + /// Decompile the Complus table. + /// + /// The table to decompile. + private void DecompileComplusTable(Table table) + { + foreach (var row in table.Rows) + { + if (null != row[1]) + { + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0])); + + if (null != component) + { + component.ComPlusFlags = Convert.ToInt32(row[1]); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[0]), "Component")); + } + } + } + } + + /// + /// Decompile the Component table. + /// + /// The table to decompile. + private void DecompileComponentTable(Table table) + { + foreach (var row in table.Rows) + { + var component = new Wix.Component(); + + component.Id = Convert.ToString(row[0]); + + component.Guid = Convert.ToString(row[1]); + + var attributes = Convert.ToInt32(row[3]); + + if (MsiInterop.MsidbComponentAttributesSourceOnly == (attributes & MsiInterop.MsidbComponentAttributesSourceOnly)) + { + component.Location = Wix.Component.LocationType.source; + } + else if (MsiInterop.MsidbComponentAttributesOptional == (attributes & MsiInterop.MsidbComponentAttributesOptional)) + { + component.Location = Wix.Component.LocationType.either; + } + + if (MsiInterop.MsidbComponentAttributesSharedDllRefCount == (attributes & MsiInterop.MsidbComponentAttributesSharedDllRefCount)) + { + component.SharedDllRefCount = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbComponentAttributesPermanent == (attributes & MsiInterop.MsidbComponentAttributesPermanent)) + { + component.Permanent = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbComponentAttributesTransitive == (attributes & MsiInterop.MsidbComponentAttributesTransitive)) + { + component.Transitive = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbComponentAttributesNeverOverwrite == (attributes & MsiInterop.MsidbComponentAttributesNeverOverwrite)) + { + component.NeverOverwrite = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbComponentAttributes64bit == (attributes & MsiInterop.MsidbComponentAttributes64bit)) + { + component.Win64 = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbComponentAttributesDisableRegistryReflection == (attributes & MsiInterop.MsidbComponentAttributesDisableRegistryReflection)) + { + component.DisableRegistryReflection = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbComponentAttributesUninstallOnSupersedence == (attributes & MsiInterop.MsidbComponentAttributesUninstallOnSupersedence)) + { + component.UninstallWhenSuperseded = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbComponentAttributesShared == (attributes & MsiInterop.MsidbComponentAttributesShared)) + { + component.Shared = Wix.YesNoType.yes; + } + + if (null != row[4]) + { + var condition = new Wix.Condition(); + + condition.Content = Convert.ToString(row[4]); + + component.AddChild(condition); + } + + var directory = (Wix.Directory)this.core.GetIndexedElement("Directory", Convert.ToString(row[2])); + if (null != directory) + { + directory.AddChild(component); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_", Convert.ToString(row[2]), "Directory")); + } + this.core.IndexElement(row, component); + } + } + + /// + /// Decompile the Condition table. + /// + /// The table to decompile. + private void DecompileConditionTable(Table table) + { + foreach (var row in table.Rows) + { + var condition = new Wix.Condition(); + + condition.Level = Convert.ToInt32(row[1]); + + if (null != row[2]) + { + condition.Content = Convert.ToString(row[2]); + } + + var feature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); + if (null != feature) + { + feature.AddChild(condition); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", Convert.ToString(row[0]), "Feature")); + } + } + } + + /// + /// Decompile the Dialog table. + /// + /// The table to decompile. + private void DecompileDialogTable(Table table) + { + foreach (var row in table.Rows) + { + var dialog = new Wix.Dialog(); + + dialog.Id = Convert.ToString(row[0]); + + dialog.X = Convert.ToInt32(row[1]); + + dialog.Y = Convert.ToInt32(row[2]); + + dialog.Width = Convert.ToInt32(row[3]); + + dialog.Height = Convert.ToInt32(row[4]); + + if (null != row[5]) + { + var attributes = Convert.ToInt32(row[5]); + + if (0 == (attributes & MsiInterop.MsidbDialogAttributesVisible)) + { + dialog.Hidden = Wix.YesNoType.yes; + } + + if (0 == (attributes & MsiInterop.MsidbDialogAttributesModal)) + { + dialog.Modeless = Wix.YesNoType.yes; + } + + if (0 == (attributes & MsiInterop.MsidbDialogAttributesMinimize)) + { + dialog.NoMinimize = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbDialogAttributesSysModal == (attributes & MsiInterop.MsidbDialogAttributesSysModal)) + { + dialog.SystemModal = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbDialogAttributesKeepModeless == (attributes & MsiInterop.MsidbDialogAttributesKeepModeless)) + { + dialog.KeepModeless = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbDialogAttributesTrackDiskSpace == (attributes & MsiInterop.MsidbDialogAttributesTrackDiskSpace)) + { + dialog.TrackDiskSpace = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbDialogAttributesUseCustomPalette == (attributes & MsiInterop.MsidbDialogAttributesUseCustomPalette)) + { + dialog.CustomPalette = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbDialogAttributesRTLRO == (attributes & MsiInterop.MsidbDialogAttributesRTLRO)) + { + dialog.RightToLeft = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbDialogAttributesRightAligned == (attributes & MsiInterop.MsidbDialogAttributesRightAligned)) + { + dialog.RightAligned = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbDialogAttributesLeftScroll == (attributes & MsiInterop.MsidbDialogAttributesLeftScroll)) + { + dialog.LeftScroll = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbDialogAttributesError == (attributes & MsiInterop.MsidbDialogAttributesError)) + { + dialog.ErrorDialog = Wix.YesNoType.yes; + } + } + + if (null != row[6]) + { + dialog.Title = Convert.ToString(row[6]); + } + + this.core.UIElement.AddChild(dialog); + this.core.IndexElement(row, dialog); + } + } + + /// + /// Decompile the Directory table. + /// + /// The table to decompile. + private void DecompileDirectoryTable(Table table) + { + foreach (var row in table.Rows) + { + var directory = new Wix.Directory(); + + directory.Id = Convert.ToString(row[0]); + + var names = Common.GetNames(Convert.ToString(row[2])); + + if (String.Equals(directory.Id, "TARGETDIR", StringComparison.Ordinal) && !String.Equals(names[0], "SourceDir", StringComparison.Ordinal)) + { + this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir()); + directory.Name = "SourceDir"; + } + else + { + if (null != names[0] && "." != names[0]) + { + if (null != names[1]) + { + directory.ShortName = names[0]; + } + else + { + directory.Name = names[0]; + } + } + + if (null != names[1]) + { + directory.Name = names[1]; + } + } + + if (null != names[2]) + { + if (null != names[3]) + { + directory.ShortSourceName = names[2]; + } + else + { + directory.SourceName = names[2]; + } + } + + if (null != names[3]) + { + directory.SourceName = names[3]; + } + + this.core.IndexElement(row, directory); + } + + // nest the directories + foreach (var row in table.Rows) + { + var directory = (Wix.Directory)this.core.GetIndexedElement(row); + + if (null == row[1]) + { + this.core.RootElement.AddChild(directory); + } + else + { + var parentDirectory = (Wix.Directory)this.core.GetIndexedElement("Directory", Convert.ToString(row[1])); + + if (null == parentDirectory) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", Convert.ToString(row[1]), "Directory")); + } + else if (parentDirectory == directory) // another way to specify a root directory + { + this.core.RootElement.AddChild(directory); + } + else + { + parentDirectory.AddChild(directory); + } + } + } + } + + /// + /// Decompile the DrLocator table. + /// + /// The table to decompile. + private void DecompileDrLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var directorySearch = new Wix.DirectorySearch(); + + directorySearch.Id = Convert.ToString(row[0]); + + if (null != row[2]) + { + directorySearch.Path = Convert.ToString(row[2]); + } + + if (null != row[3]) + { + directorySearch.Depth = Convert.ToInt32(row[3]); + } + + this.core.IndexElement(row, directorySearch); + } + } + + /// + /// Decompile the DuplicateFile table. + /// + /// The table to decompile. + private void DecompileDuplicateFileTable(Table table) + { + foreach (var row in table.Rows) + { + var copyFile = new Wix.CopyFile(); + + copyFile.Id = Convert.ToString(row[0]); + + copyFile.FileId = Convert.ToString(row[2]); + + if (null != row[3]) + { + var names = Common.GetNames(Convert.ToString(row[3])); + if (null != names[0] && null != names[1]) + { + copyFile.DestinationShortName = names[0]; + copyFile.DestinationName = names[1]; + } + else if (null != names[0]) + { + copyFile.DestinationName = names[0]; + } + } + + // destination directory/property is set in FinalizeDuplicateMoveFileTables + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + if (null != component) + { + component.AddChild(copyFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + this.core.IndexElement(row, copyFile); + } + } + + /// + /// Decompile the Environment table. + /// + /// The table to decompile. + private void DecompileEnvironmentTable(Table table) + { + foreach (var row in table.Rows) + { + var environment = new Wix.Environment(); + + environment.Id = Convert.ToString(row[0]); + + var done = false; + var permanent = true; + var name = Convert.ToString(row[1]); + for (var i = 0; i < name.Length && !done; i++) + { + switch (name[i]) + { + case '=': + environment.Action = Wix.Environment.ActionType.set; + break; + case '+': + environment.Action = Wix.Environment.ActionType.create; + break; + case '-': + permanent = false; + break; + case '!': + environment.Action = Wix.Environment.ActionType.remove; + break; + case '*': + environment.System = Wix.YesNoType.yes; + break; + default: + environment.Name = name.Substring(i); + done = true; + break; + } + } + + if (permanent) + { + environment.Permanent = Wix.YesNoType.yes; + } + + if (null != row[2]) + { + var value = Convert.ToString(row[2]); + + if (value.StartsWith("[~]", StringComparison.Ordinal)) + { + environment.Part = Wix.Environment.PartType.last; + + if (3 < value.Length) + { + environment.Separator = value.Substring(3, 1); + environment.Value = value.Substring(4); + } + } + else if (value.EndsWith("[~]", StringComparison.Ordinal)) + { + environment.Part = Wix.Environment.PartType.first; + + if (3 < value.Length) + { + environment.Separator = value.Substring(value.Length - 4, 1); + environment.Value = value.Substring(0, value.Length - 4); + } + } + else + { + environment.Value = value; + } + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[3])); + if (null != component) + { + component.AddChild(environment); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[3]), "Component")); + } + } + } + + /// + /// Decompile the Error table. + /// + /// The table to decompile. + private void DecompileErrorTable(Table table) + { + foreach (var row in table.Rows) + { + var error = new Wix.Error(); + + error.Id = Convert.ToInt32(row[0]); + + error.Content = Convert.ToString(row[1]); + + this.core.UIElement.AddChild(error); + } + } + + /// + /// Decompile the EventMapping table. + /// + /// The table to decompile. + private void DecompileEventMappingTable(Table table) + { + foreach (var row in table.Rows) + { + var subscribe = new Wix.Subscribe(); + + subscribe.Event = Convert.ToString(row[2]); + + subscribe.Attribute = Convert.ToString(row[3]); + + var control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1])); + if (null != control) + { + control.AddChild(subscribe); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control")); + } + } + } + + /// + /// Decompile the Extension table. + /// + /// The table to decompile. + private void DecompileExtensionTable(Table table) + { + foreach (var row in table.Rows) + { + var extension = new Wix.Extension(); + + extension.Advertise = Wix.YesNoType.yes; + + extension.Id = Convert.ToString(row[0]); + + if (null != row[3]) + { + var mime = (Wix.MIME)this.core.GetIndexedElement("MIME", Convert.ToString(row[3])); + + if (null != mime) + { + mime.Default = Wix.YesNoType.yes; + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", Convert.ToString(row[3]), "MIME")); + } + } + + if (null != row[2]) + { + var progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[2])); + + if (null != progId) + { + progId.AddChild(extension); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_", Convert.ToString(row[2]), "ProgId")); + } + } + else + { + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + + if (null != component) + { + component.AddChild(extension); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + } + + this.core.IndexElement(row, extension); + } + } + + /// + /// Decompile the ExternalFiles table. + /// + /// The table to decompile. + private void DecompileExternalFilesTable(Table table) + { + foreach (var row in table.Rows) + { + var externalFile = new Wix.ExternalFile(); + + externalFile.File = Convert.ToString(row[1]); + + externalFile.Source = Convert.ToString(row[2]); + + if (null != row[3]) + { + var symbolPaths = (Convert.ToString(row[3])).Split(';'); + + foreach (var symbolPathString in symbolPaths) + { + var symbolPath = new Wix.SymbolPath(); + + symbolPath.Path = symbolPathString; + + externalFile.AddChild(symbolPath); + } + } + + if (null != row[4] && null != row[5]) + { + var ignoreOffsets = (Convert.ToString(row[4])).Split(','); + var ignoreLengths = (Convert.ToString(row[5])).Split(','); + + if (ignoreOffsets.Length == ignoreLengths.Length) + { + for (var i = 0; i < ignoreOffsets.Length; i++) + { + var ignoreRange = new Wix.IgnoreRange(); + + if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) + { + ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i].Substring(2), 16); + } + else + { + ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture); + } + + if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) + { + ignoreRange.Length = Convert.ToInt32(ignoreLengths[i].Substring(2), 16); + } + else + { + ignoreRange.Length = Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture); + } + + externalFile.AddChild(ignoreRange); + } + } + else + { + // TODO: warn + } + } + else if (null != row[4] || null != row[5]) + { + // TODO: warn about mismatch between columns + } + + // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable + + if (null != row[7]) + { + externalFile.Order = Convert.ToInt32(row[7]); + } + + var family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[0])); + if (null != family) + { + family.AddChild(externalFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[0]), "ImageFamilies")); + } + this.core.IndexElement(row, externalFile); + } + } + + /// + /// Decompile the Feature table. + /// + /// The table to decompile. + private void DecompileFeatureTable(Table table) + { + var sortedFeatures = new SortedList(); + + foreach (var row in table.Rows) + { + var feature = new Wix.Feature(); + + feature.Id = Convert.ToString(row[0]); + + if (null != row[2]) + { + feature.Title = Convert.ToString(row[2]); + } + + if (null != row[3]) + { + feature.Description = Convert.ToString(row[3]); + } + + if (null == row[4]) + { + feature.Display = "hidden"; + } + else + { + var display = Convert.ToInt32(row[4]); + + if (0 == display) + { + feature.Display = "hidden"; + } + else if (1 == display % 2) + { + feature.Display = "expand"; + } + } + + feature.Level = Convert.ToInt32(row[5]); + + if (null != row[6]) + { + feature.ConfigurableDirectory = Convert.ToString(row[6]); + } + + var attributes = Convert.ToInt32(row[7]); + + if (MsiInterop.MsidbFeatureAttributesFavorSource == (attributes & MsiInterop.MsidbFeatureAttributesFavorSource) && MsiInterop.MsidbFeatureAttributesFollowParent == (attributes & MsiInterop.MsidbFeatureAttributesFollowParent)) + { + // TODO: display a warning for setting favor local and follow parent together + } + else if (MsiInterop.MsidbFeatureAttributesFavorSource == (attributes & MsiInterop.MsidbFeatureAttributesFavorSource)) + { + feature.InstallDefault = Wix.Feature.InstallDefaultType.source; + } + else if (MsiInterop.MsidbFeatureAttributesFollowParent == (attributes & MsiInterop.MsidbFeatureAttributesFollowParent)) + { + feature.InstallDefault = Wix.Feature.InstallDefaultType.followParent; + } + + if (MsiInterop.MsidbFeatureAttributesFavorAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesFavorAdvertise)) + { + feature.TypicalDefault = Wix.Feature.TypicalDefaultType.advertise; + } + + if (MsiInterop.MsidbFeatureAttributesDisallowAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesDisallowAdvertise) && + MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise)) + { + this.Messaging.Write(WarningMessages.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no")); + feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.no; + } + else if (MsiInterop.MsidbFeatureAttributesDisallowAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesDisallowAdvertise)) + { + feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.no; + } + else if (MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise)) + { + feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.system; + } + + if (MsiInterop.MsidbFeatureAttributesUIDisallowAbsent == (attributes & MsiInterop.MsidbFeatureAttributesUIDisallowAbsent)) + { + feature.Absent = Wix.Feature.AbsentType.disallow; + } + + this.core.IndexElement(row, feature); + + // sort the features by their display column (and append the identifier to ensure unique keys) + sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", Convert.ToInt32(row[4], CultureInfo.InvariantCulture), row[0]), row); + } + + // nest the features + foreach (Row row in sortedFeatures.Values) + { + var feature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); + + if (null == row[1]) + { + this.core.RootElement.AddChild(feature); + } + else + { + var parentFeature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[1])); + + if (null == parentFeature) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", Convert.ToString(row[1]), "Feature")); + } + else if (parentFeature == feature) + { + // TODO: display a warning about self-nesting + } + else + { + parentFeature.AddChild(feature); + } + } + } + } + + /// + /// Decompile the FeatureComponents table. + /// + /// The table to decompile. + private void DecompileFeatureComponentsTable(Table table) + { + foreach (var row in table.Rows) + { + var componentRef = new Wix.ComponentRef(); + + componentRef.Id = Convert.ToString(row[1]); + + var parentFeature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); + if (null != parentFeature) + { + parentFeature.AddChild(componentRef); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", Convert.ToString(row[0]), "Feature")); + } + this.core.IndexElement(row, componentRef); + } + } + + /// + /// Decompile the File table. + /// + /// The table to decompile. + private void DecompileFileTable(Table table) + { + foreach (FileRow fileRow in table.Rows) + { + var file = new Wix.File(); + + file.Id = fileRow.File; + + var names = Common.GetNames(fileRow.FileName); + if (null != names[0] && null != names[1]) + { + file.ShortName = names[0]; + file.Name = names[1]; + } + else if (null != names[0]) + { + file.Name = names[0]; + } + + if (null != fileRow.Version && 0 < fileRow.Version.Length) + { + if (!Char.IsDigit(fileRow.Version[0])) + { + file.CompanionFile = fileRow.Version; + } + } + + if (MsiInterop.MsidbFileAttributesReadOnly == (fileRow.Attributes & MsiInterop.MsidbFileAttributesReadOnly)) + { + file.ReadOnly = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbFileAttributesHidden == (fileRow.Attributes & MsiInterop.MsidbFileAttributesHidden)) + { + file.Hidden = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbFileAttributesSystem == (fileRow.Attributes & MsiInterop.MsidbFileAttributesSystem)) + { + file.System = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbFileAttributesVital != (fileRow.Attributes & MsiInterop.MsidbFileAttributesVital)) + { + file.Vital = Wix.YesNoType.no; + } + + if (MsiInterop.MsidbFileAttributesChecksum == (fileRow.Attributes & MsiInterop.MsidbFileAttributesChecksum)) + { + file.Checksum = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbFileAttributesNoncompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesNoncompressed) && + MsiInterop.MsidbFileAttributesCompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesCompressed)) + { + // TODO: error + } + else if (MsiInterop.MsidbFileAttributesNoncompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesNoncompressed)) + { + file.Compressed = Wix.YesNoDefaultType.no; + } + else if (MsiInterop.MsidbFileAttributesCompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesCompressed)) + { + file.Compressed = Wix.YesNoDefaultType.yes; + } + + this.core.IndexElement(fileRow, file); + } + } + + /// + /// Decompile the FileSFPCatalog table. + /// + /// The table to decompile. + private void DecompileFileSFPCatalogTable(Table table) + { + foreach (var row in table.Rows) + { + var sfpFile = new Wix.SFPFile(); + + sfpFile.Id = Convert.ToString(row[0]); + + var sfpCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement("SFPCatalog", Convert.ToString(row[1])); + if (null != sfpCatalog) + { + sfpCatalog.AddChild(sfpFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SFPCatalog_", Convert.ToString(row[1]), "SFPCatalog")); + } + } + } + + /// + /// Decompile the Font table. + /// + /// The table to decompile. + private void DecompileFontTable(Table table) + { + foreach (var row in table.Rows) + { + var file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); + + if (null != file) + { + if (null != row[1]) + { + file.FontTitle = Convert.ToString(row[1]); + } + else + { + file.TrueType = Wix.YesNoType.yes; + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); + } + } + } + + /// + /// Decompile the Icon table. + /// + /// The table to decompile. + private void DecompileIconTable(Table table) + { + foreach (var row in table.Rows) + { + var icon = new Wix.Icon(); + + icon.Id = Convert.ToString(row[0]); + + icon.SourceFile = Convert.ToString(row[1]); + + this.core.RootElement.AddChild(icon); + } + } + + /// + /// Decompile the ImageFamilies table. + /// + /// The table to decompile. + private void DecompileImageFamiliesTable(Table table) + { + foreach (var row in table.Rows) + { + var family = new Wix.Family(); + + family.Name = Convert.ToString(row[0]); + + if (null != row[1]) + { + family.MediaSrcProp = Convert.ToString(row[1]); + } + + if (null != row[2]) + { + family.DiskId = Convert.ToString(Convert.ToInt32(row[2])); + } + + if (null != row[3]) + { + family.SequenceStart = Convert.ToInt32(row[3]); + } + + if (null != row[4]) + { + family.DiskPrompt = Convert.ToString(row[4]); + } + + if (null != row[5]) + { + family.VolumeLabel = Convert.ToString(row[5]); + } + + this.core.RootElement.AddChild(family); + this.core.IndexElement(row, family); + } + } + + /// + /// Decompile the IniFile table. + /// + /// The table to decompile. + private void DecompileIniFileTable(Table table) + { + foreach (var row in table.Rows) + { + var iniFile = new Wix.IniFile(); + + iniFile.Id = Convert.ToString(row[0]); + + var names = Common.GetNames(Convert.ToString(row[1])); + + if (null != names[0]) + { + if (null == names[1]) + { + iniFile.Name = names[0]; + } + else + { + iniFile.ShortName = names[0]; + } + } + + if (null != names[1]) + { + iniFile.Name = names[1]; + } + + if (null != row[2]) + { + iniFile.Directory = Convert.ToString(row[2]); + } + + iniFile.Section = Convert.ToString(row[3]); + + iniFile.Key = Convert.ToString(row[4]); + + iniFile.Value = Convert.ToString(row[5]); + + switch (Convert.ToInt32(row[6])) + { + case MsiInterop.MsidbIniFileActionAddLine: + iniFile.Action = Wix.IniFile.ActionType.addLine; + break; + case MsiInterop.MsidbIniFileActionCreateLine: + iniFile.Action = Wix.IniFile.ActionType.createLine; + break; + case MsiInterop.MsidbIniFileActionAddTag: + iniFile.Action = Wix.IniFile.ActionType.addTag; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[7])); + if (null != component) + { + component.AddChild(iniFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[7]), "Component")); + } + } + } + + /// + /// Decompile the IniLocator table. + /// + /// The table to decompile. + private void DecompileIniLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var iniFileSearch = new Wix.IniFileSearch(); + + iniFileSearch.Id = Convert.ToString(row[0]); + + var names = Common.GetNames(Convert.ToString(row[1])); + if (null != names[0] && null != names[1]) + { + iniFileSearch.ShortName = names[0]; + iniFileSearch.Name = names[1]; + } + else if (null != names[0]) + { + iniFileSearch.Name = names[0]; + } + + iniFileSearch.Section = Convert.ToString(row[2]); + + iniFileSearch.Key = Convert.ToString(row[3]); + + if (null != row[4]) + { + var field = Convert.ToInt32(row[4]); + + if (0 != field) + { + iniFileSearch.Field = field; + } + } + + if (null != row[5]) + { + switch (Convert.ToInt32(row[5])) + { + case MsiInterop.MsidbLocatorTypeDirectory: + iniFileSearch.Type = Wix.IniFileSearch.TypeType.directory; + break; + case MsiInterop.MsidbLocatorTypeFileName: + // this is the default value + break; + case MsiInterop.MsidbLocatorTypeRawValue: + iniFileSearch.Type = Wix.IniFileSearch.TypeType.raw; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); + break; + } + } + + this.core.IndexElement(row, iniFileSearch); + } + } + + /// + /// Decompile the IsolatedComponent table. + /// + /// The table to decompile. + private void DecompileIsolatedComponentTable(Table table) + { + foreach (var row in table.Rows) + { + var isolateComponent = new Wix.IsolateComponent(); + + isolateComponent.Shared = Convert.ToString(row[0]); + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + if (null != component) + { + component.AddChild(isolateComponent); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + } + } + + /// + /// Decompile the LaunchCondition table. + /// + /// The table to decompile. + private void DecompileLaunchConditionTable(Table table) + { + foreach (var row in table.Rows) + { + if (Common.DowngradePreventedCondition == Convert.ToString(row[0]) || Common.UpgradePreventedCondition == Convert.ToString(row[0])) + { + continue; // MajorUpgrade rows processed in FinalizeUpgradeTable + } + + var condition = new Wix.Condition(); + + condition.Content = Convert.ToString(row[0]); + + condition.Message = Convert.ToString(row[1]); + + this.core.RootElement.AddChild(condition); + } + } + + /// + /// Decompile the ListBox table. + /// + /// The table to decompile. + private void DecompileListBoxTable(Table table) + { + Wix.ListBox listBox = null; + var listBoxRows = new SortedList(); + + // sort the list boxes by their property and order + foreach (var row in table.Rows) + { + listBoxRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); + } + + foreach (Row row in listBoxRows.Values) + { + if (null == listBox || Convert.ToString(row[0]) != listBox.Property) + { + listBox = new Wix.ListBox(); + + listBox.Property = Convert.ToString(row[0]); + + this.core.UIElement.AddChild(listBox); + } + + var listItem = new Wix.ListItem(); + + listItem.Value = Convert.ToString(row[2]); + + if (null != row[3]) + { + listItem.Text = Convert.ToString(row[3]); + } + + listBox.AddChild(listItem); + } + } + + /// + /// Decompile the ListView table. + /// + /// The table to decompile. + private void DecompileListViewTable(Table table) + { + Wix.ListView listView = null; + var listViewRows = new SortedList(); + + // sort the list views by their property and order + foreach (var row in table.Rows) + { + listViewRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); + } + + foreach (Row row in listViewRows.Values) + { + if (null == listView || Convert.ToString(row[0]) != listView.Property) + { + listView = new Wix.ListView(); + + listView.Property = Convert.ToString(row[0]); + + this.core.UIElement.AddChild(listView); + } + + var listItem = new Wix.ListItem(); + + listItem.Value = Convert.ToString(row[2]); + + if (null != row[3]) + { + listItem.Text = Convert.ToString(row[3]); + } + + if (null != row[4]) + { + listItem.Icon = Convert.ToString(row[4]); + } + + listView.AddChild(listItem); + } + } + + /// + /// Decompile the LockPermissions table. + /// + /// The table to decompile. + private void DecompileLockPermissionsTable(Table table) + { + foreach (var row in table.Rows) + { + var permission = new Wix.Permission(); + string[] specialPermissions; + + switch (Convert.ToString(row[1])) + { + case "CreateFolder": + specialPermissions = Common.FolderPermissions; + break; + case "File": + specialPermissions = Common.FilePermissions; + break; + case "Registry": + specialPermissions = Common.RegistryPermissions; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); + return; + } + + var permissionBits = Convert.ToInt32(row[4]); + for (var i = 0; i < 32; i++) + { + if (0 != ((permissionBits >> i) & 1)) + { + string name = null; + + if (specialPermissions.Length > i) + { + name = specialPermissions[i]; + } + else if (16 > i && specialPermissions.Length <= i) + { + name = "SpecificRightsAll"; + } + else if (28 > i && Common.StandardPermissions.Length > (i - 16)) + { + name = Common.StandardPermissions[i - 16]; + } + else if (0 <= (i - 28) && Common.GenericPermissions.Length > (i - 28)) + { + name = Common.GenericPermissions[i - 28]; + } + + if (null == name) + { + this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i)); + } + else + { + switch (name) + { + case "Append": + permission.Append = Wix.YesNoType.yes; + break; + case "ChangePermission": + permission.ChangePermission = Wix.YesNoType.yes; + break; + case "CreateChild": + permission.CreateChild = Wix.YesNoType.yes; + break; + case "CreateFile": + permission.CreateFile = Wix.YesNoType.yes; + break; + case "CreateLink": + permission.CreateLink = Wix.YesNoType.yes; + break; + case "CreateSubkeys": + permission.CreateSubkeys = Wix.YesNoType.yes; + break; + case "Delete": + permission.Delete = Wix.YesNoType.yes; + break; + case "DeleteChild": + permission.DeleteChild = Wix.YesNoType.yes; + break; + case "EnumerateSubkeys": + permission.EnumerateSubkeys = Wix.YesNoType.yes; + break; + case "Execute": + permission.Execute = Wix.YesNoType.yes; + break; + case "FileAllRights": + permission.FileAllRights = Wix.YesNoType.yes; + break; + case "GenericAll": + permission.GenericAll = Wix.YesNoType.yes; + break; + case "GenericExecute": + permission.GenericExecute = Wix.YesNoType.yes; + break; + case "GenericRead": + permission.GenericRead = Wix.YesNoType.yes; + break; + case "GenericWrite": + permission.GenericWrite = Wix.YesNoType.yes; + break; + case "Notify": + permission.Notify = Wix.YesNoType.yes; + break; + case "Read": + permission.Read = Wix.YesNoType.yes; + break; + case "ReadAttributes": + permission.ReadAttributes = Wix.YesNoType.yes; + break; + case "ReadExtendedAttributes": + permission.ReadExtendedAttributes = Wix.YesNoType.yes; + break; + case "ReadPermission": + permission.ReadPermission = Wix.YesNoType.yes; + break; + case "SpecificRightsAll": + permission.SpecificRightsAll = Wix.YesNoType.yes; + break; + case "Synchronize": + permission.Synchronize = Wix.YesNoType.yes; + break; + case "TakeOwnership": + permission.TakeOwnership = Wix.YesNoType.yes; + break; + case "Traverse": + permission.Traverse = Wix.YesNoType.yes; + break; + case "Write": + permission.Write = Wix.YesNoType.yes; + break; + case "WriteAttributes": + permission.WriteAttributes = Wix.YesNoType.yes; + break; + case "WriteExtendedAttributes": + permission.WriteExtendedAttributes = Wix.YesNoType.yes; + break; + default: + throw new InvalidOperationException($"Unknown permission attribute '{name}'."); + } + } + } + } + + if (null != row[2]) + { + permission.Domain = Convert.ToString(row[2]); + } + + permission.User = Convert.ToString(row[3]); + + this.core.IndexElement(row, permission); + } + } + + /// + /// Decompile the Media table. + /// + /// The table to decompile. + private void DecompileMediaTable(Table table) + { + foreach (MediaRow mediaRow in table.Rows) + { + var media = new Wix.Media(); + + media.Id = Convert.ToString(mediaRow.DiskId); + + if (null != mediaRow.DiskPrompt) + { + media.DiskPrompt = mediaRow.DiskPrompt; + } + + if (null != mediaRow.Cabinet) + { + var cabinet = mediaRow.Cabinet; + + if (cabinet.StartsWith("#", StringComparison.Ordinal)) + { + media.EmbedCab = Wix.YesNoType.yes; + cabinet = cabinet.Substring(1); + } + + media.Cabinet = cabinet; + } + + if (null != mediaRow.VolumeLabel) + { + media.VolumeLabel = mediaRow.VolumeLabel; + } + + this.core.RootElement.AddChild(media); + this.core.IndexElement(mediaRow, media); + } + } + + /// + /// Decompile the MIME table. + /// + /// The table to decompile. + private void DecompileMIMETable(Table table) + { + foreach (var row in table.Rows) + { + var mime = new Wix.MIME(); + + mime.ContentType = Convert.ToString(row[0]); + + if (null != row[2]) + { + mime.Class = Convert.ToString(row[2]); + } + + this.core.IndexElement(row, mime); + } + } + + /// + /// Decompile the ModuleConfiguration table. + /// + /// The table to decompile. + private void DecompileModuleConfigurationTable(Table table) + { + foreach (var row in table.Rows) + { + var configuration = new Wix.Configuration(); + + configuration.Name = Convert.ToString(row[0]); + + switch (Convert.ToInt32(row[1])) + { + case 0: + configuration.Format = Wix.Configuration.FormatType.Text; + break; + case 1: + configuration.Format = Wix.Configuration.FormatType.Key; + break; + case 2: + configuration.Format = Wix.Configuration.FormatType.Integer; + break; + case 3: + configuration.Format = Wix.Configuration.FormatType.Bitfield; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + if (null != row[2]) + { + configuration.Type = Convert.ToString(row[2]); + } + + if (null != row[3]) + { + configuration.ContextData = Convert.ToString(row[3]); + } + + if (null != row[4]) + { + configuration.DefaultValue = Convert.ToString(row[4]); + } + + if (null != row[5]) + { + var attributes = Convert.ToInt32(row[5]); + + if (MsiInterop.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & MsiInterop.MsidbMsmConfigurableOptionKeyNoOrphan)) + { + configuration.KeyNoOrphan = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbMsmConfigurableOptionNonNullable == (attributes & MsiInterop.MsidbMsmConfigurableOptionNonNullable)) + { + configuration.NonNullable = Wix.YesNoType.yes; + } + + if (3 < attributes) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); + } + } + + if (null != row[6]) + { + configuration.DisplayName = Convert.ToString(row[6]); + } + + if (null != row[7]) + { + configuration.Description = Convert.ToString(row[7]); + } + + if (null != row[8]) + { + configuration.HelpLocation = Convert.ToString(row[8]); + } + + if (null != row[9]) + { + configuration.HelpKeyword = Convert.ToString(row[9]); + } + + this.core.RootElement.AddChild(configuration); + } + } + + /// + /// Decompile the ModuleDependency table. + /// + /// The table to decompile. + private void DecompileModuleDependencyTable(Table table) + { + foreach (var row in table.Rows) + { + var dependency = new Wix.Dependency(); + + dependency.RequiredId = Convert.ToString(row[2]); + + dependency.RequiredLanguage = Convert.ToInt32(row[3], CultureInfo.InvariantCulture); + + if (null != row[4]) + { + dependency.RequiredVersion = Convert.ToString(row[4]); + } + + this.core.RootElement.AddChild(dependency); + } + } + + /// + /// Decompile the ModuleExclusion table. + /// + /// The table to decompile. + private void DecompileModuleExclusionTable(Table table) + { + foreach (var row in table.Rows) + { + var exclusion = new Wix.Exclusion(); + + exclusion.ExcludedId = Convert.ToString(row[2]); + + var excludedLanguage = Convert.ToInt32(Convert.ToString(row[3]), CultureInfo.InvariantCulture); + if (0 < excludedLanguage) + { + exclusion.ExcludeLanguage = excludedLanguage; + } + else if (0 > excludedLanguage) + { + exclusion.ExcludeExceptLanguage = -excludedLanguage; + } + + if (null != row[4]) + { + exclusion.ExcludedMinVersion = Convert.ToString(row[4]); + } + + if (null != row[5]) + { + exclusion.ExcludedMinVersion = Convert.ToString(row[5]); + } + + this.core.RootElement.AddChild(exclusion); + } + } + + /// + /// Decompile the ModuleIgnoreTable table. + /// + /// The table to decompile. + private void DecompileModuleIgnoreTableTable(Table table) + { + foreach (var row in table.Rows) + { + var tableName = Convert.ToString(row[0]); + + // the linker automatically adds a ModuleIgnoreTable row for some tables + if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName) + { + var ignoreTable = new Wix.IgnoreTable(); + + ignoreTable.Id = tableName; + + this.core.RootElement.AddChild(ignoreTable); + } + } + } + + /// + /// Decompile the ModuleSignature table. + /// + /// The table to decompile. + private void DecompileModuleSignatureTable(Table table) + { + if (1 == table.Rows.Count) + { + var row = table.Rows[0]; + + var module = (Wix.Module)this.core.RootElement; + + module.Id = Convert.ToString(row[0]); + + // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability) + module.Language = Convert.ToString(row[1], CultureInfo.InvariantCulture); + + module.Version = Convert.ToString(row[2]); + } + else + { + // TODO: warn + } + } + + /// + /// Decompile the ModuleSubstitution table. + /// + /// The table to decompile. + private void DecompileModuleSubstitutionTable(Table table) + { + foreach (var row in table.Rows) + { + var substitution = new Wix.Substitution(); + + substitution.Table = Convert.ToString(row[0]); + + substitution.Row = Convert.ToString(row[1]); + + substitution.Column = Convert.ToString(row[2]); + + if (null != row[3]) + { + substitution.Value = Convert.ToString(row[3]); + } + + this.core.RootElement.AddChild(substitution); + } + } + + /// + /// Decompile the MoveFile table. + /// + /// The table to decompile. + private void DecompileMoveFileTable(Table table) + { + foreach (var row in table.Rows) + { + var copyFile = new Wix.CopyFile(); + + copyFile.Id = Convert.ToString(row[0]); + + if (null != row[2]) + { + copyFile.SourceName = Convert.ToString(row[2]); + } + + if (null != row[3]) + { + var names = Common.GetNames(Convert.ToString(row[3])); + if (null != names[0] && null != names[1]) + { + copyFile.DestinationShortName = names[0]; + copyFile.DestinationName = names[1]; + } + else if (null != names[0]) + { + copyFile.DestinationName = names[0]; + } + } + + // source/destination directory/property is set in FinalizeDuplicateMoveFileTables + + switch (Convert.ToInt32(row[6])) + { + case 0: + break; + case MsiInterop.MsidbMoveFileOptionsMove: + copyFile.Delete = Wix.YesNoType.yes; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + if (null != component) + { + component.AddChild(copyFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + this.core.IndexElement(row, copyFile); + } + } + + /// + /// Decompile the MsiDigitalCertificate table. + /// + /// The table to decompile. + private void DecompileMsiDigitalCertificateTable(Table table) + { + foreach (var row in table.Rows) + { + var digitalCertificate = new Wix.DigitalCertificate(); + + digitalCertificate.Id = Convert.ToString(row[0]); + + digitalCertificate.SourceFile = Convert.ToString(row[1]); + + this.core.IndexElement(row, digitalCertificate); + } + } + + /// + /// Decompile the MsiDigitalSignature table. + /// + /// The table to decompile. + private void DecompileMsiDigitalSignatureTable(Table table) + { + foreach (var row in table.Rows) + { + var digitalSignature = new Wix.DigitalSignature(); + + if (null != row[3]) + { + digitalSignature.SourceFile = Convert.ToString(row[3]); + } + + var digitalCertificate = (Wix.DigitalCertificate)this.core.GetIndexedElement("MsiDigitalCertificate", Convert.ToString(row[2])); + if (null != digitalCertificate) + { + digitalSignature.AddChild(digitalCertificate); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", Convert.ToString(row[2]), "MsiDigitalCertificate")); + } + + var parentElement = (Wix.IParentElement)this.core.GetIndexedElement(Convert.ToString(row[0]), Convert.ToString(row[1])); + if (null != parentElement) + { + parentElement.AddChild(digitalSignature); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", Convert.ToString(row[1]), Convert.ToString(row[0]))); + } + } + } + + /// + /// Decompile the MsiEmbeddedChainer table. + /// + /// The table to decompile. + private void DecompileMsiEmbeddedChainerTable(Table table) + { + foreach (var row in table.Rows) + { + var embeddedChainer = new Wix.EmbeddedChainer(); + + embeddedChainer.Id = Convert.ToString(row[0]); + + embeddedChainer.Content = Convert.ToString(row[1]); + + if (null != row[2]) + { + embeddedChainer.CommandLine = Convert.ToString(row[2]); + } + + switch (Convert.ToInt32(row[4])) + { + case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeBinaryData: + embeddedChainer.BinarySource = Convert.ToString(row[3]); + break; + case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeSourceFile: + embeddedChainer.FileSource = Convert.ToString(row[3]); + break; + case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeProperty: + embeddedChainer.PropertySource = Convert.ToString(row[3]); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + this.core.RootElement.AddChild(embeddedChainer); + } + } + + /// + /// Decompile the MsiEmbeddedUI table. + /// + /// The table to decompile. + private void DecompileMsiEmbeddedUITable(Table table) + { + var embeddedUI = new Wix.EmbeddedUI(); + var foundEmbeddedUI = false; + var foundEmbeddedResources = false; + + foreach (var row in table.Rows) + { + var attributes = Convert.ToInt32(row[2]); + + if (MsiInterop.MsidbEmbeddedUI == (attributes & MsiInterop.MsidbEmbeddedUI)) + { + if (foundEmbeddedUI) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + } + else + { + embeddedUI.Id = Convert.ToString(row[0]); + embeddedUI.Name = Convert.ToString(row[1]); + + var messageFilter = Convert.ToInt32(row[3]); + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_FATALEXIT)) + { + embeddedUI.IgnoreFatalExit = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ERROR)) + { + embeddedUI.IgnoreError = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_WARNING)) + { + embeddedUI.IgnoreWarning = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_USER)) + { + embeddedUI.IgnoreUser = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INFO)) + { + embeddedUI.IgnoreInfo = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_FILESINUSE)) + { + embeddedUI.IgnoreFilesInUse = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_RESOLVESOURCE)) + { + embeddedUI.IgnoreResolveSource = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_OUTOFDISKSPACE)) + { + embeddedUI.IgnoreOutOfDiskSpace = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ACTIONSTART)) + { + embeddedUI.IgnoreActionStart = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ACTIONDATA)) + { + embeddedUI.IgnoreActionData = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_PROGRESS)) + { + embeddedUI.IgnoreProgress = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_COMMONDATA)) + { + embeddedUI.IgnoreCommonData = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INITIALIZE)) + { + embeddedUI.IgnoreInitialize = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_TERMINATE)) + { + embeddedUI.IgnoreTerminate = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_SHOWDIALOG)) + { + embeddedUI.IgnoreShowDialog = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_RMFILESINUSE)) + { + embeddedUI.IgnoreRMFilesInUse = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INSTALLSTART)) + { + embeddedUI.IgnoreInstallStart = Wix.YesNoType.yes; + } + + if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INSTALLEND)) + { + embeddedUI.IgnoreInstallEnd = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbEmbeddedHandlesBasic == (attributes & MsiInterop.MsidbEmbeddedHandlesBasic)) + { + embeddedUI.SupportBasicUI = Wix.YesNoType.yes; + } + + embeddedUI.SourceFile = Convert.ToString(row[4]); + + this.core.UIElement.AddChild(embeddedUI); + foundEmbeddedUI = true; + } + } + else + { + var embeddedResource = new Wix.EmbeddedUIResource(); + + embeddedResource.Id = Convert.ToString(row[0]); + embeddedResource.Name = Convert.ToString(row[1]); + embeddedResource.SourceFile = Convert.ToString(row[4]); + + embeddedUI.AddChild(embeddedResource); + foundEmbeddedResources = true; + } + } + + if (!foundEmbeddedUI && foundEmbeddedResources) + { + // TODO: warn + } + } + + /// + /// Decompile the MsiLockPermissionsEx table. + /// + /// The table to decompile. + private void DecompileMsiLockPermissionsExTable(Table table) + { + foreach (var row in table.Rows) + { + var permissionEx = new Wix.PermissionEx(); + permissionEx.Id = Convert.ToString(row[0]); + permissionEx.Sddl = Convert.ToString(row[3]); + + if (null != row[4]) + { + var condition = new Wix.Condition(); + condition.Content = Convert.ToString(row[4]); + permissionEx.AddChild(condition); + } + + switch (Convert.ToString(row[2])) + { + case "CreateFolder": + case "File": + case "Registry": + case "ServiceInstall": + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); + return; + } + + this.core.IndexElement(row, permissionEx); + } + } + + /// + /// Decompile the MsiPackageCertificate table. + /// + /// The table to decompile. + private void DecompileMsiPackageCertificateTable(Table table) + { + if (0 < table.Rows.Count) + { + var packageCertificates = new Wix.PackageCertificates(); + this.core.RootElement.AddChild(packageCertificates); + this.AddCertificates(table, packageCertificates); + } + } + + /// + /// Decompile the MsiPatchCertificate table. + /// + /// The table to decompile. + private void DecompileMsiPatchCertificateTable(Table table) + { + if (0 < table.Rows.Count) + { + var patchCertificates = new Wix.PatchCertificates(); + this.core.RootElement.AddChild(patchCertificates); + this.AddCertificates(table, patchCertificates); + } + } + + /// + /// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table. + /// + /// The table being decompiled. + /// DigitalCertificate parent + private void AddCertificates(Table table, Wix.IParentElement parent) + { + foreach (var row in table.Rows) + { + var digitalCertificate = (Wix.DigitalCertificate)this.core.GetIndexedElement("MsiDigitalCertificate", Convert.ToString(row[1])); + + if (null != digitalCertificate) + { + parent.AddChild(digitalCertificate); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", Convert.ToString(row[1]), "MsiDigitalCertificate")); + } + } + } + + /// + /// Decompile the MsiShortcutProperty table. + /// + /// The table to decompile. + private void DecompileMsiShortcutPropertyTable(Table table) + { + foreach (var row in table.Rows) + { + var property = new Wix.ShortcutProperty(); + property.Id = Convert.ToString(row[0]); + property.Key = Convert.ToString(row[2]); + property.Value = Convert.ToString(row[3]); + + var shortcut = (Wix.Shortcut)this.core.GetIndexedElement("Shortcut", Convert.ToString(row[1])); + if (null != shortcut) + { + shortcut.AddChild(property); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Shortcut_", Convert.ToString(row[1]), "Shortcut")); + } + } + } + + /// + /// Decompile the ODBCAttribute table. + /// + /// The table to decompile. + private void DecompileODBCAttributeTable(Table table) + { + foreach (var row in table.Rows) + { + var property = new Wix.Property(); + + property.Id = Convert.ToString(row[1]); + + if (null != row[2]) + { + property.Value = Convert.ToString(row[2]); + } + + var odbcDriver = (Wix.ODBCDriver)this.core.GetIndexedElement("ODBCDriver", Convert.ToString(row[0])); + if (null != odbcDriver) + { + odbcDriver.AddChild(property); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Driver_", Convert.ToString(row[0]), "ODBCDriver")); + } + } + } + + /// + /// Decompile the ODBCDataSource table. + /// + /// The table to decompile. + private void DecompileODBCDataSourceTable(Table table) + { + foreach (var row in table.Rows) + { + var odbcDataSource = new Wix.ODBCDataSource(); + + odbcDataSource.Id = Convert.ToString(row[0]); + + odbcDataSource.Name = Convert.ToString(row[2]); + + odbcDataSource.DriverName = Convert.ToString(row[3]); + + switch (Convert.ToInt32(row[4])) + { + case MsiInterop.MsidbODBCDataSourceRegistrationPerMachine: + odbcDataSource.Registration = Wix.ODBCDataSource.RegistrationType.machine; + break; + case MsiInterop.MsidbODBCDataSourceRegistrationPerUser: + odbcDataSource.Registration = Wix.ODBCDataSource.RegistrationType.user; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + this.core.IndexElement(row, odbcDataSource); + } + } + + /// + /// Decompile the ODBCDriver table. + /// + /// The table to decompile. + private void DecompileODBCDriverTable(Table table) + { + foreach (var row in table.Rows) + { + var odbcDriver = new Wix.ODBCDriver(); + + odbcDriver.Id = Convert.ToString(row[0]); + + odbcDriver.Name = Convert.ToString(row[2]); + + odbcDriver.File = Convert.ToString(row[3]); + + if (null != row[4]) + { + odbcDriver.SetupFile = Convert.ToString(row[4]); + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + if (null != component) + { + component.AddChild(odbcDriver); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + this.core.IndexElement(row, odbcDriver); + } + } + + /// + /// Decompile the ODBCSourceAttribute table. + /// + /// The table to decompile. + private void DecompileODBCSourceAttributeTable(Table table) + { + foreach (var row in table.Rows) + { + var property = new Wix.Property(); + + property.Id = Convert.ToString(row[1]); + + if (null != row[2]) + { + property.Value = Convert.ToString(row[2]); + } + + var odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement("ODBCDataSource", Convert.ToString(row[0])); + if (null != odbcDataSource) + { + odbcDataSource.AddChild(property); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DataSource_", Convert.ToString(row[0]), "ODBCDataSource")); + } + } + } + + /// + /// Decompile the ODBCTranslator table. + /// + /// The table to decompile. + private void DecompileODBCTranslatorTable(Table table) + { + foreach (var row in table.Rows) + { + var odbcTranslator = new Wix.ODBCTranslator(); + + odbcTranslator.Id = Convert.ToString(row[0]); + + odbcTranslator.Name = Convert.ToString(row[2]); + + odbcTranslator.File = Convert.ToString(row[3]); + + if (null != row[4]) + { + odbcTranslator.SetupFile = Convert.ToString(row[4]); + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + if (null != component) + { + component.AddChild(odbcTranslator); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + } + } + + /// + /// Decompile the PatchMetadata table. + /// + /// The table to decompile. + private void DecompilePatchMetadataTable(Table table) + { + if (0 < table.Rows.Count) + { + var patchMetadata = new Wix.PatchMetadata(); + + foreach (var row in table.Rows) + { + var value = Convert.ToString(row[2]); + + switch (Convert.ToString(row[1])) + { + case "AllowRemoval": + if ("1" == value) + { + patchMetadata.AllowRemoval = Wix.YesNoType.yes; + } + break; + case "Classification": + if (null != value) + { + patchMetadata.Classification = value; + } + break; + case "CreationTimeUTC": + if (null != value) + { + patchMetadata.CreationTimeUTC = value; + } + break; + case "Description": + if (null != value) + { + patchMetadata.Description = value; + } + break; + case "DisplayName": + if (null != value) + { + patchMetadata.DisplayName = value; + } + break; + case "ManufacturerName": + if (null != value) + { + patchMetadata.ManufacturerName = value; + } + break; + case "MinorUpdateTargetRTM": + if (null != value) + { + patchMetadata.MinorUpdateTargetRTM = value; + } + break; + case "MoreInfoURL": + if (null != value) + { + patchMetadata.MoreInfoURL = value; + } + break; + case "OptimizeCA": + var optimizeCustomActions = new Wix.OptimizeCustomActions(); + var optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture); + if (0 != (Convert.ToInt32(OptimizeCA.SkipAssignment) & optimizeCA)) + { + optimizeCustomActions.SkipAssignment = Wix.YesNoType.yes; + } + + if (0 != (Convert.ToInt32(OptimizeCA.SkipImmediate) & optimizeCA)) + { + optimizeCustomActions.SkipImmediate = Wix.YesNoType.yes; + } + + if (0 != (Convert.ToInt32(OptimizeCA.SkipDeferred) & optimizeCA)) + { + optimizeCustomActions.SkipDeferred = Wix.YesNoType.yes; + } + + patchMetadata.AddChild(optimizeCustomActions); + break; + case "OptimizedInstallMode": + if ("1" == value) + { + patchMetadata.OptimizedInstallMode = Wix.YesNoType.yes; + } + break; + case "TargetProductName": + if (null != value) + { + patchMetadata.TargetProductName = value; + } + break; + default: + var customProperty = new Wix.CustomProperty(); + + if (null != row[0]) + { + customProperty.Company = Convert.ToString(row[0]); + } + + customProperty.Property = Convert.ToString(row[1]); + + if (null != row[2]) + { + customProperty.Value = Convert.ToString(row[2]); + } + + patchMetadata.AddChild(customProperty); + break; + } + } + + this.core.RootElement.AddChild(patchMetadata); + } + } + + /// + /// Decompile the PatchSequence table. + /// + /// The table to decompile. + private void DecompilePatchSequenceTable(Table table) + { + foreach (var row in table.Rows) + { + var patchSequence = new Wix.PatchSequence(); + + patchSequence.PatchFamily = Convert.ToString(row[0]); + + if (null != row[1]) + { + try + { + var guid = new Guid(Convert.ToString(row[1])); + + patchSequence.ProductCode = Convert.ToString(row[1]); + } + catch // non-guid value + { + patchSequence.TargetImage = Convert.ToString(row[1]); + } + } + + if (null != row[2]) + { + patchSequence.Sequence = Convert.ToString(row[2]); + } + + if (null != row[3] && 0x1 == Convert.ToInt32(row[3])) + { + patchSequence.Supersede = Wix.YesNoType.yes; + } + + this.core.RootElement.AddChild(patchSequence); + } + } + + /// + /// Decompile the ProgId table. + /// + /// The table to decompile. + private void DecompileProgIdTable(Table table) + { + foreach (var row in table.Rows) + { + var progId = new Wix.ProgId(); + + progId.Advertise = Wix.YesNoType.yes; + + progId.Id = Convert.ToString(row[0]); + + if (null != row[3]) + { + progId.Description = Convert.ToString(row[3]); + } + + if (null != row[4]) + { + progId.Icon = Convert.ToString(row[4]); + } + + if (null != row[5]) + { + progId.IconIndex = Convert.ToInt32(row[5]); + } + + this.core.IndexElement(row, progId); + } + + // nest the ProgIds + foreach (var row in table.Rows) + { + var progId = (Wix.ProgId)this.core.GetIndexedElement(row); + + if (null != row[1]) + { + var parentProgId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[1])); + + if (null != parentProgId) + { + parentProgId.AddChild(progId); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Parent", Convert.ToString(row[1]), "ProgId")); + } + } + else if (null != row[2]) + { + // nesting is handled in FinalizeProgIdTable + } + else + { + // TODO: warn for orphaned ProgId + } + } + } + + /// + /// Decompile the Properties table. + /// + /// The table to decompile. + private void DecompilePropertiesTable(Table table) + { + var patchCreation = (Wix.PatchCreation)this.core.RootElement; + + foreach (var row in table.Rows) + { + var name = Convert.ToString(row[0]); + var value = Convert.ToString(row[1]); + + switch (name) + { + case "AllowProductCodeMismatches": + if ("1" == value) + { + patchCreation.AllowProductCodeMismatches = Wix.YesNoType.yes; + } + break; + case "AllowProductVersionMajorMismatches": + if ("1" == value) + { + patchCreation.AllowMajorVersionMismatches = Wix.YesNoType.yes; + } + break; + case "ApiPatchingSymbolFlags": + if (null != value) + { + try + { + // remove the leading "0x" if its present + if (value.StartsWith("0x", StringComparison.Ordinal)) + { + value = value.Substring(2); + } + + patchCreation.SymbolFlags = Convert.ToInt32(value, 16); + } + catch + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + } + } + break; + case "DontRemoveTempFolderWhenFinished": + if ("1" == value) + { + patchCreation.CleanWorkingFolder = Wix.YesNoType.no; + } + break; + case "IncludeWholeFilesOnly": + if ("1" == value) + { + patchCreation.WholeFilesOnly = Wix.YesNoType.yes; + } + break; + case "ListOfPatchGUIDsToReplace": + if (null != value) + { + var guidRegex = 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}\}"); + var guidMatches = guidRegex.Matches(value); + + foreach (Match guidMatch in guidMatches) + { + var replacePatch = new Wix.ReplacePatch(); + + replacePatch.Id = guidMatch.Value; + + this.core.RootElement.AddChild(replacePatch); + } + } + break; + case "ListOfTargetProductCodes": + if (null != value) + { + var targetProductCodes = value.Split(';'); + + foreach (var targetProductCodeString in targetProductCodes) + { + var targetProductCode = new Wix.TargetProductCode(); + + targetProductCode.Id = targetProductCodeString; + + this.core.RootElement.AddChild(targetProductCode); + } + } + break; + case "PatchGUID": + patchCreation.Id = value; + break; + case "PatchSourceList": + patchCreation.SourceList = value; + break; + case "PatchOutputPath": + patchCreation.OutputPath = value; + break; + default: + var patchProperty = new Wix.PatchProperty(); + + patchProperty.Name = name; + + patchProperty.Value = value; + + this.core.RootElement.AddChild(patchProperty); + break; + } + } + } + + /// + /// Decompile the Property table. + /// + /// The table to decompile. + private void DecompilePropertyTable(Table table) + { + foreach (var row in table.Rows) + { + var id = Convert.ToString(row[0]); + var value = Convert.ToString(row[1]); + + if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id) + { + if (0 < value.Length) + { + foreach (var propertyId in value.Split(';')) + { + if (Common.DowngradeDetectedProperty == propertyId || Common.UpgradeDetectedProperty == propertyId) + { + continue; + } + + var property = propertyId; + var suppressModulularization = false; + if (OutputType.Module == this.OutputType) + { + if (propertyId.EndsWith(this.modularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal)) + { + property = propertyId.Substring(0, propertyId.Length - this.modularizationGuid.Length + 1); + } + else + { + suppressModulularization = true; + } + } + + var specialProperty = this.EnsureProperty(property); + if (suppressModulularization) + { + specialProperty.SuppressModularization = Wix.YesNoType.yes; + } + + switch (id) + { + case "AdminProperties": + specialProperty.Admin = Wix.YesNoType.yes; + break; + case "MsiHiddenProperties": + specialProperty.Hidden = Wix.YesNoType.yes; + break; + case "SecureCustomProperties": + specialProperty.Secure = Wix.YesNoType.yes; + break; + } + } + } + + continue; + } + else if (OutputType.Product == this.OutputType) + { + var product = (Wix.Product)this.core.RootElement; + + switch (id) + { + case "Manufacturer": + product.Manufacturer = value; + continue; + case "ProductCode": + product.Id = value.ToUpper(CultureInfo.InvariantCulture); + continue; + case "ProductLanguage": + product.Language = value; + continue; + case "ProductName": + product.Name = value; + continue; + case "ProductVersion": + product.Version = value; + continue; + case "UpgradeCode": + product.UpgradeCode = value; + continue; + } + } + + if (!this.SuppressUI || "ErrorDialog" != id) + { + var property = this.EnsureProperty(id); + + property.Value = value; + } + } + } + + /// + /// Decompile the PublishComponent table. + /// + /// The table to decompile. + private void DecompilePublishComponentTable(Table table) + { + foreach (var row in table.Rows) + { + var category = new Wix.Category(); + + category.Id = Convert.ToString(row[0]); + + category.Qualifier = Convert.ToString(row[1]); + + if (null != row[3]) + { + category.AppData = Convert.ToString(row[3]); + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2])); + if (null != component) + { + component.AddChild(category); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[2]), "Component")); + } + } + } + + /// + /// Decompile the RadioButton table. + /// + /// The table to decompile. + private void DecompileRadioButtonTable(Table table) + { + var radioButtons = new SortedList(); + var radioButtonGroups = new Hashtable(); + + foreach (var row in table.Rows) + { + var radioButton = new Wix.RadioButton(); + + radioButton.Value = Convert.ToString(row[2]); + + radioButton.X = Convert.ToString(row[3], CultureInfo.InvariantCulture); + + radioButton.Y = Convert.ToString(row[4], CultureInfo.InvariantCulture); + + radioButton.Width = Convert.ToString(row[5], CultureInfo.InvariantCulture); + + radioButton.Height = Convert.ToString(row[6], CultureInfo.InvariantCulture); + + if (null != row[7]) + { + radioButton.Text = Convert.ToString(row[7]); + } + + if (null != row[8]) + { + var help = (Convert.ToString(row[8])).Split('|'); + + if (2 == help.Length) + { + if (0 < help[0].Length) + { + radioButton.ToolTip = help[0]; + } + + if (0 < help[1].Length) + { + radioButton.Help = help[1]; + } + } + } + + radioButtons.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[1]), row); + this.core.IndexElement(row, radioButton); + } + + // nest the radio buttons + foreach (Row row in radioButtons.Values) + { + var radioButton = (Wix.RadioButton)this.core.GetIndexedElement(row); + var radioButtonGroup = (Wix.RadioButtonGroup)radioButtonGroups[Convert.ToString(row[0])]; + + if (null == radioButtonGroup) + { + radioButtonGroup = new Wix.RadioButtonGroup(); + + radioButtonGroup.Property = Convert.ToString(row[0]); + + this.core.UIElement.AddChild(radioButtonGroup); + radioButtonGroups.Add(Convert.ToString(row[0]), radioButtonGroup); + } + + radioButtonGroup.AddChild(radioButton); + } + } + + /// + /// Decompile the Registry table. + /// + /// The table to decompile. + private void DecompileRegistryTable(Table table) + { + foreach (var row in table.Rows) + { + if (("-" == Convert.ToString(row[3]) || "+" == Convert.ToString(row[3]) || "*" == Convert.ToString(row[3])) && null == row[4]) + { + var registryKey = new Wix.RegistryKey(); + + registryKey.Id = Convert.ToString(row[0]); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + registryKey.Root = registryRootType; + } + + registryKey.Key = Convert.ToString(row[2]); + + switch (Convert.ToString(row[3])) + { + case "+": + registryKey.ForceCreateOnInstall = Wix.YesNoType.yes; + break; + case "-": + registryKey.ForceDeleteOnUninstall = Wix.YesNoType.yes; + break; + case "*": + registryKey.ForceDeleteOnUninstall = Wix.YesNoType.yes; + registryKey.ForceCreateOnInstall = Wix.YesNoType.yes; + break; + } + + this.core.IndexElement(row, registryKey); + } + else + { + var registryValue = new Wix.RegistryValue(); + + registryValue.Id = Convert.ToString(row[0]); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + registryValue.Root = registryRootType; + } + + registryValue.Key = Convert.ToString(row[2]); + + if (null != row[3]) + { + registryValue.Name = Convert.ToString(row[3]); + } + + if (null != row[4]) + { + var value = Convert.ToString(row[4]); + + if (value.StartsWith("#x", StringComparison.Ordinal)) + { + registryValue.Type = Wix.RegistryValue.TypeType.binary; + registryValue.Value = value.Substring(2); + } + else if (value.StartsWith("#%", StringComparison.Ordinal)) + { + registryValue.Type = Wix.RegistryValue.TypeType.expandable; + registryValue.Value = value.Substring(2); + } + else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal)) + { + registryValue.Type = Wix.RegistryValue.TypeType.integer; + registryValue.Value = value.Substring(1); + } + else + { + if (value.StartsWith("##", StringComparison.Ordinal)) + { + value = value.Substring(1); + } + + if (0 <= value.IndexOf("[~]", StringComparison.Ordinal)) + { + registryValue.Type = Wix.RegistryValue.TypeType.multiString; + + if ("[~]" == value) + { + value = String.Empty; + } + else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal)) + { + value = value.Substring(3, value.Length - 6); + } + else if (value.StartsWith("[~]", StringComparison.Ordinal)) + { + registryValue.Action = Wix.RegistryValue.ActionType.append; + value = value.Substring(3); + } + else if (value.EndsWith("[~]", StringComparison.Ordinal)) + { + registryValue.Action = Wix.RegistryValue.ActionType.prepend; + value = value.Substring(0, value.Length - 3); + } + + var multiValues = NullSplitter.Split(value); + foreach (var multiValue in multiValues) + { + var multiStringValue = new Wix.MultiStringValue(); + + multiStringValue.Content = multiValue; + + registryValue.AddChild(multiStringValue); + } + } + else + { + registryValue.Type = Wix.RegistryValue.TypeType.@string; + registryValue.Value = value; + } + } + } + else + { + registryValue.Type = Wix.RegistryValue.TypeType.@string; + registryValue.Value = String.Empty; + } + + this.core.IndexElement(row, registryValue); + } + } + } + + /// + /// Decompile the RegLocator table. + /// + /// The table to decompile. + private void DecompileRegLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var registrySearch = new Wix.RegistrySearch(); + + registrySearch.Id = Convert.ToString(row[0]); + + switch (Convert.ToInt32(row[1])) + { + case MsiInterop.MsidbRegistryRootClassesRoot: + registrySearch.Root = Wix.RegistrySearch.RootType.HKCR; + break; + case MsiInterop.MsidbRegistryRootCurrentUser: + registrySearch.Root = Wix.RegistrySearch.RootType.HKCU; + break; + case MsiInterop.MsidbRegistryRootLocalMachine: + registrySearch.Root = Wix.RegistrySearch.RootType.HKLM; + break; + case MsiInterop.MsidbRegistryRootUsers: + registrySearch.Root = Wix.RegistrySearch.RootType.HKU; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + registrySearch.Key = Convert.ToString(row[2]); + + if (null != row[3]) + { + registrySearch.Name = Convert.ToString(row[3]); + } + + if (null == row[4]) + { + registrySearch.Type = Wix.RegistrySearch.TypeType.file; + } + else + { + var type = Convert.ToInt32(row[4]); + + if (MsiInterop.MsidbLocatorType64bit == (type & MsiInterop.MsidbLocatorType64bit)) + { + registrySearch.Win64 = Wix.YesNoType.yes; + type &= ~MsiInterop.MsidbLocatorType64bit; + } + + switch (type) + { + case MsiInterop.MsidbLocatorTypeDirectory: + registrySearch.Type = Wix.RegistrySearch.TypeType.directory; + break; + case MsiInterop.MsidbLocatorTypeFileName: + registrySearch.Type = Wix.RegistrySearch.TypeType.file; + break; + case MsiInterop.MsidbLocatorTypeRawValue: + registrySearch.Type = Wix.RegistrySearch.TypeType.raw; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + } + + this.core.IndexElement(row, registrySearch); + } + } + + /// + /// Decompile the RemoveFile table. + /// + /// The table to decompile. + private void DecompileRemoveFileTable(Table table) + { + foreach (var row in table.Rows) + { + if (null == row[2]) + { + var removeFolder = new Wix.RemoveFolder(); + + removeFolder.Id = Convert.ToString(row[0]); + + // directory/property is set in FinalizeDecompile + + switch (Convert.ToInt32(row[4])) + { + case MsiInterop.MsidbRemoveFileInstallModeOnInstall: + removeFolder.On = Wix.InstallUninstallType.install; + break; + case MsiInterop.MsidbRemoveFileInstallModeOnRemove: + removeFolder.On = Wix.InstallUninstallType.uninstall; + break; + case MsiInterop.MsidbRemoveFileInstallModeOnBoth: + removeFolder.On = Wix.InstallUninstallType.both; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + if (null != component) + { + component.AddChild(removeFolder); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + this.core.IndexElement(row, removeFolder); + } + else + { + var removeFile = new Wix.RemoveFile(); + + removeFile.Id = Convert.ToString(row[0]); + + var names = Common.GetNames(Convert.ToString(row[2])); + if (null != names[0] && null != names[1]) + { + removeFile.ShortName = names[0]; + removeFile.Name = names[1]; + } + else if (null != names[0]) + { + removeFile.Name = names[0]; + } + + // directory/property is set in FinalizeDecompile + + switch (Convert.ToInt32(row[4])) + { + case MsiInterop.MsidbRemoveFileInstallModeOnInstall: + removeFile.On = Wix.InstallUninstallType.install; + break; + case MsiInterop.MsidbRemoveFileInstallModeOnRemove: + removeFile.On = Wix.InstallUninstallType.uninstall; + break; + case MsiInterop.MsidbRemoveFileInstallModeOnBoth: + removeFile.On = Wix.InstallUninstallType.both; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + if (null != component) + { + component.AddChild(removeFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + this.core.IndexElement(row, removeFile); + } + } + } + + /// + /// Decompile the RemoveIniFile table. + /// + /// The table to decompile. + private void DecompileRemoveIniFileTable(Table table) + { + foreach (var row in table.Rows) + { + var iniFile = new Wix.IniFile(); + + iniFile.Id = Convert.ToString(row[0]); + + var names = Common.GetNames(Convert.ToString(row[1])); + if (null != names[0] && null != names[1]) + { + iniFile.ShortName = names[0]; + iniFile.Name = names[1]; + } + else if (null != names[0]) + { + iniFile.Name = names[0]; + } + + if (null != row[2]) + { + iniFile.Directory = Convert.ToString(row[2]); + } + + iniFile.Section = Convert.ToString(row[3]); + + iniFile.Key = Convert.ToString(row[4]); + + if (null != row[5]) + { + iniFile.Value = Convert.ToString(row[5]); + } + + switch (Convert.ToInt32(row[6])) + { + case MsiInterop.MsidbIniFileActionRemoveLine: + iniFile.Action = Wix.IniFile.ActionType.removeLine; + break; + case MsiInterop.MsidbIniFileActionRemoveTag: + iniFile.Action = Wix.IniFile.ActionType.removeTag; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[7])); + if (null != component) + { + component.AddChild(iniFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[7]), "Component")); + } + } + } + + /// + /// Decompile the RemoveRegistry table. + /// + /// The table to decompile. + private void DecompileRemoveRegistryTable(Table table) + { + foreach (var row in table.Rows) + { + if ("-" == Convert.ToString(row[3])) + { + var removeRegistryKey = new Wix.RemoveRegistryKey(); + + removeRegistryKey.Id = Convert.ToString(row[0]); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + removeRegistryKey.Root = registryRootType; + } + + removeRegistryKey.Key = Convert.ToString(row[2]); + + removeRegistryKey.Action = Wix.RemoveRegistryKey.ActionType.removeOnInstall; + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[4])); + if (null != component) + { + component.AddChild(removeRegistryKey); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[4]), "Component")); + } + } + else + { + var removeRegistryValue = new Wix.RemoveRegistryValue(); + + removeRegistryValue.Id = Convert.ToString(row[0]); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + removeRegistryValue.Root = registryRootType; + } + + removeRegistryValue.Key = Convert.ToString(row[2]); + + if (null != row[3]) + { + removeRegistryValue.Name = Convert.ToString(row[3]); + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[4])); + if (null != component) + { + component.AddChild(removeRegistryValue); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[4]), "Component")); + } + } + } + } + + /// + /// Decompile the ReserveCost table. + /// + /// The table to decompile. + private void DecompileReserveCostTable(Table table) + { + foreach (var row in table.Rows) + { + var reserveCost = new Wix.ReserveCost(); + + reserveCost.Id = Convert.ToString(row[0]); + + if (null != row[2]) + { + reserveCost.Directory = Convert.ToString(row[2]); + } + + reserveCost.RunLocal = Convert.ToInt32(row[3]); + + reserveCost.RunFromSource = Convert.ToInt32(row[4]); + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); + if (null != component) + { + component.AddChild(reserveCost); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + } + } + } + + /// + /// Decompile the SelfReg table. + /// + /// The table to decompile. + private void DecompileSelfRegTable(Table table) + { + foreach (var row in table.Rows) + { + var file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); + + if (null != file) + { + if (null != row[1]) + { + file.SelfRegCost = Convert.ToInt32(row[1]); + } + else + { + file.SelfRegCost = 0; + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); + } + } + } + + /// + /// Decompile the ServiceControl table. + /// + /// The table to decompile. + private void DecompileServiceControlTable(Table table) + { + foreach (var row in table.Rows) + { + var serviceControl = new Wix.ServiceControl(); + + serviceControl.Id = Convert.ToString(row[0]); + + serviceControl.Name = Convert.ToString(row[1]); + + var eventValue = Convert.ToInt32(row[2]); + if (MsiInterop.MsidbServiceControlEventStart == (eventValue & MsiInterop.MsidbServiceControlEventStart) && + MsiInterop.MsidbServiceControlEventUninstallStart == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStart)) + { + serviceControl.Start = Wix.InstallUninstallType.both; + } + else if (MsiInterop.MsidbServiceControlEventStart == (eventValue & MsiInterop.MsidbServiceControlEventStart)) + { + serviceControl.Start = Wix.InstallUninstallType.install; + } + else if (MsiInterop.MsidbServiceControlEventUninstallStart == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStart)) + { + serviceControl.Start = Wix.InstallUninstallType.uninstall; + } + + if (MsiInterop.MsidbServiceControlEventStop == (eventValue & MsiInterop.MsidbServiceControlEventStop) && + MsiInterop.MsidbServiceControlEventUninstallStop == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStop)) + { + serviceControl.Stop = Wix.InstallUninstallType.both; + } + else if (MsiInterop.MsidbServiceControlEventStop == (eventValue & MsiInterop.MsidbServiceControlEventStop)) + { + serviceControl.Stop = Wix.InstallUninstallType.install; + } + else if (MsiInterop.MsidbServiceControlEventUninstallStop == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStop)) + { + serviceControl.Stop = Wix.InstallUninstallType.uninstall; + } + + if (MsiInterop.MsidbServiceControlEventDelete == (eventValue & MsiInterop.MsidbServiceControlEventDelete) && + MsiInterop.MsidbServiceControlEventUninstallDelete == (eventValue & MsiInterop.MsidbServiceControlEventUninstallDelete)) + { + serviceControl.Remove = Wix.InstallUninstallType.both; + } + else if (MsiInterop.MsidbServiceControlEventDelete == (eventValue & MsiInterop.MsidbServiceControlEventDelete)) + { + serviceControl.Remove = Wix.InstallUninstallType.install; + } + else if (MsiInterop.MsidbServiceControlEventUninstallDelete == (eventValue & MsiInterop.MsidbServiceControlEventUninstallDelete)) + { + serviceControl.Remove = Wix.InstallUninstallType.uninstall; + } + + if (null != row[3]) + { + var arguments = NullSplitter.Split(Convert.ToString(row[3])); + + foreach (var argument in arguments) + { + var serviceArgument = new Wix.ServiceArgument(); + + serviceArgument.Content = argument; + + serviceControl.AddChild(serviceArgument); + } + } + + if (null != row[4]) + { + if (0 == Convert.ToInt32(row[4])) + { + serviceControl.Wait = Wix.YesNoType.no; + } + else + { + serviceControl.Wait = Wix.YesNoType.yes; + } + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[5])); + if (null != component) + { + component.AddChild(serviceControl); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[5]), "Component")); + } + } + } + + /// + /// Decompile the ServiceInstall table. + /// + /// The table to decompile. + private void DecompileServiceInstallTable(Table table) + { + foreach (var row in table.Rows) + { + var serviceInstall = new Wix.ServiceInstall(); + + serviceInstall.Id = Convert.ToString(row[0]); + + serviceInstall.Name = Convert.ToString(row[1]); + + if (null != row[2]) + { + serviceInstall.DisplayName = Convert.ToString(row[2]); + } + + var serviceType = Convert.ToInt32(row[3]); + if (MsiInterop.MsidbServiceInstallInteractive == (serviceType & MsiInterop.MsidbServiceInstallInteractive)) + { + serviceInstall.Interactive = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbServiceInstallOwnProcess == (serviceType & MsiInterop.MsidbServiceInstallOwnProcess) && + MsiInterop.MsidbServiceInstallShareProcess == (serviceType & MsiInterop.MsidbServiceInstallShareProcess)) + { + // TODO: warn + } + else if (MsiInterop.MsidbServiceInstallOwnProcess == (serviceType & MsiInterop.MsidbServiceInstallOwnProcess)) + { + serviceInstall.Type = Wix.ServiceInstall.TypeType.ownProcess; + } + else if (MsiInterop.MsidbServiceInstallShareProcess == (serviceType & MsiInterop.MsidbServiceInstallShareProcess)) + { + serviceInstall.Type = Wix.ServiceInstall.TypeType.shareProcess; + } + + var startType = Convert.ToInt32(row[4]); + if (MsiInterop.MsidbServiceInstallDisabled == startType) + { + serviceInstall.Start = Wix.ServiceInstall.StartType.disabled; + } + else if (MsiInterop.MsidbServiceInstallDemandStart == startType) + { + serviceInstall.Start = Wix.ServiceInstall.StartType.demand; + } + else if (MsiInterop.MsidbServiceInstallAutoStart == startType) + { + serviceInstall.Start = Wix.ServiceInstall.StartType.auto; + } + else + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + } + + var errorControl = Convert.ToInt32(row[5]); + if (MsiInterop.MsidbServiceInstallErrorCritical == (errorControl & MsiInterop.MsidbServiceInstallErrorCritical)) + { + serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.critical; + } + else if (MsiInterop.MsidbServiceInstallErrorNormal == (errorControl & MsiInterop.MsidbServiceInstallErrorNormal)) + { + serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.normal; + } + else + { + serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.ignore; + } + + if (MsiInterop.MsidbServiceInstallErrorControlVital == (errorControl & MsiInterop.MsidbServiceInstallErrorControlVital)) + { + serviceInstall.Vital = Wix.YesNoType.yes; + } + + if (null != row[6]) + { + serviceInstall.LoadOrderGroup = Convert.ToString(row[6]); + } + + if (null != row[7]) + { + var dependencies = NullSplitter.Split(Convert.ToString(row[7])); + + foreach (var dependency in dependencies) + { + if (0 < dependency.Length) + { + var serviceDependency = new Wix.ServiceDependency(); + + if (dependency.StartsWith("+", StringComparison.Ordinal)) + { + serviceDependency.Group = Wix.YesNoType.yes; + serviceDependency.Id = dependency.Substring(1); + } + else + { + serviceDependency.Id = dependency; + } + + serviceInstall.AddChild(serviceDependency); + } + } + } + + if (null != row[8]) + { + serviceInstall.Account = Convert.ToString(row[8]); + } + + if (null != row[9]) + { + serviceInstall.Password = Convert.ToString(row[9]); + } + + if (null != row[10]) + { + serviceInstall.Arguments = Convert.ToString(row[10]); + } + + if (null != row[12]) + { + serviceInstall.Description = Convert.ToString(row[12]); + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[11])); + if (null != component) + { + component.AddChild(serviceInstall); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[11]), "Component")); + } + this.core.IndexElement(row, serviceInstall); + } + } + + /// + /// Decompile the SFPCatalog table. + /// + /// The table to decompile. + private void DecompileSFPCatalogTable(Table table) + { + foreach (var row in table.Rows) + { + var sfpCatalog = new Wix.SFPCatalog(); + + sfpCatalog.Name = Convert.ToString(row[0]); + + sfpCatalog.SourceFile = Convert.ToString(row[1]); + + this.core.IndexElement(row, sfpCatalog); + } + + // nest the SFPCatalog elements + foreach (var row in table.Rows) + { + var sfpCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement(row); + + if (null != row[2]) + { + var parentSFPCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement("SFPCatalog", Convert.ToString(row[2])); + + if (null != parentSFPCatalog) + { + parentSFPCatalog.AddChild(sfpCatalog); + } + else + { + sfpCatalog.Dependency = Convert.ToString(row[2]); + + this.core.RootElement.AddChild(sfpCatalog); + } + } + else + { + this.core.RootElement.AddChild(sfpCatalog); + } + } + } + + /// + /// Decompile the Shortcut table. + /// + /// The table to decompile. + private void DecompileShortcutTable(Table table) + { + foreach (var row in table.Rows) + { + var shortcut = new Wix.Shortcut(); + + shortcut.Id = Convert.ToString(row[0]); + + shortcut.Directory = Convert.ToString(row[1]); + + var names = Common.GetNames(Convert.ToString(row[2])); + if (null != names[0] && null != names[1]) + { + shortcut.ShortName = names[0]; + shortcut.Name = names[1]; + } + else if (null != names[0]) + { + shortcut.Name = names[0]; + } + + var target = Convert.ToString(row[4]); + if (target.StartsWith("[", StringComparison.Ordinal) && target.EndsWith("]", StringComparison.Ordinal)) + { + // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element + shortcut.Target = target; + } + else + { + shortcut.Advertise = Wix.YesNoType.yes; + + // primary feature is set in FinalizeFeatureComponentsTable + } + + if (null != row[5]) + { + shortcut.Arguments = Convert.ToString(row[5]); + } + + if (null != row[6]) + { + shortcut.Description = Convert.ToString(row[6]); + } + + if (null != row[7]) + { + shortcut.Hotkey = Convert.ToInt32(row[7]); + } + + if (null != row[8]) + { + shortcut.Icon = Convert.ToString(row[8]); + } + + if (null != row[9]) + { + shortcut.IconIndex = Convert.ToInt32(row[9]); + } + + if (null != row[10]) + { + switch (Convert.ToInt32(row[10])) + { + case 1: + shortcut.Show = Wix.Shortcut.ShowType.normal; + break; + case 3: + shortcut.Show = Wix.Shortcut.ShowType.maximized; + break; + case 7: + shortcut.Show = Wix.Shortcut.ShowType.minimized; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10])); + break; + } + } + + if (null != row[11]) + { + shortcut.WorkingDirectory = Convert.ToString(row[11]); + } + + // Only try to read the MSI 4.0-specific columns if they actually exist + if (15 < row.Fields.Length) + { + if (null != row[12]) + { + shortcut.DisplayResourceDll = Convert.ToString(row[12]); + } + + if (null != row[13]) + { + shortcut.DisplayResourceId = Convert.ToInt32(row[13]); + } + + if (null != row[14]) + { + shortcut.DescriptionResourceDll = Convert.ToString(row[14]); + } + + if (null != row[15]) + { + shortcut.DescriptionResourceId = Convert.ToInt32(row[15]); + } + } + + var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[3])); + if (null != component) + { + component.AddChild(shortcut); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[3]), "Component")); + } + + this.core.IndexElement(row, shortcut); + } + } + + /// + /// Decompile the Signature table. + /// + /// The table to decompile. + private void DecompileSignatureTable(Table table) + { + foreach (var row in table.Rows) + { + var fileSearch = new Wix.FileSearch(); + + fileSearch.Id = Convert.ToString(row[0]); + + var names = Common.GetNames(Convert.ToString(row[1])); + if (null != names[0]) + { + // it is permissable to just have a long name + if (!this.core.IsValidShortFilename(names[0], false) && null == names[1]) + { + fileSearch.Name = names[0]; + } + else + { + fileSearch.ShortName = names[0]; + } + } + + if (null != names[1]) + { + fileSearch.Name = names[1]; + } + + if (null != row[2]) + { + fileSearch.MinVersion = Convert.ToString(row[2]); + } + + if (null != row[3]) + { + fileSearch.MaxVersion = Convert.ToString(row[3]); + } + + if (null != row[4]) + { + fileSearch.MinSize = Convert.ToInt32(row[4]); + } + + if (null != row[5]) + { + fileSearch.MaxSize = Convert.ToInt32(row[5]); + } + + if (null != row[6]) + { + fileSearch.MinDate = this.core.ConvertIntegerToDateTime(Convert.ToInt32(row[6])); + } + + if (null != row[7]) + { + fileSearch.MaxDate = this.core.ConvertIntegerToDateTime(Convert.ToInt32(row[7])); + } + + if (null != row[8]) + { + fileSearch.Languages = Convert.ToString(row[8]); + } + + this.core.IndexElement(row, fileSearch); + } + } + + /// + /// Decompile the TargetFiles_OptionalData table. + /// + /// The table to decompile. + private void DecompileTargetFiles_OptionalDataTable(Table table) + { + foreach (var row in table.Rows) + { + var targetFile = (Wix.TargetFile)this.patchTargetFiles[row[0]]; + if (null == targetFile) + { + targetFile = new Wix.TargetFile(); + + targetFile.Id = Convert.ToString(row[1]); + + var targetImage = (Wix.TargetImage)this.core.GetIndexedElement("TargetImages", Convert.ToString(row[0])); + if (null != targetImage) + { + targetImage.AddChild(targetFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", Convert.ToString(row[0]), "TargetImages")); + } + this.patchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), targetFile); + } + + if (null != row[2]) + { + var symbolPaths = (Convert.ToString(row[2])).Split(';'); + + foreach (var symbolPathString in symbolPaths) + { + var symbolPath = new Wix.SymbolPath(); + + symbolPath.Path = symbolPathString; + + targetFile.AddChild(symbolPath); + } + } + + if (null != row[3] && null != row[4]) + { + var ignoreOffsets = (Convert.ToString(row[3])).Split(','); + var ignoreLengths = (Convert.ToString(row[4])).Split(','); + + if (ignoreOffsets.Length == ignoreLengths.Length) + { + for (var i = 0; i < ignoreOffsets.Length; i++) + { + var ignoreRange = new Wix.IgnoreRange(); + + if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) + { + ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i].Substring(2), 16); + } + else + { + ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture); + } + + if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) + { + ignoreRange.Length = Convert.ToInt32(ignoreLengths[i].Substring(2), 16); + } + else + { + ignoreRange.Length = Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture); + } + + targetFile.AddChild(ignoreRange); + } + } + else + { + // TODO: warn + } + } + else if (null != row[3] || null != row[4]) + { + // TODO: warn about mismatch between columns + } + + // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable + } + } + + /// + /// Decompile the TargetImages table. + /// + /// The table to decompile. + private void DecompileTargetImagesTable(Table table) + { + foreach (var row in table.Rows) + { + var targetImage = new Wix.TargetImage(); + + targetImage.Id = Convert.ToString(row[0]); + + targetImage.SourceFile = Convert.ToString(row[1]); + + if (null != row[2]) + { + var symbolPaths = (Convert.ToString(row[3])).Split(';'); + + foreach (var symbolPathString in symbolPaths) + { + var symbolPath = new Wix.SymbolPath(); + + symbolPath.Path = symbolPathString; + + targetImage.AddChild(symbolPath); + } + } + + targetImage.Order = Convert.ToInt32(row[4]); + + if (null != row[5]) + { + targetImage.Validation = Convert.ToString(row[5]); + } + + if (0 != Convert.ToInt32(row[6])) + { + targetImage.IgnoreMissingFiles = Wix.YesNoType.yes; + } + + var upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[3])); + if (null != upgradeImage) + { + upgradeImage.AddChild(targetImage); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[3]), "UpgradedImages")); + } + this.core.IndexElement(row, targetImage); + } + } + + /// + /// Decompile the TextStyle table. + /// + /// The table to decompile. + private void DecompileTextStyleTable(Table table) + { + foreach (var row in table.Rows) + { + var textStyle = new Wix.TextStyle(); + + textStyle.Id = Convert.ToString(row[0]); + + textStyle.FaceName = Convert.ToString(row[1]); + + textStyle.Size = Convert.ToString(row[2]); + + if (null != row[3]) + { + var color = Convert.ToInt32(row[3]); + + textStyle.Red = color & 0xFF; + + textStyle.Green = (color & 0xFF00) >> 8; + + textStyle.Blue = (color & 0xFF0000) >> 16; + } + + if (null != row[4]) + { + var styleBits = Convert.ToInt32(row[4]); + + if (MsiInterop.MsidbTextStyleStyleBitsBold == (styleBits & MsiInterop.MsidbTextStyleStyleBitsBold)) + { + textStyle.Bold = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbTextStyleStyleBitsItalic == (styleBits & MsiInterop.MsidbTextStyleStyleBitsItalic)) + { + textStyle.Italic = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbTextStyleStyleBitsUnderline == (styleBits & MsiInterop.MsidbTextStyleStyleBitsUnderline)) + { + textStyle.Underline = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbTextStyleStyleBitsStrike == (styleBits & MsiInterop.MsidbTextStyleStyleBitsStrike)) + { + textStyle.Strike = Wix.YesNoType.yes; + } + } + + this.core.UIElement.AddChild(textStyle); + } + } + + /// + /// Decompile the TypeLib table. + /// + /// The table to decompile. + private void DecompileTypeLibTable(Table table) + { + foreach (var row in table.Rows) + { + var typeLib = new Wix.TypeLib(); + + typeLib.Id = Convert.ToString(row[0]); + + typeLib.Advertise = Wix.YesNoType.yes; + + typeLib.Language = Convert.ToInt32(row[1]); + + if (null != row[3]) + { + var version = Convert.ToInt32(row[3]); + + if (65536 == version) + { + this.Messaging.Write(WarningMessages.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, typeLib.Id)); + } + + typeLib.MajorVersion = ((version & 0xFFFF00) >> 8); + typeLib.MinorVersion = (version & 0xFF); + } + + if (null != row[4]) + { + typeLib.Description = Convert.ToString(row[4]); + } + + if (null != row[5]) + { + typeLib.HelpDirectory = Convert.ToString(row[5]); + } + + if (null != row[7]) + { + typeLib.Cost = Convert.ToInt32(row[7]); + } + + // nested under the appropriate File element in FinalizeFileTable + this.core.IndexElement(row, typeLib); + } + } + + /// + /// Decompile the Upgrade table. + /// + /// The table to decompile. + private void DecompileUpgradeTable(Table table) + { + var upgradeElements = new Hashtable(); + + foreach (UpgradeRow upgradeRow in table.Rows) + { + if (Common.UpgradeDetectedProperty == upgradeRow.ActionProperty || Common.DowngradeDetectedProperty == upgradeRow.ActionProperty) + { + continue; // MajorUpgrade rows processed in FinalizeUpgradeTable + } + + var upgrade = (Wix.Upgrade)upgradeElements[upgradeRow.UpgradeCode]; + + // create the parent Upgrade element if it doesn't already exist + if (null == upgrade) + { + upgrade = new Wix.Upgrade(); + + upgrade.Id = upgradeRow.UpgradeCode; + + this.core.RootElement.AddChild(upgrade); + upgradeElements.Add(upgrade.Id, upgrade); + } + + var upgradeVersion = new Wix.UpgradeVersion(); + + if (null != upgradeRow.VersionMin) + { + upgradeVersion.Minimum = upgradeRow.VersionMin; + } + + if (null != upgradeRow.VersionMax) + { + upgradeVersion.Maximum = upgradeRow.VersionMax; + } + + if (null != upgradeRow.Language) + { + upgradeVersion.Language = upgradeRow.Language; + } + + if (MsiInterop.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesMigrateFeatures)) + { + upgradeVersion.MigrateFeatures = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect)) + { + upgradeVersion.OnlyDetect = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure)) + { + upgradeVersion.IgnoreRemoveFailure = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive)) + { + upgradeVersion.IncludeMinimum = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive)) + { + upgradeVersion.IncludeMaximum = Wix.YesNoType.yes; + } + + if (MsiInterop.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive)) + { + upgradeVersion.ExcludeLanguages = Wix.YesNoType.yes; + } + + if (null != upgradeRow.Remove) + { + upgradeVersion.RemoveFeatures = upgradeRow.Remove; + } + + upgradeVersion.Property = upgradeRow.ActionProperty; + + upgrade.AddChild(upgradeVersion); + } + } + + /// + /// Decompile the UpgradedFiles_OptionalData table. + /// + /// The table to decompile. + private void DecompileUpgradedFiles_OptionalDataTable(Table table) + { + foreach (var row in table.Rows) + { + var upgradeFile = new Wix.UpgradeFile(); + + upgradeFile.File = Convert.ToString(row[1]); + + if (null != row[2]) + { + var symbolPaths = (Convert.ToString(row[2])).Split(';'); + + foreach (var symbolPathString in symbolPaths) + { + var symbolPath = new Wix.SymbolPath(); + + symbolPath.Path = symbolPathString; + + upgradeFile.AddChild(symbolPath); + } + } + + if (null != row[3] && 1 == Convert.ToInt32(row[3])) + { + upgradeFile.AllowIgnoreOnError = Wix.YesNoType.yes; + } + + if (null != row[4] && 0 != Convert.ToInt32(row[4])) + { + upgradeFile.WholeFile = Wix.YesNoType.yes; + } + + upgradeFile.Ignore = Wix.YesNoType.no; + + var upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[0])); + if (null != upgradeImage) + { + upgradeImage.AddChild(upgradeFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[0]), "UpgradedImages")); + } + } + } + + /// + /// Decompile the UpgradedFilesToIgnore table. + /// + /// The table to decompile. + private void DecompileUpgradedFilesToIgnoreTable(Table table) + { + foreach (var row in table.Rows) + { + if ("*" != Convert.ToString(row[0])) + { + var upgradeFile = new Wix.UpgradeFile(); + + upgradeFile.File = Convert.ToString(row[1]); + + upgradeFile.Ignore = Wix.YesNoType.yes; + + var upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[0])); + if (null != upgradeImage) + { + upgradeImage.AddChild(upgradeFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[0]), "UpgradedImages")); + } + } + else + { + this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0])); + } + } + } + + /// + /// Decompile the UpgradedImages table. + /// + /// The table to decompile. + private void DecompileUpgradedImagesTable(Table table) + { + foreach (var row in table.Rows) + { + var upgradeImage = new Wix.UpgradeImage(); + + upgradeImage.Id = Convert.ToString(row[0]); + + upgradeImage.SourceFile = Convert.ToString(row[1]); + + if (null != row[2]) + { + upgradeImage.SourcePatch = Convert.ToString(row[2]); + } + + if (null != row[3]) + { + var symbolPaths = (Convert.ToString(row[3])).Split(';'); + + foreach (var symbolPathString in symbolPaths) + { + var symbolPath = new Wix.SymbolPath(); + + symbolPath.Path = symbolPathString; + + upgradeImage.AddChild(symbolPath); + } + } + + var family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[4])); + if (null != family) + { + family.AddChild(upgradeImage); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[4]), "ImageFamilies")); + } + this.core.IndexElement(row, upgradeImage); + } + } + + /// + /// Decompile the UIText table. + /// + /// The table to decompile. + private void DecompileUITextTable(Table table) + { + foreach (var row in table.Rows) + { + var uiText = new Wix.UIText(); + + uiText.Id = Convert.ToString(row[0]); + + uiText.Content = Convert.ToString(row[1]); + + this.core.UIElement.AddChild(uiText); + } + } + + /// + /// Decompile the Verb table. + /// + /// The table to decompile. + private void DecompileVerbTable(Table table) + { + foreach (var row in table.Rows) + { + var verb = new Wix.Verb(); + + verb.Id = Convert.ToString(row[1]); + + if (null != row[2]) + { + verb.Sequence = Convert.ToInt32(row[2]); + } + + if (null != row[3]) + { + verb.Command = Convert.ToString(row[3]); + } + + if (null != row[4]) + { + verb.Argument = Convert.ToString(row[4]); + } + + this.core.IndexElement(row, verb); + } + } + + /// + /// Gets the RegistryRootType from an integer representation of the root. + /// + /// The source line information for the root. + /// The name of the table containing the field. + /// The field containing the root value. + /// The strongly-typed representation of the root. + /// true if the value could be converted; false otherwise. + private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out Wix.RegistryRootType registryRootType) + { + switch (Convert.ToInt32(field.Data)) + { + case (-1): + registryRootType = Wix.RegistryRootType.HKMU; + return true; + case MsiInterop.MsidbRegistryRootClassesRoot: + registryRootType = Wix.RegistryRootType.HKCR; + return true; + case MsiInterop.MsidbRegistryRootCurrentUser: + registryRootType = Wix.RegistryRootType.HKCU; + return true; + case MsiInterop.MsidbRegistryRootLocalMachine: + registryRootType = Wix.RegistryRootType.HKLM; + return true; + case MsiInterop.MsidbRegistryRootUsers: + registryRootType = Wix.RegistryRootType.HKU; + return true; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data)); + registryRootType = Wix.RegistryRootType.HKCR; // assign anything to satisfy the out parameter + return false; + } + } + + /// + /// Set the primary feature for a component. + /// + /// The row which specifies a primary feature. + /// The index of the column contaning the feature identifier. + /// The index of the column containing the component identifier. + private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex) + { + // only products contain primary features + if (OutputType.Product == this.OutputType) + { + var featureField = row.Fields[featureColumnIndex]; + var componentField = row.Fields[componentColumnIndex]; + + var componentRef = (Wix.ComponentRef)this.core.GetIndexedElement("FeatureComponents", Convert.ToString(featureField.Data), Convert.ToString(componentField.Data)); + + if (null != componentRef) + { + componentRef.Primary = Wix.YesNoType.yes; + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), featureField.Column.Name, Convert.ToString(featureField.Data), componentField.Column.Name, Convert.ToString(componentField.Data), "FeatureComponents")); + } + } + } + + /// + /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it. + /// + /// The collection of all tables. + private static Wix.MajorUpgrade.ScheduleType DetermineMajorUpgradeScheduling(TableIndexedCollection tables) + { + var sequenceRemoveExistingProducts = 0; + var sequenceInstallValidate = 0; + var sequenceInstallInitialize = 0; + var sequenceInstallFinalize = 0; + var sequenceInstallExecute = 0; + var sequenceInstallExecuteAgain = 0; + + var installExecuteSequenceTable = tables["InstallExecuteSequence"]; + if (null != installExecuteSequenceTable) + { + var removeExistingProductsRow = -1; + for (var i = 0; i < installExecuteSequenceTable.Rows.Count; i++) + { + var row = installExecuteSequenceTable.Rows[i]; + var action = Convert.ToString(row[0]); + var sequence = Convert.ToInt32(row[2]); + + switch (action) + { + case "RemoveExistingProducts": + sequenceRemoveExistingProducts = sequence; + removeExistingProductsRow = i; + break; + case "InstallValidate": + sequenceInstallValidate = sequence; + break; + case "InstallInitialize": + sequenceInstallInitialize = sequence; + break; + case "InstallExecute": + sequenceInstallExecute = sequence; + break; + case "InstallExecuteAgain": + sequenceInstallExecuteAgain = sequence; + break; + case "InstallFinalize": + sequenceInstallFinalize = sequence; + break; + } + } + + installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow); + } + + if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize) + { + return Wix.MajorUpgrade.ScheduleType.afterInstallValidate; + } + else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute) + { + return Wix.MajorUpgrade.ScheduleType.afterInstallInitialize; + } + else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain) + { + return Wix.MajorUpgrade.ScheduleType.afterInstallExecute; + } + else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize) + { + return Wix.MajorUpgrade.ScheduleType.afterInstallExecuteAgain; + } + else + { + return Wix.MajorUpgrade.ScheduleType.afterInstallFinalize; + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/DecompilerCore.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/DecompilerCore.cs new file mode 100644 index 00000000..17c97e09 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/DecompilerCore.cs @@ -0,0 +1,110 @@ +// 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 +{ + using System; + using System.Collections; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using Wix = WixToolset.Data.Serialize; + + /// + /// The base of the decompiler. Holds some variables used by the decompiler and extensions, + /// as well as some utility methods. + /// + internal class DecompilerCore + { + private readonly Hashtable elements; + private Wix.UI uiElement; + + /// + /// Instantiate a new decompiler core. + /// + /// The root element of the decompiled database. + /// The message handler. + internal DecompilerCore(Wix.IParentElement rootElement) + { + this.elements = new Hashtable(); + this.RootElement = rootElement; + } + + /// + /// Gets the root element of the decompiled output. + /// + /// The root element of the decompiled output. + public Wix.IParentElement RootElement { get; } + + /// + /// Gets the UI element. + /// + /// The UI element. + public Wix.UI UIElement + { + get + { + if (null == this.uiElement) + { + this.uiElement = new Wix.UI(); + this.RootElement.AddChild(this.uiElement); + } + + return this.uiElement; + } + } + + /// + /// Verifies if a filename is a valid short filename. + /// + /// Filename to verify. + /// true if wildcards are allowed in the filename. + /// True if the filename is a valid short filename + public virtual bool IsValidShortFilename(string filename, bool allowWildcards) + { + return false; + } + + /// + /// Convert an Int32 into a DateTime. + /// + /// The Int32 value. + /// The DateTime. + public DateTime ConvertIntegerToDateTime(int value) + { + var date = value / 65536; + var time = value % 65536; + + return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2); + } + + /// + /// Gets the element corresponding to the row it came from. + /// + /// The row corresponding to the element. + /// The indexed element. + public Wix.ISchemaElement GetIndexedElement(Row row) + { + return this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + } + + /// + /// Gets the element corresponding to the primary key of the given table. + /// + /// The table corresponding to the element. + /// The primary key corresponding to the element. + /// The indexed element. + public Wix.ISchemaElement GetIndexedElement(string table, params string[] primaryKey) + { + return (Wix.ISchemaElement)this.elements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))]; + } + + /// + /// Index an element by its corresponding row. + /// + /// The row corresponding to the element. + /// The element to index. + public void IndexElement(Row row, Wix.ISchemaElement element) + { + this.elements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompiler.cs deleted file mode 100644 index c5d68db3..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Decompiler.cs +++ /dev/null @@ -1,9356 +0,0 @@ -// 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 -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.IO; - using System.Text; - using System.Text.RegularExpressions; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Core.Native; - using Wix = WixToolset.Data.Serialize; - using WixToolset.Core; - - /// - /// Decompiles an msi database into WiX source. - /// - public class Decompiler - { - private static readonly Regex NullSplitter = new Regex(@"\[~]"); -#if TODO - private int codepage; - private bool compressed; - private bool shortNames; - private DecompilerCore core; - private string exportFilePath; - private List extensions; - private Dictionary extensionsByTableName; - private string modularizationGuid; - private OutputType outputType; - private Hashtable patchTargetFiles; - private Hashtable sequenceElements; - private bool showPedanticMessages; - private WixActionRowCollection standardActions; - private bool suppressCustomTables; - private bool suppressDroppingEmptyTables; - private bool suppressRelativeActionSequencing; - private bool suppressUI; - private TableDefinitionCollection tableDefinitions; - // private TempFileCollection tempFiles; - private bool treatProductAsModule; - - /// - /// Creates a new decompiler object with a default set of table definitions. - /// - public Decompiler() - { - this.standardActions = WindowsInstallerStandard.GetStandardActions(); - - this.extensions = new List(); - this.extensionsByTableName = new Dictionary(); - this.patchTargetFiles = new Hashtable(); - this.sequenceElements = new Hashtable(); - this.tableDefinitions = new TableDefinitionCollection(); - this.exportFilePath = "SourceDir"; - } - - /// - /// Gets or sets the base source file path. - /// - /// Base source file path. - public string ExportFilePath - { - get { return this.exportFilePath; } - set { this.exportFilePath = value; } - } - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - public bool ShowPedanticMessages - { - get { return this.showPedanticMessages; } - set { this.showPedanticMessages = value; } - } - - /// - /// Gets or sets the option to suppress custom tables. - /// - /// The option to suppress dropping empty tables. - public bool SuppressCustomTables - { - get { return this.suppressCustomTables; } - set { this.suppressCustomTables = value; } - } - - /// - /// Gets or sets the option to suppress dropping empty tables. - /// - /// The option to suppress dropping empty tables. - public bool SuppressDroppingEmptyTables - { - get { return this.suppressDroppingEmptyTables; } - set { this.suppressDroppingEmptyTables = value; } - } - - /// - /// Gets or sets the option to suppress decompiling with relative action sequencing (uses sequence numbers). - /// - /// The option to suppress decompiling with relative action sequencing (uses sequence numbers). - public bool SuppressRelativeActionSequencing - { - get { return this.suppressRelativeActionSequencing; } - set { this.suppressRelativeActionSequencing = value; } - } - - /// - /// Gets or sets the option to suppress decompiling UI-related tables. - /// - /// The option to suppress decompiling UI-related tables. - public bool SuppressUI - { - get { return this.suppressUI; } - set { this.suppressUI = value; } - } - - /// - /// Gets or sets the temporary path for the Decompiler. If left null, the decompiler - /// will use %TEMP% environment variable. - /// - /// Path to temp files. - public string TempFilesLocation - { - get - { - // return null == this.tempFiles ? String.Empty : this.tempFiles.BasePath; - return Path.GetTempPath(); - } - - // set - // { - // if (null == value) - // { - // this.tempFiles = new TempFileCollection(); - // } - // else - // { - // this.tempFiles = new TempFileCollection(value); - // } - // } - } - - /// - /// Gets or sets whether the decompiler should use module logic on a product output. - /// - /// The option to treat a product like a module - public bool TreatProductAsModule - { - get { return this.treatProductAsModule; } - set { this.treatProductAsModule = value; } - } - - /// - /// Decompile the database file. - /// - /// The output to decompile. - /// The serialized WiX source code. - [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] - public Wix.Wix Decompile(Output output) - { - if (null == output) - { - throw new ArgumentNullException("output"); - } - - this.codepage = output.Codepage; - this.outputType = output.Type; - - // collect the table definitions from the output - this.tableDefinitions.Clear(); - foreach (Table table in output.Tables) - { - this.tableDefinitions.Add(table.Definition); - } - - // add any missing standard and wix-specific table definitions - foreach (TableDefinition tableDefinition in WindowsInstallerStandard.GetTableDefinitions()) - { - if (!this.tableDefinitions.Contains(tableDefinition.Name)) - { - this.tableDefinitions.Add(tableDefinition); - } - } - - // add any missing extension table definitions - foreach (IDecompilerExtension extension in this.extensions) - { - if (null != extension.TableDefinitions) - { - foreach (TableDefinition tableDefinition in extension.TableDefinitions) - { - if (!this.tableDefinitions.Contains(tableDefinition.Name)) - { - this.tableDefinitions.Add(tableDefinition); - } - } - } - } - - // if we don't have the temporary files object yet, get one -#if REDO_IN_NETCORE - if (null == this.tempFiles) - { - this.TempFilesLocation = null; - } -#endif - Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there - - bool encounteredError = false; - Wix.IParentElement rootElement; - Wix.Wix wixElement = new Wix.Wix(); - - switch (this.outputType) - { - case OutputType.Module: - rootElement = new Wix.Module(); - break; - case OutputType.PatchCreation: - rootElement = new Wix.PatchCreation(); - break; - case OutputType.Product: - rootElement = new Wix.Product(); - break; - default: - throw new InvalidOperationException(WixStrings.EXP_UnknownOutputType); - } - wixElement.AddChild((Wix.ISchemaElement)rootElement); - - // try to decompile the database file - try - { - this.core = new DecompilerCore(rootElement); - this.core.ShowPedanticMessages = this.showPedanticMessages; - - // stop processing if an error previously occurred - if (this.core.EncounteredError) - { - return null; - } - - // initialize the decompiler and its extensions - foreach (IDecompilerExtension extension in this.extensions) - { - extension.Core = this.core; - extension.Initialize(output.Tables); - } - this.InitializeDecompile(output.Tables); - - // stop processing if an error previously occurred - if (this.core.EncounteredError) - { - return null; - } - - // decompile the tables - this.DecompileTables(output); - - // finalize the decompiler and its extensions - this.FinalizeDecompile(output.Tables); - foreach (IDecompilerExtension extension in this.extensions) - { - extension.Finish(output.Tables); - } - } - finally - { - encounteredError = this.core.EncounteredError; - - this.core = null; - foreach (IDecompilerExtension extension in this.extensions) - { - extension.Core = null; - } - } - - // return the root element only if decompilation completed successfully - return (encounteredError ? null : wixElement); - } - - /// - /// Adds an extension. - /// - /// The extension to add. - public void AddExtension(IDecompilerExtension extension) - { - this.extensions.Add(extension); - - if (null != extension.TableDefinitions) - { - foreach (TableDefinition tableDefinition in extension.TableDefinitions) - { - if (!this.extensionsByTableName.ContainsKey(tableDefinition.Name)) - { - this.extensionsByTableName.Add(tableDefinition.Name, extension); - } - else - { - Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name)); - } - } - } - } - - /// - /// Cleans up the temp files used by the Decompiler. - /// - /// True if all files were deleted, false otherwise. - /// - /// This should be called after every call to Decompile to ensure there - /// are no conflicts between each decompiled database. - /// - public bool DeleteTempFiles() - { -#if REDO_IN_NETCORE - if (null == this.tempFiles) - { - return true; // no work to do - } - else - { - bool deleted = Common.DeleteTempFiles(this.tempFiles.BasePath, this.core); - - if (deleted) - { - this.tempFiles = null; // temp files have been deleted, no need to remember this now - } - - return deleted; - } -#endif - return true; - } - - /// - /// Set the common control attributes in a control element. - /// - /// The control attributes. - /// The control element. - private static void SetControlAttributes(int attributes, Wix.Control control) - { - if (0 == (attributes & MsiInterop.MsidbControlAttributesEnabled)) - { - control.Disabled = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbControlAttributesIndirect == (attributes & MsiInterop.MsidbControlAttributesIndirect)) - { - control.Indirect = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbControlAttributesInteger == (attributes & MsiInterop.MsidbControlAttributesInteger)) - { - control.Integer = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbControlAttributesLeftScroll == (attributes & MsiInterop.MsidbControlAttributesLeftScroll)) - { - control.LeftScroll = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbControlAttributesRightAligned == (attributes & MsiInterop.MsidbControlAttributesRightAligned)) - { - control.RightAligned = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbControlAttributesRTLRO == (attributes & MsiInterop.MsidbControlAttributesRTLRO)) - { - control.RightToLeft = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbControlAttributesSunken == (attributes & MsiInterop.MsidbControlAttributesSunken)) - { - control.Sunken = Wix.YesNoType.yes; - } - - if (0 == (attributes & MsiInterop.MsidbControlAttributesVisible)) - { - control.Hidden = Wix.YesNoType.yes; - } - } - - /// - /// Creates an action element. - /// - /// The action row from which the element should be created. - private void CreateActionElement(WixActionRow actionRow) - { - Wix.ISchemaElement actionElement = null; - - if (null != this.core.GetIndexedElement("CustomAction", actionRow.Action)) // custom action - { - Wix.Custom custom = new Wix.Custom(); - - custom.Action = actionRow.Action; - - if (null != actionRow.Condition) - { - custom.Content = actionRow.Condition; - } - - switch (actionRow.Sequence) - { - case (-4): - custom.OnExit = Wix.ExitType.suspend; - break; - case (-3): - custom.OnExit = Wix.ExitType.error; - break; - case (-2): - custom.OnExit = Wix.ExitType.cancel; - break; - case (-1): - custom.OnExit = Wix.ExitType.success; - break; - default: - if (null != actionRow.Before) - { - custom.Before = actionRow.Before; - } - else if (null != actionRow.After) - { - custom.After = actionRow.After; - } - else if (0 < actionRow.Sequence) - { - custom.Sequence = actionRow.Sequence; - } - break; - } - - actionElement = custom; - } - else if (null != this.core.GetIndexedElement("Dialog", actionRow.Action)) // dialog - { - Wix.Show show = new Wix.Show(); - - show.Dialog = actionRow.Action; - - if (null != actionRow.Condition) - { - show.Content = actionRow.Condition; - } - - switch (actionRow.Sequence) - { - case (-4): - show.OnExit = Wix.ExitType.suspend; - break; - case (-3): - show.OnExit = Wix.ExitType.error; - break; - case (-2): - show.OnExit = Wix.ExitType.cancel; - break; - case (-1): - show.OnExit = Wix.ExitType.success; - break; - default: - if (null != actionRow.Before) - { - show.Before = actionRow.Before; - } - else if (null != actionRow.After) - { - show.After = actionRow.After; - } - else if (0 < actionRow.Sequence) - { - show.Sequence = actionRow.Sequence; - } - break; - } - - actionElement = show; - } - else // possibly a standard action without suggested sequence information - { - actionElement = this.CreateStandardActionElement(actionRow); - } - - // add the action element to the appropriate sequence element - if (null != actionElement) - { - string sequenceTable = actionRow.SequenceTable.ToString(); - Wix.IParentElement sequenceElement = (Wix.IParentElement)this.sequenceElements[sequenceTable]; - - if (null == sequenceElement) - { - switch (actionRow.SequenceTable) - { - case SequenceTable.AdminExecuteSequence: - sequenceElement = new Wix.AdminExecuteSequence(); - break; - case SequenceTable.AdminUISequence: - sequenceElement = new Wix.AdminUISequence(); - break; - case SequenceTable.AdvtExecuteSequence: - sequenceElement = new Wix.AdvertiseExecuteSequence(); - break; - case SequenceTable.InstallExecuteSequence: - sequenceElement = new Wix.InstallExecuteSequence(); - break; - case SequenceTable.InstallUISequence: - sequenceElement = new Wix.InstallUISequence(); - break; - default: - throw new InvalidOperationException(WixStrings.EXP_UnknowSequenceTable); - } - - this.core.RootElement.AddChild((Wix.ISchemaElement)sequenceElement); - this.sequenceElements.Add(sequenceTable, sequenceElement); - } - - try - { - sequenceElement.AddChild(actionElement); - } - catch (System.ArgumentException) // action/dialog is not valid for this sequence - { - this.core.OnMessage(WixWarnings.IllegalActionInSequence(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); - } - } - } - - /// - /// Creates a standard action element. - /// - /// The action row from which the element should be created. - /// The created element. - private Wix.ISchemaElement CreateStandardActionElement(WixActionRow actionRow) - { - Wix.ActionSequenceType actionElement = null; - - switch (actionRow.Action) - { - case "AllocateRegistrySpace": - actionElement = new Wix.AllocateRegistrySpace(); - break; - case "AppSearch": - WixActionRow appSearchActionRow = this.standardActions[actionRow.SequenceTable, actionRow.Action]; - - if (null != actionRow.Before || null != actionRow.After || (null != appSearchActionRow && actionRow.Sequence != appSearchActionRow.Sequence)) - { - Wix.AppSearch appSearch = new Wix.AppSearch(); - - if (null != actionRow.Condition) - { - appSearch.Content = actionRow.Condition; - } - - if (null != actionRow.Before) - { - appSearch.Before = actionRow.Before; - } - else if (null != actionRow.After) - { - appSearch.After = actionRow.After; - } - else if (0 < actionRow.Sequence) - { - appSearch.Sequence = actionRow.Sequence; - } - - return appSearch; - } - break; - case "BindImage": - actionElement = new Wix.BindImage(); - break; - case "CCPSearch": - Wix.CCPSearch ccpSearch = new Wix.CCPSearch(); - Decompiler.SequenceRelativeAction(actionRow, ccpSearch); - return ccpSearch; - case "CostFinalize": - actionElement = new Wix.CostFinalize(); - break; - case "CostInitialize": - actionElement = new Wix.CostInitialize(); - break; - case "CreateFolders": - actionElement = new Wix.CreateFolders(); - break; - case "CreateShortcuts": - actionElement = new Wix.CreateShortcuts(); - break; - case "DeleteServices": - actionElement = new Wix.DeleteServices(); - break; - case "DisableRollback": - Wix.DisableRollback disableRollback = new Wix.DisableRollback(); - Decompiler.SequenceRelativeAction(actionRow, disableRollback); - return disableRollback; - case "DuplicateFiles": - actionElement = new Wix.DuplicateFiles(); - break; - case "ExecuteAction": - actionElement = new Wix.ExecuteAction(); - break; - case "FileCost": - actionElement = new Wix.FileCost(); - break; - case "FindRelatedProducts": - Wix.FindRelatedProducts findRelatedProducts = new Wix.FindRelatedProducts(); - Decompiler.SequenceRelativeAction(actionRow, findRelatedProducts); - return findRelatedProducts; - case "ForceReboot": - Wix.ForceReboot forceReboot = new Wix.ForceReboot(); - Decompiler.SequenceRelativeAction(actionRow, forceReboot); - return forceReboot; - case "InstallAdminPackage": - actionElement = new Wix.InstallAdminPackage(); - break; - case "InstallExecute": - Wix.InstallExecute installExecute = new Wix.InstallExecute(); - Decompiler.SequenceRelativeAction(actionRow, installExecute); - return installExecute; - case "InstallExecuteAgain": - Wix.InstallExecuteAgain installExecuteAgain = new Wix.InstallExecuteAgain(); - Decompiler.SequenceRelativeAction(actionRow, installExecuteAgain); - return installExecuteAgain; - case "InstallFiles": - actionElement = new Wix.InstallFiles(); - break; - case "InstallFinalize": - actionElement = new Wix.InstallFinalize(); - break; - case "InstallInitialize": - actionElement = new Wix.InstallInitialize(); - break; - case "InstallODBC": - actionElement = new Wix.InstallODBC(); - break; - case "InstallServices": - actionElement = new Wix.InstallServices(); - break; - case "InstallValidate": - actionElement = new Wix.InstallValidate(); - break; - case "IsolateComponents": - actionElement = new Wix.IsolateComponents(); - break; - case "LaunchConditions": - Wix.LaunchConditions launchConditions = new Wix.LaunchConditions(); - Decompiler.SequenceRelativeAction(actionRow, launchConditions); - return launchConditions; - case "MigrateFeatureStates": - actionElement = new Wix.MigrateFeatureStates(); - break; - case "MoveFiles": - actionElement = new Wix.MoveFiles(); - break; - case "MsiPublishAssemblies": - actionElement = new Wix.MsiPublishAssemblies(); - break; - case "MsiUnpublishAssemblies": - actionElement = new Wix.MsiUnpublishAssemblies(); - break; - case "PatchFiles": - actionElement = new Wix.PatchFiles(); - break; - case "ProcessComponents": - actionElement = new Wix.ProcessComponents(); - break; - case "PublishComponents": - actionElement = new Wix.PublishComponents(); - break; - case "PublishFeatures": - actionElement = new Wix.PublishFeatures(); - break; - case "PublishProduct": - actionElement = new Wix.PublishProduct(); - break; - case "RegisterClassInfo": - actionElement = new Wix.RegisterClassInfo(); - break; - case "RegisterComPlus": - actionElement = new Wix.RegisterComPlus(); - break; - case "RegisterExtensionInfo": - actionElement = new Wix.RegisterExtensionInfo(); - break; - case "RegisterFonts": - actionElement = new Wix.RegisterFonts(); - break; - case "RegisterMIMEInfo": - actionElement = new Wix.RegisterMIMEInfo(); - break; - case "RegisterProduct": - actionElement = new Wix.RegisterProduct(); - break; - case "RegisterProgIdInfo": - actionElement = new Wix.RegisterProgIdInfo(); - break; - case "RegisterTypeLibraries": - actionElement = new Wix.RegisterTypeLibraries(); - break; - case "RegisterUser": - actionElement = new Wix.RegisterUser(); - break; - case "RemoveDuplicateFiles": - actionElement = new Wix.RemoveDuplicateFiles(); - break; - case "RemoveEnvironmentStrings": - actionElement = new Wix.RemoveEnvironmentStrings(); - break; - case "RemoveExistingProducts": - Wix.RemoveExistingProducts removeExistingProducts = new Wix.RemoveExistingProducts(); - Decompiler.SequenceRelativeAction(actionRow, removeExistingProducts); - return removeExistingProducts; - case "RemoveFiles": - actionElement = new Wix.RemoveFiles(); - break; - case "RemoveFolders": - actionElement = new Wix.RemoveFolders(); - break; - case "RemoveIniValues": - actionElement = new Wix.RemoveIniValues(); - break; - case "RemoveODBC": - actionElement = new Wix.RemoveODBC(); - break; - case "RemoveRegistryValues": - actionElement = new Wix.RemoveRegistryValues(); - break; - case "RemoveShortcuts": - actionElement = new Wix.RemoveShortcuts(); - break; - case "ResolveSource": - Wix.ResolveSource resolveSource = new Wix.ResolveSource(); - Decompiler.SequenceRelativeAction(actionRow, resolveSource); - return resolveSource; - case "RMCCPSearch": - Wix.RMCCPSearch rmccpSearch = new Wix.RMCCPSearch(); - Decompiler.SequenceRelativeAction(actionRow, rmccpSearch); - return rmccpSearch; - case "ScheduleReboot": - Wix.ScheduleReboot scheduleReboot = new Wix.ScheduleReboot(); - Decompiler.SequenceRelativeAction(actionRow, scheduleReboot); - return scheduleReboot; - case "SelfRegModules": - actionElement = new Wix.SelfRegModules(); - break; - case "SelfUnregModules": - actionElement = new Wix.SelfUnregModules(); - break; - case "SetODBCFolders": - actionElement = new Wix.SetODBCFolders(); - break; - case "StartServices": - actionElement = new Wix.StartServices(); - break; - case "StopServices": - actionElement = new Wix.StopServices(); - break; - case "UnpublishComponents": - actionElement = new Wix.UnpublishComponents(); - break; - case "UnpublishFeatures": - actionElement = new Wix.UnpublishFeatures(); - break; - case "UnregisterClassInfo": - actionElement = new Wix.UnregisterClassInfo(); - break; - case "UnregisterComPlus": - actionElement = new Wix.UnregisterComPlus(); - break; - case "UnregisterExtensionInfo": - actionElement = new Wix.UnregisterExtensionInfo(); - break; - case "UnregisterFonts": - actionElement = new Wix.UnregisterFonts(); - break; - case "UnregisterMIMEInfo": - actionElement = new Wix.UnregisterMIMEInfo(); - break; - case "UnregisterProgIdInfo": - actionElement = new Wix.UnregisterProgIdInfo(); - break; - case "UnregisterTypeLibraries": - actionElement = new Wix.UnregisterTypeLibraries(); - break; - case "ValidateProductID": - actionElement = new Wix.ValidateProductID(); - break; - case "WriteEnvironmentStrings": - actionElement = new Wix.WriteEnvironmentStrings(); - break; - case "WriteIniValues": - actionElement = new Wix.WriteIniValues(); - break; - case "WriteRegistryValues": - actionElement = new Wix.WriteRegistryValues(); - break; - default: - this.core.OnMessage(WixWarnings.UnknownAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); - return null; - } - - if (actionElement != null) - { - this.SequenceStandardAction(actionRow, actionElement); - } - - return actionElement; - } - - /// - /// Applies the condition and sequence to a standard action element based on the action row data. - /// - /// Action row data from the database. - /// Element to be sequenced. - private void SequenceStandardAction(WixActionRow actionRow, Wix.ActionSequenceType actionElement) - { - if (null != actionRow.Condition) - { - actionElement.Content = actionRow.Condition; - } - - if ((null != actionRow.Before || null != actionRow.After) && 0 == actionRow.Sequence) - { - this.core.OnMessage(WixWarnings.DecompiledStandardActionRelativelyScheduledInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); - } - else if (0 < actionRow.Sequence) - { - actionElement.Sequence = actionRow.Sequence; - } - } - - /// - /// Applies the condition and relative sequence to an action element based on the action row data. - /// - /// Action row data from the database. - /// Element to be sequenced. - private static void SequenceRelativeAction(WixActionRow actionRow, Wix.ActionModuleSequenceType actionElement) - { - if (null != actionRow.Condition) - { - actionElement.Content = actionRow.Condition; - } - - if (null != actionRow.Before) - { - actionElement.Before = actionRow.Before; - } - else if (null != actionRow.After) - { - actionElement.After = actionRow.After; - } - else if (0 < actionRow.Sequence) - { - actionElement.Sequence = actionRow.Sequence; - } - } - - /// - /// Ensure that a particular property exists in the decompiled output. - /// - /// The identifier of the property. - /// The property element. - private Wix.Property EnsureProperty(string id) - { - Wix.Property property = (Wix.Property)this.core.GetIndexedElement("Property", id); - - if (null == property) - { - property = new Wix.Property(); - property.Id = id; - - // create a dummy row for indexing - Row row = new Row(null, this.tableDefinitions["Property"]); - row[0] = id; - - this.core.RootElement.AddChild(property); - this.core.IndexElement(row, property); - } - - return property; - } - - /// - /// Finalize decompilation. - /// - /// The collection of all tables. - private void FinalizeDecompile(TableIndexedCollection tables) - { - if (OutputType.PatchCreation == this.outputType) - { - this.FinalizeFamilyFileRangesTable(tables); - } - else - { - this.FinalizeCheckBoxTable(tables); - this.FinalizeComponentTable(tables); - this.FinalizeDialogTable(tables); - this.FinalizeDuplicateMoveFileTables(tables); - this.FinalizeFeatureComponentsTable(tables); - this.FinalizeFileTable(tables); - this.FinalizeMIMETable(tables); - this.FinalizeMsiLockPermissionsExTable(tables); - this.FinalizeLockPermissionsTable(tables); - this.FinalizeProgIdTable(tables); - this.FinalizePropertyTable(tables); - this.FinalizeRemoveFileTable(tables); - this.FinalizeSearchTables(tables); - this.FinalizeUpgradeTable(tables); - this.FinalizeSequenceTables(tables); - this.FinalizeVerbTable(tables); - } - } - - /// - /// Finalize the CheckBox table. - /// - /// The collection of all tables. - /// - /// Enumerates through all the Control rows, looking for controls of type "CheckBox" with - /// a value in the Property column. This is then possibly matched up with a CheckBox row - /// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table. - /// - private void FinalizeCheckBoxTable(TableIndexedCollection tables) - { - // if the user has requested to suppress the UI elements, we have nothing to do - if (this.suppressUI) - { - return; - } - - Table checkBoxTable = tables["CheckBox"]; - Table controlTable = tables["Control"]; - - Hashtable checkBoxes = new Hashtable(); - Hashtable checkBoxProperties = new Hashtable(); - - // index the CheckBox table - if (null != checkBoxTable) - { - foreach (Row row in checkBoxTable.Rows) - { - checkBoxes.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row); - checkBoxProperties.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), false); - } - } - - // enumerate through the Control table, adding CheckBox values where appropriate - if (null != controlTable) - { - foreach (Row row in controlTable.Rows) - { - Wix.Control control = (Wix.Control)this.core.GetIndexedElement(row); - - if ("CheckBox" == Convert.ToString(row[2]) && null != row[8]) - { - Row checkBoxRow = (Row)checkBoxes[row[8]]; - - if (null == checkBoxRow) - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", Convert.ToString(row[8]), "CheckBox")); - } - else - { - // if we've seen this property already, create a reference to it - if (Convert.ToBoolean(checkBoxProperties[row[8]])) - { - control.CheckBoxPropertyRef = Convert.ToString(row[8]); - } - else - { - control.Property = Convert.ToString(row[8]); - checkBoxProperties[row[8]] = true; - } - - if (null != checkBoxRow[1]) - { - control.CheckBoxValue = Convert.ToString(checkBoxRow[1]); - } - } - } - } - } - } - - /// - /// Finalize the Component table. - /// - /// The collection of all tables. - /// - /// Set the keypaths for each component. - /// - private void FinalizeComponentTable(TableIndexedCollection tables) - { - Table componentTable = tables["Component"]; - Table fileTable = tables["File"]; - Table odbcDataSourceTable = tables["ODBCDataSource"]; - Table registryTable = tables["Registry"]; - - // set the component keypaths - if (null != componentTable) - { - foreach (Row row in componentTable.Rows) - { - int attributes = Convert.ToInt32(row[3]); - - if (null == row[5]) - { - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0])); - - component.KeyPath = Wix.YesNoType.yes; - } - else if (MsiInterop.MsidbComponentAttributesRegistryKeyPath == (attributes & MsiInterop.MsidbComponentAttributesRegistryKeyPath)) - { - object registryObject = this.core.GetIndexedElement("Registry", Convert.ToString(row[5])); - - if (null != registryObject) - { - Wix.RegistryValue registryValue = registryObject as Wix.RegistryValue; - - if (null != registryValue) - { - registryValue.KeyPath = Wix.YesNoType.yes; - } - else - { - this.core.OnMessage(WixWarnings.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", Convert.ToString(row[5]))); - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "Registry")); - } - } - else if (MsiInterop.MsidbComponentAttributesODBCDataSource == (attributes & MsiInterop.MsidbComponentAttributesODBCDataSource)) - { - Wix.ODBCDataSource odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement("ODBCDataSource", Convert.ToString(row[5])); - - if (null != odbcDataSource) - { - odbcDataSource.KeyPath = Wix.YesNoType.yes; - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "ODBCDataSource")); - } - } - else - { - Wix.File file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[5])); - - if (null != file) - { - file.KeyPath = Wix.YesNoType.yes; - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "File")); - } - } - } - } - - // add the File children elements - if (null != fileTable) - { - foreach (FileRow fileRow in fileTable.Rows) - { - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", fileRow.Component); - Wix.File file = (Wix.File)this.core.GetIndexedElement(fileRow); - - if (null != component) - { - component.AddChild(file); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component")); - } - } - } - - // add the ODBCDataSource children elements - if (null != odbcDataSourceTable) - { - foreach (Row row in odbcDataSourceTable.Rows) - { - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - Wix.ODBCDataSource odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement(row); - - if (null != component) - { - component.AddChild(odbcDataSource); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - } - } - - // add the Registry children elements - if (null != registryTable) - { - foreach (Row row in registryTable.Rows) - { - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[5])); - Wix.ISchemaElement registryElement = (Wix.ISchemaElement)this.core.GetIndexedElement(row); - - if (null != component) - { - component.AddChild(registryElement); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[5]), "Component")); - } - } - } - } - - /// - /// Finalize the Dialog table. - /// - /// The collection of all tables. - /// - /// Sets the first, default, and cancel control for each dialog and adds all child control - /// elements to the dialog. - /// - private void FinalizeDialogTable(TableIndexedCollection tables) - { - // if the user has requested to suppress the UI elements, we have nothing to do - if (this.suppressUI) - { - return; - } - - Table controlTable = tables["Control"]; - Table dialogTable = tables["Dialog"]; - - Hashtable addedControls = new Hashtable(); - Hashtable controlRows = new Hashtable(); - - // index the rows in the control rows (because we need the Control_Next value) - if (null != controlTable) - { - foreach (Row row in controlTable.Rows) - { - controlRows.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row); - } - } - - if (null != dialogTable) - { - foreach (Row row in dialogTable.Rows) - { - Wix.Dialog dialog = (Wix.Dialog)this.core.GetIndexedElement(row); - string dialogId = Convert.ToString(row[0]); - - Wix.Control control = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[7])); - if (null == control) - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", Convert.ToString(row[7]), "Control")); - } - - // add tabbable controls - while (null != control) - { - Row controlRow = (Row)controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, control.Id)]; - - control.TabSkip = Wix.YesNoType.no; - dialog.AddChild(control); - addedControls.Add(control, null); - - if (null != controlRow[10]) - { - control = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(controlRow[10])); - if (null != control) - { - // looped back to the first control in the dialog - if (addedControls.Contains(control)) - { - control = null; - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", Convert.ToString(controlRow[10]), "Control")); - } - } - else - { - control = null; - } - } - - // set default control - if (null != row[8]) - { - Wix.Control defaultControl = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[8])); - - if (null != defaultControl) - { - defaultControl.Default = Wix.YesNoType.yes; - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(row[8]), "Control")); - } - } - - // set cancel control - if (null != row[9]) - { - Wix.Control cancelControl = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[9])); - - if (null != cancelControl) - { - cancelControl.Cancel = Wix.YesNoType.yes; - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(row[9]), "Control")); - } - } - } - } - - // add the non-tabbable controls to the dialog - if (null != controlTable) - { - foreach (Row row in controlTable.Rows) - { - Wix.Control control = (Wix.Control)this.core.GetIndexedElement(row); - Wix.Dialog dialog = (Wix.Dialog)this.core.GetIndexedElement("Dialog", Convert.ToString(row[0])); - - if (null == dialog) - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Dialog")); - continue; - } - - if (!addedControls.Contains(control)) - { - control.TabSkip = Wix.YesNoType.yes; - dialog.AddChild(control); - } - } - } - } - - /// - /// Finalize the DuplicateFile and MoveFile tables. - /// - /// The collection of all tables. - /// - /// Sets the source/destination property/directory for each DuplicateFile or - /// MoveFile row. - /// - private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables) - { - Table duplicateFileTable = tables["DuplicateFile"]; - Table moveFileTable = tables["MoveFile"]; - - if (null != duplicateFileTable) - { - foreach (Row row in duplicateFileTable.Rows) - { - Wix.CopyFile copyFile = (Wix.CopyFile)this.core.GetIndexedElement(row); - - if (null != row[4]) - { - if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[4]))) - { - copyFile.DestinationDirectory = Convert.ToString(row[4]); - } - else - { - copyFile.DestinationProperty = Convert.ToString(row[4]); - } - } - } - } - - if (null != moveFileTable) - { - foreach (Row row in moveFileTable.Rows) - { - Wix.CopyFile copyFile = (Wix.CopyFile)this.core.GetIndexedElement(row); - - if (null != row[4]) - { - if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[4]))) - { - copyFile.SourceDirectory = Convert.ToString(row[4]); - } - else - { - copyFile.SourceProperty = Convert.ToString(row[4]); - } - } - - if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[5]))) - { - copyFile.DestinationDirectory = Convert.ToString(row[5]); - } - else - { - copyFile.DestinationProperty = Convert.ToString(row[5]); - } - } - } - } - - /// - /// Finalize the FamilyFileRanges table. - /// - /// The collection of all tables. - private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables) - { - Table externalFilesTable = tables["ExternalFiles"]; - Table familyFileRangesTable = tables["FamilyFileRanges"]; - Table targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"]; - - Hashtable usedProtectRanges = new Hashtable(); - - if (null != familyFileRangesTable) - { - foreach (Row row in familyFileRangesTable.Rows) - { - Wix.ProtectRange protectRange = new Wix.ProtectRange(); - - if (null != row[2] && null != row[3]) - { - string[] retainOffsets = (Convert.ToString(row[2])).Split(','); - string[] retainLengths = (Convert.ToString(row[3])).Split(','); - - if (retainOffsets.Length == retainLengths.Length) - { - for (int i = 0; i < retainOffsets.Length; i++) - { - if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal)) - { - protectRange.Offset = Convert.ToInt32(retainOffsets[i].Substring(2), 16); - } - else - { - protectRange.Offset = Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture); - } - - if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal)) - { - protectRange.Length = Convert.ToInt32(retainLengths[i].Substring(2), 16); - } - else - { - protectRange.Length = Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture); - } - } - } - else - { - // TODO: warn - } - } - else if (null != row[2] || null != row[3]) - { - // TODO: warn about mismatch between columns - } - - this.core.IndexElement(row, protectRange); - } - } - - if (null != externalFilesTable) - { - foreach (Row row in externalFilesTable.Rows) - { - Wix.ExternalFile externalFile = (Wix.ExternalFile)this.core.GetIndexedElement(row); - - Wix.ProtectRange protectRange = (Wix.ProtectRange)this.core.GetIndexedElement("FamilyFileRanges", Convert.ToString(row[0]), Convert.ToString(row[1])); - if (null != protectRange) - { - externalFile.AddChild(protectRange); - usedProtectRanges[protectRange] = null; - } - } - } - - if (null != targetFiles_OptionalDataTable) - { - Table targetImagesTable = tables["TargetImages"]; - Table upgradedImagesTable = tables["UpgradedImages"]; - - Hashtable targetImageRows = new Hashtable(); - Hashtable upgradedImagesRows = new Hashtable(); - - // index the TargetImages table - if (null != targetImagesTable) - { - foreach (Row row in targetImagesTable.Rows) - { - targetImageRows.Add(row[0], row); - } - } - - // index the UpgradedImages table - if (null != upgradedImagesTable) - { - foreach (Row row in upgradedImagesTable.Rows) - { - upgradedImagesRows.Add(row[0], row); - } - } - - foreach (Row row in targetFiles_OptionalDataTable.Rows) - { - Wix.TargetFile targetFile = (Wix.TargetFile)this.patchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)]; - - Row targetImageRow = (Row)targetImageRows[row[0]]; - if (null == targetImageRow) - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", Convert.ToString(row[0]), "TargetImages")); - continue; - } - - Row upgradedImagesRow = (Row)upgradedImagesRows[targetImageRow[3]]; - if (null == upgradedImagesRow) - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[3]), "UpgradedImages")); - continue; - } - - Wix.ProtectRange protectRange = (Wix.ProtectRange)this.core.GetIndexedElement("FamilyFileRanges", Convert.ToString(upgradedImagesRow[4]), Convert.ToString(row[1])); - if (null != protectRange) - { - targetFile.AddChild(protectRange); - usedProtectRanges[protectRange] = null; - } - } - } - - if (null != familyFileRangesTable) - { - foreach (Row row in familyFileRangesTable.Rows) - { - Wix.ProtectRange protectRange = (Wix.ProtectRange)this.core.GetIndexedElement(row); - - if (!usedProtectRanges.Contains(protectRange)) - { - Wix.ProtectFile protectFile = new Wix.ProtectFile(); - - protectFile.File = Convert.ToString(row[1]); - - protectFile.AddChild(protectRange); - - Wix.Family family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[0])); - if (null != family) - { - family.AddChild(protectFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, familyFileRangesTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[0]), "ImageFamilies")); - } - } - } - } - } - - /// - /// Finalize the FeatureComponents table. - /// - /// The collection of all tables. - /// - /// Since tables specifying references to the FeatureComponents table have references to - /// the Feature and Component table separately, but not the FeatureComponents table specifically, - /// the FeatureComponents table and primary features must be decompiled during finalization. - /// - private void FinalizeFeatureComponentsTable(TableIndexedCollection tables) - { - Table classTable = tables["Class"]; - Table extensionTable = tables["Extension"]; - Table msiAssemblyTable = tables["MsiAssembly"]; - Table publishComponentTable = tables["PublishComponent"]; - Table shortcutTable = tables["Shortcut"]; - Table typeLibTable = tables["TypeLib"]; - - if (null != classTable) - { - foreach (Row row in classTable.Rows) - { - this.SetPrimaryFeature(row, 11, 2); - } - } - - if (null != extensionTable) - { - foreach (Row row in extensionTable.Rows) - { - this.SetPrimaryFeature(row, 4, 1); - } - } - - if (null != msiAssemblyTable) - { - foreach (Row row in msiAssemblyTable.Rows) - { - this.SetPrimaryFeature(row, 1, 0); - } - } - - if (null != publishComponentTable) - { - foreach (Row row in publishComponentTable.Rows) - { - this.SetPrimaryFeature(row, 4, 2); - } - } - - if (null != shortcutTable) - { - foreach (Row row in shortcutTable.Rows) - { - string target = Convert.ToString(row[4]); - - if (!target.StartsWith("[", StringComparison.Ordinal) && !target.EndsWith("]", StringComparison.Ordinal)) - { - this.SetPrimaryFeature(row, 4, 3); - } - } - } - - if (null != typeLibTable) - { - foreach (Row row in typeLibTable.Rows) - { - this.SetPrimaryFeature(row, 6, 2); - } - } - } - - /// - /// Finalize the File table. - /// - /// The collection of all tables. - /// - /// Sets the source, diskId, and assembly information for each file. - /// - private void FinalizeFileTable(TableIndexedCollection tables) - { - Table fileTable = tables["File"]; - Table mediaTable = tables["Media"]; - Table msiAssemblyTable = tables["MsiAssembly"]; - Table typeLibTable = tables["TypeLib"]; - - // index the media table by media id - RowDictionary mediaRows; - if (null != mediaTable) - { - mediaRows = new RowDictionary(mediaTable); - } - - // set the disk identifiers and sources for files - if (null != fileTable) - { - foreach (FileRow fileRow in fileTable.Rows) - { - Wix.File file = (Wix.File)this.core.GetIndexedElement("File", fileRow.File); - - // Don't bother processing files that are orphaned (and won't show up in the output anyway) - if (null != file.ParentElement) - { - // set the diskid - if (null != mediaTable) - { - foreach (MediaRow mediaRow in mediaTable.Rows) - { - if (fileRow.Sequence <= mediaRow.LastSequence) - { - file.DiskId = Convert.ToString(mediaRow.DiskId); - break; - } - } - } - - // set the source (done here because it requires information from the Directory table) - if (OutputType.Module == this.outputType) - { - file.Source = String.Concat(this.exportFilePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, file.Id, '.', this.modularizationGuid.Substring(1, 36).Replace('-', '_')); - } - else if (Wix.YesNoDefaultType.yes == file.Compressed || (Wix.YesNoDefaultType.no != file.Compressed && this.compressed)) - { - file.Source = String.Concat(this.exportFilePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, file.Id); - } - else // uncompressed - { - string fileName = (null != file.ShortName ? file.ShortName : file.Name); - - if (!this.shortNames && null != file.Name) - { - fileName = file.Name; - } - - if (this.compressed) // uncompressed at the root of the source image - { - file.Source = String.Concat("SourceDir", Path.DirectorySeparatorChar, fileName); - } - else - { - string sourcePath = this.GetSourcePath(file); - - file.Source = Path.Combine(sourcePath, fileName); - } - } - } - } - } - - // set the file assemblies and manifests - if (null != msiAssemblyTable) - { - foreach (Row row in msiAssemblyTable.Rows) - { - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0])); - - if (null == component) - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[0]), "Component")); - } - else - { - foreach (Wix.ISchemaElement element in component.Children) - { - Wix.File file = element as Wix.File; - - if (null != file && Wix.YesNoType.yes == file.KeyPath) - { - if (null != row[2]) - { - file.AssemblyManifest = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - file.AssemblyApplication = Convert.ToString(row[3]); - } - - if (null == row[4] || 0 == Convert.ToInt32(row[4])) - { - file.Assembly = Wix.File.AssemblyType.net; - } - else - { - file.Assembly = Wix.File.AssemblyType.win32; - } - } - } - } - } - } - - // nest the TypeLib elements - if (null != typeLibTable) - { - foreach (Row row in typeLibTable.Rows) - { - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2])); - Wix.TypeLib typeLib = (Wix.TypeLib)this.core.GetIndexedElement(row); - - foreach (Wix.ISchemaElement element in component.Children) - { - Wix.File file = element as Wix.File; - - if (null != file && Wix.YesNoType.yes == file.KeyPath) - { - file.AddChild(typeLib); - } - } - } - } - } - - /// - /// Finalize the MIME table. - /// - /// The collection of all tables. - /// - /// There is a foreign key shared between the MIME and Extension - /// tables so either one would be valid to be decompiled first, so - /// the only safe way to nest the MIME elements is to do it during finalize. - /// - private void FinalizeMIMETable(TableIndexedCollection tables) - { - Table extensionTable = tables["Extension"]; - Table mimeTable = tables["MIME"]; - - Hashtable comExtensions = new Hashtable(); - - if (null != extensionTable) - { - foreach (Row row in extensionTable.Rows) - { - Wix.Extension extension = (Wix.Extension)this.core.GetIndexedElement(row); - - // index the extension - if (!comExtensions.Contains(row[0])) - { - comExtensions.Add(row[0], new ArrayList()); - } - ((ArrayList)comExtensions[row[0]]).Add(extension); - - // set the default MIME element for this extension - if (null != row[3]) - { - Wix.MIME mime = (Wix.MIME)this.core.GetIndexedElement("MIME", Convert.ToString(row[3])); - - if (null != mime) - { - mime.Default = Wix.YesNoType.yes; - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", Convert.ToString(row[3]), "MIME")); - } - } - } - } - - if (null != mimeTable) - { - foreach (Row row in mimeTable.Rows) - { - Wix.MIME mime = (Wix.MIME)this.core.GetIndexedElement(row); - - if (comExtensions.Contains(row[1])) - { - ArrayList extensionElements = (ArrayList)comExtensions[row[1]]; - - foreach (Wix.Extension extension in extensionElements) - { - extension.AddChild(mime); - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", Convert.ToString(row[1]), "Extension")); - } - } - } - } - - /// - /// Finalize the ProgId table. - /// - /// The collection of all tables. - /// - /// Enumerates through all the Class rows, looking for child ProgIds (these are the - /// default ProgIds for a given Class). Then go through the ProgId table and add any - /// remaining ProgIds for each Class. This happens during finalize because there is - /// a circular dependency between the Class and ProgId tables. - /// - private void FinalizeProgIdTable(TableIndexedCollection tables) - { - Table classTable = tables["Class"]; - Table progIdTable = tables["ProgId"]; - Table extensionTable = tables["Extension"]; - Table componentTable = tables["Component"]; - - Hashtable addedProgIds = new Hashtable(); - Hashtable classes = new Hashtable(); - Hashtable components = new Hashtable(); - - // add the default ProgIds for each class (and index the class table) - if (null != classTable) - { - foreach (Row row in classTable.Rows) - { - Wix.Class wixClass = (Wix.Class)this.core.GetIndexedElement(row); - - if (null != row[3]) - { - Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[3])); - - if (null != progId) - { - if (addedProgIds.Contains(progId)) - { - this.core.OnMessage(WixWarnings.TooManyProgIds(row.SourceLineNumbers, Convert.ToString(row[0]), Convert.ToString(row[3]), Convert.ToString(addedProgIds[progId]))); - } - else - { - wixClass.AddChild(progId); - addedProgIds.Add(progId, wixClass.Id); - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", Convert.ToString(row[3]), "ProgId")); - } - } - - // index the Class elements for nesting of ProgId elements (which don't use the full Class primary key) - if (!classes.Contains(wixClass.Id)) - { - classes.Add(wixClass.Id, new ArrayList()); - } - ((ArrayList)classes[wixClass.Id]).Add(wixClass); - } - } - - // add the remaining non-default ProgId entries for each class - if (null != progIdTable) - { - foreach (Row row in progIdTable.Rows) - { - Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement(row); - - if (!addedProgIds.Contains(progId) && null != row[2] && null == progId.ParentElement) - { - ArrayList classElements = (ArrayList)classes[row[2]]; - - if (null != classElements) - { - foreach (Wix.Class wixClass in classElements) - { - wixClass.AddChild(progId); - addedProgIds.Add(progId, wixClass.Id); - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", Convert.ToString(row[2]), "Class")); - } - } - } - } - - if (null != componentTable) - { - foreach (Row row in componentTable.Rows) - { - Wix.Component wixComponent = (Wix.Component)this.core.GetIndexedElement(row); - - // index the Class elements for nesting of ProgId elements (which don't use the full Class primary key) - if (!components.Contains(wixComponent.Id)) - { - components.Add(wixComponent.Id, new ArrayList()); - } - ((ArrayList)components[wixComponent.Id]).Add(wixComponent); - } - } - - // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension - if (null != extensionTable) - { - foreach (Row row in extensionTable.Rows) - { - // ignore the extension if it isn't associated with a progId - if (null == row[2]) - { - continue; - } - - Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[2])); - - // Haven't added the progId yet and it doesn't have a parent progId - if (!addedProgIds.Contains(progId) && null == progId.ParentElement) - { - ArrayList componentElements = (ArrayList)components[row[1]]; - - if (null != componentElements) - { - foreach (Wix.Component wixComponent in componentElements) - { - wixComponent.AddChild(progId); - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - } - } - } - } - - /// - /// Finalize the Property table. - /// - /// The collection of all tables. - /// - /// Removes properties that are generated from other entries. - /// - private void FinalizePropertyTable(TableIndexedCollection tables) - { - Table propertyTable = tables["Property"]; - Table customActionTable = tables["CustomAction"]; - - if (null != propertyTable && null != customActionTable) - { - foreach (Row row in customActionTable.Rows) - { - int bits = Convert.ToInt32(row[1]); - if (MsiInterop.MsidbCustomActionTypeHideTarget == (bits & MsiInterop.MsidbCustomActionTypeHideTarget) && - MsiInterop.MsidbCustomActionTypeInScript == (bits & MsiInterop.MsidbCustomActionTypeInScript)) - { - Wix.Property property = (Wix.Property)this.core.GetIndexedElement("Property", Convert.ToString(row[0])); - - // If no other fields on the property are set we must have created it during link - if (null != property && null == property.Value && Wix.YesNoType.yes != property.Secure && Wix.YesNoType.yes != property.SuppressModularization) - { - this.core.RootElement.RemoveChild(property); - } - } - } - } - } - - /// - /// Finalize the RemoveFile table. - /// - /// The collection of all tables. - /// - /// Sets the directory/property for each RemoveFile row. - /// - private void FinalizeRemoveFileTable(TableIndexedCollection tables) - { - Table removeFileTable = tables["RemoveFile"]; - - if (null != removeFileTable) - { - foreach (Row row in removeFileTable.Rows) - { - bool isDirectory = false; - string property = Convert.ToString(row[3]); - - // determine if the property is actually authored as a directory - if (null != this.core.GetIndexedElement("Directory", property)) - { - isDirectory = true; - } - - Wix.ISchemaElement element = this.core.GetIndexedElement(row); - - Wix.RemoveFile removeFile = element as Wix.RemoveFile; - if (null != removeFile) - { - if (isDirectory) - { - removeFile.Directory = property; - } - else - { - removeFile.Property = property; - } - } - else - { - Wix.RemoveFolder removeFolder = (Wix.RemoveFolder)element; - - if (isDirectory) - { - removeFolder.Directory = property; - } - else - { - removeFolder.Property = property; - } - } - } - } - } - - /// - /// Finalize the LockPermissions table. - /// - /// The collection of all tables. - /// - /// Nests the Permission elements below their parent elements. There are no declared foreign - /// keys for the parents of the LockPermissions table. - /// - private void FinalizeLockPermissionsTable(TableIndexedCollection tables) - { - Table createFolderTable = tables["CreateFolder"]; - Table lockPermissionsTable = tables["LockPermissions"]; - - Hashtable createFolders = new Hashtable(); - - // index the CreateFolder table because the foreign key to this table from the - // LockPermissions table is only part of the primary key of this table - if (null != createFolderTable) - { - foreach (Row row in createFolderTable.Rows) - { - Wix.CreateFolder createFolder = (Wix.CreateFolder)this.core.GetIndexedElement(row); - string directoryId = Convert.ToString(row[0]); - - if (!createFolders.Contains(directoryId)) - { - createFolders.Add(directoryId, new ArrayList()); - } - ((ArrayList)createFolders[directoryId]).Add(createFolder); - } - } - - if (null != lockPermissionsTable) - { - foreach (Row row in lockPermissionsTable.Rows) - { - string id = Convert.ToString(row[0]); - string table = Convert.ToString(row[1]); - - Wix.Permission permission = (Wix.Permission)this.core.GetIndexedElement(row); - - if ("CreateFolder" == table) - { - ArrayList createFolderElements = (ArrayList)createFolders[id]; - - if (null != createFolderElements) - { - foreach (Wix.CreateFolder createFolder in createFolderElements) - { - createFolder.AddChild(permission); - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "LockPermissions", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } - } - else - { - Wix.IParentElement parentElement = (Wix.IParentElement)this.core.GetIndexedElement(table, id); - - if (null != parentElement) - { - parentElement.AddChild(permission); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "LockPermissions", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } - } - } - } - } - - /// - /// Finalize the MsiLockPermissionsEx table. - /// - /// The collection of all tables. - /// - /// Nests the PermissionEx elements below their parent elements. There are no declared foreign - /// keys for the parents of the MsiLockPermissionsEx table. - /// - private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) - { - Table createFolderTable = tables["CreateFolder"]; - Table msiLockPermissionsExTable = tables["MsiLockPermissionsEx"]; - - Hashtable createFolders = new Hashtable(); - - // index the CreateFolder table because the foreign key to this table from the - // MsiLockPermissionsEx table is only part of the primary key of this table - if (null != createFolderTable) - { - foreach (Row row in createFolderTable.Rows) - { - Wix.CreateFolder createFolder = (Wix.CreateFolder)this.core.GetIndexedElement(row); - string directoryId = Convert.ToString(row[0]); - - if (!createFolders.Contains(directoryId)) - { - createFolders.Add(directoryId, new ArrayList()); - } - ((ArrayList)createFolders[directoryId]).Add(createFolder); - } - } - - if (null != msiLockPermissionsExTable) - { - foreach (Row row in msiLockPermissionsExTable.Rows) - { - string id = Convert.ToString(row[1]); - string table = Convert.ToString(row[2]); - - Wix.PermissionEx permissionEx = (Wix.PermissionEx)this.core.GetIndexedElement(row); - - if ("CreateFolder" == table) - { - ArrayList createFolderElements = (ArrayList)createFolders[id]; - - if (null != createFolderElements) - { - foreach (Wix.CreateFolder createFolder in createFolderElements) - { - createFolder.AddChild(permissionEx); - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "MsiLockPermissionsEx", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } - } - else - { - Wix.IParentElement parentElement = (Wix.IParentElement)this.core.GetIndexedElement(table, id); - - if (null != parentElement) - { - parentElement.AddChild(permissionEx); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "MsiLockPermissionsEx", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } - } - } - } - } - - /// - /// Finalize the search tables. - /// - /// The collection of all tables. - /// Does all the complex linking required for the search tables. - private void FinalizeSearchTables(TableIndexedCollection tables) - { - Table appSearchTable = tables["AppSearch"]; - Table ccpSearchTable = tables["CCPSearch"]; - Table drLocatorTable = tables["DrLocator"]; - - Hashtable appSearches = new Hashtable(); - Hashtable ccpSearches = new Hashtable(); - Hashtable drLocators = new Hashtable(); - Hashtable locators = new Hashtable(); - Hashtable usedSearchElements = new Hashtable(); - ArrayList unusedSearchElements = new ArrayList(); - - Wix.ComplianceCheck complianceCheck = null; - - // index the AppSearch table by signatures - if (null != appSearchTable) - { - foreach (Row row in appSearchTable.Rows) - { - string property = Convert.ToString(row[0]); - string signature = Convert.ToString(row[1]); - - if (!appSearches.Contains(signature)) - { - appSearches.Add(signature, new StringCollection()); - } - - ((StringCollection)appSearches[signature]).Add(property); - } - } - - // index the CCPSearch table by signatures - if (null != ccpSearchTable) - { - foreach (Row row in ccpSearchTable.Rows) - { - string signature = Convert.ToString(row[0]); - - if (!ccpSearches.Contains(signature)) - { - ccpSearches.Add(signature, new StringCollection()); - } - - ((StringCollection)ccpSearches[signature]).Add(null); - - if (null == complianceCheck && !appSearches.Contains(signature)) - { - complianceCheck = new Wix.ComplianceCheck(); - this.core.RootElement.AddChild(complianceCheck); - } - } - } - - // index the directory searches by their search elements (to get back the original row) - if (null != drLocatorTable) - { - foreach (Row row in drLocatorTable.Rows) - { - drLocators.Add(this.core.GetIndexedElement(row), row); - } - } - - // index the locator tables by their signatures - string[] locatorTableNames = new string[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" }; - foreach (string locatorTableName in locatorTableNames) - { - Table locatorTable = tables[locatorTableName]; - - if (null != locatorTable) - { - foreach (Row row in locatorTable.Rows) - { - string signature = Convert.ToString(row[0]); - - if (!locators.Contains(signature)) - { - locators.Add(signature, new ArrayList()); - } - - ((ArrayList)locators[signature]).Add(row); - } - } - } - - // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef) - foreach (ArrayList locatorRows in locators.Values) - { - int firstDrLocator = -1; - - for (int i = 0; i < locatorRows.Count; i++) - { - Row locatorRow = (Row)locatorRows[i]; - - if ("DrLocator" == locatorRow.TableDefinition.Name) - { - if (-1 == firstDrLocator) - { - firstDrLocator = i; - } - - if ("CCP_DRIVE" == Convert.ToString(locatorRow[1])) - { - locatorRows.RemoveAt(i); - locatorRows.Insert(firstDrLocator, locatorRow); - break; - } - } - } - } - - foreach (string signature in locators.Keys) - { - ArrayList locatorRows = (ArrayList)locators[signature]; - ArrayList signatureSearchElements = new ArrayList(); - - foreach (Row locatorRow in locatorRows) - { - bool used = true; - Wix.ISchemaElement searchElement = this.core.GetIndexedElement(locatorRow); - - if ("Signature" == locatorRow.TableDefinition.Name && 0 < signatureSearchElements.Count) - { - foreach (Wix.IParentElement searchParentElement in signatureSearchElements) - { - if (!usedSearchElements.Contains(searchElement)) - { - searchParentElement.AddChild(searchElement); - usedSearchElements[searchElement] = null; - } - else - { - Wix.FileSearchRef fileSearchRef = new Wix.FileSearchRef(); - - fileSearchRef.Id = signature; - - searchParentElement.AddChild(fileSearchRef); - } - } - } - else if ("DrLocator" == locatorRow.TableDefinition.Name && null != locatorRow[1]) - { - string parentSignature = Convert.ToString(locatorRow[1]); - - if ("CCP_DRIVE" == parentSignature) - { - if (appSearches.Contains(signature)) - { - StringCollection appSearchPropertyIds = (StringCollection)appSearches[signature]; - - foreach (string propertyId in appSearchPropertyIds) - { - Wix.Property property = this.EnsureProperty(propertyId); - Wix.ComplianceDrive complianceDrive = null; - - if (ccpSearches.Contains(signature)) - { - property.ComplianceCheck = Wix.YesNoType.yes; - } - - foreach (Wix.ISchemaElement element in property.Children) - { - complianceDrive = element as Wix.ComplianceDrive; - if (null != complianceDrive) - { - break; - } - } - - if (null == complianceDrive) - { - complianceDrive = new Wix.ComplianceDrive(); - property.AddChild(complianceDrive); - } - - if (!usedSearchElements.Contains(searchElement)) - { - complianceDrive.AddChild(searchElement); - usedSearchElements[searchElement] = null; - } - else - { - Wix.DirectorySearchRef directorySearchRef = new Wix.DirectorySearchRef(); - - directorySearchRef.Id = signature; - - if (null != locatorRow[1]) - { - directorySearchRef.Parent = Convert.ToString(locatorRow[1]); - } - - if (null != locatorRow[2]) - { - directorySearchRef.Path = Convert.ToString(locatorRow[2]); - } - - complianceDrive.AddChild(directorySearchRef); - signatureSearchElements.Add(directorySearchRef); - } - } - } - else if (ccpSearches.Contains(signature)) - { - Wix.ComplianceDrive complianceDrive = null; - - foreach (Wix.ISchemaElement element in complianceCheck.Children) - { - complianceDrive = element as Wix.ComplianceDrive; - if (null != complianceDrive) - { - break; - } - } - - if (null == complianceDrive) - { - complianceDrive = new Wix.ComplianceDrive(); - complianceCheck.AddChild(complianceDrive); - } - - if (!usedSearchElements.Contains(searchElement)) - { - complianceDrive.AddChild(searchElement); - usedSearchElements[searchElement] = null; - } - else - { - Wix.DirectorySearchRef directorySearchRef = new Wix.DirectorySearchRef(); - - directorySearchRef.Id = signature; - - if (null != locatorRow[1]) - { - directorySearchRef.Parent = Convert.ToString(locatorRow[1]); - } - - if (null != locatorRow[2]) - { - directorySearchRef.Path = Convert.ToString(locatorRow[2]); - } - - complianceDrive.AddChild(directorySearchRef); - signatureSearchElements.Add(directorySearchRef); - } - } - } - else - { - bool usedDrLocator = false; - ArrayList parentLocatorRows = (ArrayList)locators[parentSignature]; - - if (null != parentLocatorRows) - { - foreach (Row parentLocatorRow in parentLocatorRows) - { - if ("DrLocator" == parentLocatorRow.TableDefinition.Name) - { - Wix.IParentElement parentSearchElement = (Wix.IParentElement)this.core.GetIndexedElement(parentLocatorRow); - - if (parentSearchElement.Children.GetEnumerator().MoveNext()) - { - Row parentDrLocatorRow = (Row)drLocators[parentSearchElement]; - Wix.DirectorySearchRef directorySeachRef = new Wix.DirectorySearchRef(); - - directorySeachRef.Id = parentSignature; - - if (null != parentDrLocatorRow[1]) - { - directorySeachRef.Parent = Convert.ToString(parentDrLocatorRow[1]); - } - - if (null != parentDrLocatorRow[2]) - { - directorySeachRef.Path = Convert.ToString(parentDrLocatorRow[2]); - } - - parentSearchElement = directorySeachRef; - unusedSearchElements.Add(directorySeachRef); - } - - if (!usedSearchElements.Contains(searchElement)) - { - parentSearchElement.AddChild(searchElement); - usedSearchElements[searchElement] = null; - usedDrLocator = true; - } - else - { - Wix.DirectorySearchRef directorySearchRef = new Wix.DirectorySearchRef(); - - directorySearchRef.Id = signature; - - directorySearchRef.Parent = parentSignature; - - if (null != locatorRow[2]) - { - directorySearchRef.Path = Convert.ToString(locatorRow[2]); - } - - parentSearchElement.AddChild(searchElement); - usedDrLocator = true; - } - } - } - - // keep track of unused DrLocator rows - if (!usedDrLocator) - { - unusedSearchElements.Add(searchElement); - } - } - else - { - // TODO: warn - } - } - } - else if (appSearches.Contains(signature)) - { - StringCollection appSearchPropertyIds = (StringCollection)appSearches[signature]; - - foreach (string propertyId in appSearchPropertyIds) - { - Wix.Property property = this.EnsureProperty(propertyId); - - if (ccpSearches.Contains(signature)) - { - property.ComplianceCheck = Wix.YesNoType.yes; - } - - if (!usedSearchElements.Contains(searchElement)) - { - property.AddChild(searchElement); - usedSearchElements[searchElement] = null; - } - else if ("RegLocator" == locatorRow.TableDefinition.Name) - { - Wix.RegistrySearchRef registrySearchRef = new Wix.RegistrySearchRef(); - - registrySearchRef.Id = signature; - - property.AddChild(registrySearchRef); - signatureSearchElements.Add(registrySearchRef); - } - else - { - // TODO: warn about unavailable Ref element - } - } - } - else if (ccpSearches.Contains(signature)) - { - if (!usedSearchElements.Contains(searchElement)) - { - complianceCheck.AddChild(searchElement); - usedSearchElements[searchElement] = null; - } - else if ("RegLocator" == locatorRow.TableDefinition.Name) - { - Wix.RegistrySearchRef registrySearchRef = new Wix.RegistrySearchRef(); - - registrySearchRef.Id = signature; - - complianceCheck.AddChild(registrySearchRef); - signatureSearchElements.Add(registrySearchRef); - } - else - { - // TODO: warn about unavailable Ref element - } - } - else - { - if ("DrLocator" == locatorRow.TableDefinition.Name) - { - unusedSearchElements.Add(searchElement); - } - else - { - // TODO: warn - used = false; - } - } - - // keep track of the search elements for this signature so that nested searches go in the proper parents - if (used) - { - signatureSearchElements.Add(searchElement); - } - } - } - - foreach (Wix.IParentElement unusedSearchElement in unusedSearchElements) - { - bool used = false; - - foreach (Wix.ISchemaElement schemaElement in unusedSearchElement.Children) - { - Wix.DirectorySearch directorySearch = schemaElement as Wix.DirectorySearch; - if (null != directorySearch) - { - StringCollection appSearchProperties = (StringCollection)appSearches[directorySearch.Id]; - - Wix.ISchemaElement unusedSearchSchemaElement = unusedSearchElement as Wix.ISchemaElement; - if (null != appSearchProperties) - { - Wix.Property property = this.EnsureProperty(appSearchProperties[0]); - - property.AddChild(unusedSearchSchemaElement); - used = true; - break; - } - else if (ccpSearches.Contains(directorySearch.Id)) - { - complianceCheck.AddChild(unusedSearchSchemaElement); - used = true; - break; - } - else - { - // TODO: warn - } - } - } - - if (!used) - { - // TODO: warn - } - } - } - - /// - /// Finalize the sequence tables. - /// - /// The collection of all tables. - /// - /// Creates the sequence elements. Occurs during finalization because its - /// not known if sequences refer to custom actions or dialogs during decompilation. - /// - private void FinalizeSequenceTables(TableIndexedCollection tables) - { - // finalize the normal sequence tables - if (OutputType.Product == this.outputType && !this.treatProductAsModule) - { - foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) - { - // if suppressing UI elements, skip UI-related sequence tables - if (this.suppressUI && ("AdminUISequence" == sequenceTable.ToString() || "InstallUISequence" == sequenceTable.ToString())) - { - continue; - } - - Table actionsTable = new Table(null, this.tableDefinitions["WixAction"]); - Table table = tables[sequenceTable.ToString()]; - - if (null != table) - { - ArrayList actionRows = new ArrayList(); - bool needAbsoluteScheduling = this.suppressRelativeActionSequencing; - WixActionRowCollection nonSequencedActionRows = new WixActionRowCollection(); - WixActionRowCollection suppressedRelativeActionRows = new WixActionRowCollection(); - - // create a sorted array of actions in this table - foreach (Row row in table.Rows) - { - WixActionRow actionRow = (WixActionRow)actionsTable.CreateRow(null); - - actionRow.Action = Convert.ToString(row[0]); - - if (null != row[1]) - { - actionRow.Condition = Convert.ToString(row[1]); - } - - actionRow.Sequence = Convert.ToInt32(row[2]); - - actionRow.SequenceTable = sequenceTable; - - actionRows.Add(actionRow); - } - actionRows.Sort(); - - for (int i = 0; i < actionRows.Count && !needAbsoluteScheduling; i++) - { - WixActionRow actionRow = (WixActionRow)actionRows[i]; - WixActionRow standardActionRow = this.standardActions[actionRow.SequenceTable, actionRow.Action]; - - // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions - if ("AppSearch" == actionRow.Action || null == standardActionRow || actionRow.Condition != standardActionRow.Condition) - { - WixActionRow previousActionRow = null; - WixActionRow nextActionRow = null; - - // find the previous action row if there is one - if (0 <= i - 1) - { - previousActionRow = (WixActionRow)actionRows[i - 1]; - } - - // find the next action row if there is one - if (actionRows.Count > i + 1) - { - nextActionRow = (WixActionRow)actionRows[i + 1]; - } - - // the logic for setting the before or after attribute for an action: - // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced. - // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it. - // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it. - // 4. If this action is not standard and the previous action is 1 sequence number lower and does not occur before this action, this action occurs after it. - // 5. If this action is not standard and the previous action does not have the same sequence number and the next action is 1 sequence number higher, this action occurs before it. - // 6. If this action is AppSearch and has all standard information, ignore it. - // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information. - // 8. Everything must be absolutely sequenced. - if ((null != previousActionRow && actionRow.Sequence == previousActionRow.Sequence) || (null != nextActionRow && actionRow.Sequence == nextActionRow.Sequence)) - { - needAbsoluteScheduling = true; - } - else if (null != nextActionRow && null != this.standardActions[sequenceTable, nextActionRow.Action] && actionRow.Sequence + 1 == nextActionRow.Sequence) - { - actionRow.Before = nextActionRow.Action; - } - else if (null != previousActionRow && null != this.standardActions[sequenceTable, previousActionRow.Action] && actionRow.Sequence - 1 == previousActionRow.Sequence) - { - actionRow.After = previousActionRow.Action; - } - else if (null == standardActionRow && null != previousActionRow && actionRow.Sequence - 1 == previousActionRow.Sequence && previousActionRow.Before != actionRow.Action) - { - actionRow.After = previousActionRow.Action; - } - else if (null == standardActionRow && null != previousActionRow && actionRow.Sequence != previousActionRow.Sequence && null != nextActionRow && actionRow.Sequence + 1 == nextActionRow.Sequence) - { - actionRow.Before = nextActionRow.Action; - } - else if ("AppSearch" == actionRow.Action && null != standardActionRow && actionRow.Sequence == standardActionRow.Sequence && actionRow.Condition == standardActionRow.Condition) - { - // ignore an AppSearch row which has the WiX standard sequence and a standard condition - } - else if (null != standardActionRow && actionRow.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers - { - nonSequencedActionRows.Add(actionRow); - } - else if (0 < actionRow.Sequence) - { - needAbsoluteScheduling = true; - } - } - else - { - suppressedRelativeActionRows.Add(actionRow); - } - } - - // create the actions now that we know if they must be absolutely or relatively scheduled - foreach (WixActionRow actionRow in actionRows) - { - if (needAbsoluteScheduling) - { - // remove any before/after information to ensure this is absolutely sequenced - actionRow.Before = null; - actionRow.After = null; - } - else if (nonSequencedActionRows.Contains(actionRow.SequenceTable, actionRow.Action)) - { - // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after) - actionRow.Sequence = 0; - } - else if (suppressedRelativeActionRows.Contains(actionRow.SequenceTable, actionRow.Action)) - { - // skip the suppressed relatively scheduled action rows - continue; - } - - // create the action element - this.CreateActionElement(actionRow); - } - } - } - } - else if (OutputType.Module == this.outputType || this.treatProductAsModule) // finalize the Module sequence tables - { - foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) - { - // if suppressing UI elements, skip UI-related sequence tables - if (this.suppressUI && ("AdminUISequence" == sequenceTable.ToString() || "InstallUISequence" == sequenceTable.ToString())) - { - continue; - } - - Table actionsTable = new Table(null, this.tableDefinitions["WixAction"]); - Table table = tables[String.Concat("Module", sequenceTable.ToString())]; - - if (null != table) - { - foreach (Row row in table.Rows) - { - WixActionRow actionRow = (WixActionRow)actionsTable.CreateRow(null); - - actionRow.Action = Convert.ToString(row[0]); - - if (null != row[1]) - { - actionRow.Sequence = Convert.ToInt32(row[1]); - } - - if (null != row[2] && null != row[3]) - { - switch (Convert.ToInt32(row[3])) - { - case 0: - actionRow.Before = Convert.ToString(row[2]); - break; - case 1: - actionRow.After = Convert.ToString(row[2]); - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3])); - break; - } - } - - if (null != row[4]) - { - actionRow.Condition = Convert.ToString(row[4]); - } - - actionRow.SequenceTable = sequenceTable; - - // create action elements for non-standard actions - if (null == this.standardActions[actionRow.SequenceTable, actionRow.Action] || null != actionRow.After || null != actionRow.Before) - { - this.CreateActionElement(actionRow); - } - } - } - } - } - } - - /// - /// Finalize the Upgrade table. - /// - /// The collection of all tables. - /// - /// Decompile the rows from the Upgrade and LaunchCondition tables - /// created by the MajorUpgrade element. - /// - private void FinalizeUpgradeTable(TableIndexedCollection tables) - { - Table launchConditionTable = tables["LaunchCondition"]; - Table upgradeTable = tables["Upgrade"]; - string downgradeErrorMessage = null; - string disallowUpgradeErrorMessage = null; - Wix.MajorUpgrade majorUpgrade = new Wix.MajorUpgrade(); - - // find the DowngradePreventedCondition launch condition message - if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count) - { - foreach (Row launchRow in launchConditionTable.Rows) - { - if (Compiler.DowngradePreventedCondition == Convert.ToString(launchRow[0])) - { - downgradeErrorMessage = Convert.ToString(launchRow[1]); - } - else if (Compiler.UpgradePreventedCondition == Convert.ToString(launchRow[0])) - { - disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]); - } - } - } - - if (null != upgradeTable && 0 < upgradeTable.Rows.Count) - { - bool hasMajorUpgrade = false; - - foreach (Row row in upgradeTable.Rows) - { - UpgradeRow upgradeRow = (UpgradeRow)row; - - if (Compiler.UpgradeDetectedProperty == upgradeRow.ActionProperty) - { - hasMajorUpgrade = true; - int attr = upgradeRow.Attributes; - string removeFeatures = upgradeRow.Remove; - - if (MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive == (attr & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive)) - { - majorUpgrade.AllowSameVersionUpgrades = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbUpgradeAttributesMigrateFeatures != (attr & MsiInterop.MsidbUpgradeAttributesMigrateFeatures)) - { - majorUpgrade.MigrateFeatures = Wix.YesNoType.no; - } - - if (MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure)) - { - majorUpgrade.IgnoreRemoveFailure = Wix.YesNoType.yes; - } - - if (!String.IsNullOrEmpty(removeFeatures)) - { - majorUpgrade.RemoveFeatures = removeFeatures; - } - } - else if (Compiler.DowngradeDetectedProperty == upgradeRow.ActionProperty) - { - hasMajorUpgrade = true; - majorUpgrade.DowngradeErrorMessage = downgradeErrorMessage; - } - } - - if (hasMajorUpgrade) - { - if (String.IsNullOrEmpty(downgradeErrorMessage)) - { - majorUpgrade.AllowDowngrades = Wix.YesNoType.yes; - } - - if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage)) - { - majorUpgrade.Disallow = Wix.YesNoType.yes; - majorUpgrade.DisallowUpgradeErrorMessage = disallowUpgradeErrorMessage; - } - - majorUpgrade.Schedule = DetermineMajorUpgradeScheduling(tables); - this.core.RootElement.AddChild(majorUpgrade); - } - } - } - - /// - /// Finalize the Verb table. - /// - /// The collection of all tables. - /// - /// The Extension table is a foreign table for the Verb table, but the - /// foreign key is only part of the primary key of the Extension table, - /// so it needs special logic to be nested properly. - /// - private void FinalizeVerbTable(TableIndexedCollection tables) - { - Table extensionTable = tables["Extension"]; - Table verbTable = tables["Verb"]; - - Hashtable extensionElements = new Hashtable(); - - if (null != extensionTable) - { - foreach (Row row in extensionTable.Rows) - { - Wix.Extension extension = (Wix.Extension)this.core.GetIndexedElement(row); - - if (!extensionElements.Contains(row[0])) - { - extensionElements.Add(row[0], new ArrayList()); - } - - ((ArrayList)extensionElements[row[0]]).Add(extension); - } - } - - if (null != verbTable) - { - foreach (Row row in verbTable.Rows) - { - Wix.Verb verb = (Wix.Verb)this.core.GetIndexedElement(row); - - ArrayList extensionsArray = (ArrayList)extensionElements[row[0]]; - if (null != extensionsArray) - { - foreach (Wix.Extension extension in extensionsArray) - { - extension.AddChild(verb); - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", Convert.ToString(row[0]), "Extension")); - } - } - } - } - - /// - /// Get the path to a file in the source image. - /// - /// The file. - /// The path to the file in the source image. - private string GetSourcePath(Wix.File file) - { - StringBuilder sourcePath = new StringBuilder(); - - Wix.Component component = (Wix.Component)file.ParentElement; - - for (Wix.Directory directory = (Wix.Directory)component.ParentElement; null != directory; directory = directory.ParentElement as Wix.Directory) - { - string name; - - if (!this.shortNames && null != directory.SourceName) - { - name = directory.SourceName; - } - else if (null != directory.ShortSourceName) - { - name = directory.ShortSourceName; - } - else if (!this.shortNames || null == directory.ShortName) - { - name = directory.Name; - } - else - { - name = directory.ShortName; - } - - if (0 == sourcePath.Length) - { - sourcePath.Append(name); - } - else - { - sourcePath.Insert(0, Path.DirectorySeparatorChar); - sourcePath.Insert(0, name); - } - } - - return sourcePath.ToString(); - } - - /// - /// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames). - /// - /// The name of the table to resolve. - /// The unsorted table names. - /// The sorted table names. - private void ResolveTableDependencies(string tableName, SortedList unsortedTableNames, StringCollection sortedTableNames) - { - unsortedTableNames.Remove(tableName); - - foreach (ColumnDefinition columnDefinition in this.tableDefinitions[tableName].Columns) - { - // no dependency to resolve because this column doesn't reference another table - if (null == columnDefinition.KeyTable) - { - continue; - } - - foreach (string keyTable in columnDefinition.KeyTable.Split(';')) - { - if (tableName == keyTable) - { - continue; // self-referencing dependency - } - else if (sortedTableNames.Contains(keyTable)) - { - continue; // dependent table has already been sorted - } - else if (!this.tableDefinitions.Contains(keyTable)) - { - this.core.OnMessage(WixErrors.MissingTableDefinition(keyTable)); - } - else if (unsortedTableNames.Contains(keyTable)) - { - this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames); - } - else - { - // found a circular dependency, so ignore it (this assumes that the tables will - // use a finalize method to nest their elements since the ordering will not be - // deterministic - } - } - } - - sortedTableNames.Add(tableName); - } - - /// - /// Get the names of the tables to process in the order they should be processed, according to their dependencies. - /// - /// A StringCollection containing the ordered table names. - private StringCollection GetSortedTableNames() - { - StringCollection sortedTableNames = new StringCollection(); - SortedList unsortedTableNames = new SortedList(); - - // index the table names - foreach (TableDefinition tableDefinition in this.tableDefinitions) - { - unsortedTableNames.Add(tableDefinition.Name, tableDefinition.Name); - } - - // resolve the dependencies for each table - while (0 < unsortedTableNames.Count) - { - this.ResolveTableDependencies(Convert.ToString(unsortedTableNames.GetByIndex(0)), unsortedTableNames, sortedTableNames); - } - - return sortedTableNames; - } - - /// - /// Initialize decompilation. - /// - /// The collection of all tables. - private void InitializeDecompile(TableIndexedCollection tables) - { - // reset all the state information - this.compressed = false; - this.patchTargetFiles.Clear(); - this.sequenceElements.Clear(); - this.shortNames = false; - - // set the codepage if its not neutral (0) - if (0 != this.codepage) - { - switch (this.outputType) - { - case OutputType.Module: - ((Wix.Module)this.core.RootElement).Codepage = this.codepage.ToString(CultureInfo.InvariantCulture); - break; - case OutputType.PatchCreation: - ((Wix.PatchCreation)this.core.RootElement).Codepage = this.codepage.ToString(CultureInfo.InvariantCulture); - break; - case OutputType.Product: - ((Wix.Product)this.core.RootElement).Codepage = this.codepage.ToString(CultureInfo.InvariantCulture); - break; - } - } - - // index the rows from the extension libraries - Dictionary> indexedExtensionTables = new Dictionary>(); - foreach (IDecompilerExtension extension in this.extensions) - { - // Get the optional library from the extension with the rows to be removed. - Library library = extension.GetLibraryToRemove(this.tableDefinitions); - if (null != library) - { - foreach (Section section in library.Sections) - { - foreach (Table table in section.Tables) - { - foreach (Row row in table.Rows) - { - string primaryKey; - string tableName; - - // the Actions table needs to be handled specially - if ("WixAction" == table.Name) - { - primaryKey = Convert.ToString(row[1]); - - if (OutputType.Module == this.outputType) - { - tableName = String.Concat("Module", Convert.ToString(row[0])); - } - else - { - tableName = Convert.ToString(row[0]); - } - } - else - { - primaryKey = row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter); - tableName = table.Name; - } - - if (null != primaryKey) - { - HashSet indexedExtensionRows; - if (!indexedExtensionTables.TryGetValue(tableName, out indexedExtensionRows)) - { - indexedExtensionRows = new HashSet(); - indexedExtensionTables.Add(tableName, indexedExtensionRows); - } - - indexedExtensionRows.Add(primaryKey); - } - } - } - } - } - } - - // remove the rows from the extension libraries (to allow full round-tripping) - foreach (var kvp in indexedExtensionTables) - { - string tableName = kvp.Key; - HashSet indexedExtensionRows = kvp.Value; - - Table table = tables[tableName]; - if (null != table) - { - RowDictionary originalRows = new RowDictionary(table); - - // remove the original rows so that they can be added back if they should remain - table.Rows.Clear(); - - foreach (Row row in originalRows.Values) - { - if (!indexedExtensionRows.Contains(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter))) - { - table.Rows.Add(row); - } - } - } - } - } - - /// - /// Decompile the tables. - /// - /// The output being decompiled. - private void DecompileTables(Output output) - { - StringCollection sortedTableNames = this.GetSortedTableNames(); - - foreach (string tableName in sortedTableNames) - { - Table table = output.Tables[tableName]; - - // table does not exist in this database or should not be decompiled - if (null == table || !this.DecompilableTable(output, tableName)) - { - continue; - } - - this.core.OnMessage(WixVerboses.DecompilingTable(table.Name)); - - // empty tables may be kept with EnsureTable if the user set the proper option - if (0 == table.Rows.Count && this.suppressDroppingEmptyTables) - { - Wix.EnsureTable ensureTable = new Wix.EnsureTable(); - ensureTable.Id = table.Name; - this.core.RootElement.AddChild(ensureTable); - } - - switch (table.Name) - { - case "_SummaryInformation": - this.Decompile_SummaryInformationTable(table); - break; - case "AdminExecuteSequence": - case "AdminUISequence": - case "AdvtExecuteSequence": - case "InstallExecuteSequence": - case "InstallUISequence": - case "ModuleAdminExecuteSequence": - case "ModuleAdminUISequence": - case "ModuleAdvtExecuteSequence": - case "ModuleInstallExecuteSequence": - case "ModuleInstallUISequence": - // handled in FinalizeSequenceTables - break; - case "ActionText": - this.DecompileActionTextTable(table); - break; - case "AdvtUISequence": - this.core.OnMessage(WixWarnings.DeprecatedTable(table.Name)); - break; - case "AppId": - this.DecompileAppIdTable(table); - break; - case "AppSearch": - // handled in FinalizeSearchTables - break; - case "BBControl": - this.DecompileBBControlTable(table); - break; - case "Billboard": - this.DecompileBillboardTable(table); - break; - case "Binary": - this.DecompileBinaryTable(table); - break; - case "BindImage": - this.DecompileBindImageTable(table); - break; - case "CCPSearch": - // handled in FinalizeSearchTables - break; - case "CheckBox": - // handled in FinalizeCheckBoxTable - break; - case "Class": - this.DecompileClassTable(table); - break; - case "ComboBox": - this.DecompileComboBoxTable(table); - break; - case "Control": - this.DecompileControlTable(table); - break; - case "ControlCondition": - this.DecompileControlConditionTable(table); - break; - case "ControlEvent": - this.DecompileControlEventTable(table); - break; - case "CreateFolder": - this.DecompileCreateFolderTable(table); - break; - case "CustomAction": - this.DecompileCustomActionTable(table); - break; - case "CompLocator": - this.DecompileCompLocatorTable(table); - break; - case "Complus": - this.DecompileComplusTable(table); - break; - case "Component": - this.DecompileComponentTable(table); - break; - case "Condition": - this.DecompileConditionTable(table); - break; - case "Dialog": - this.DecompileDialogTable(table); - break; - case "Directory": - this.DecompileDirectoryTable(table); - break; - case "DrLocator": - this.DecompileDrLocatorTable(table); - break; - case "DuplicateFile": - this.DecompileDuplicateFileTable(table); - break; - case "Environment": - this.DecompileEnvironmentTable(table); - break; - case "Error": - this.DecompileErrorTable(table); - break; - case "EventMapping": - this.DecompileEventMappingTable(table); - break; - case "Extension": - this.DecompileExtensionTable(table); - break; - case "ExternalFiles": - this.DecompileExternalFilesTable(table); - break; - case "FamilyFileRanges": - // handled in FinalizeFamilyFileRangesTable - break; - case "Feature": - this.DecompileFeatureTable(table); - break; - case "FeatureComponents": - this.DecompileFeatureComponentsTable(table); - break; - case "File": - this.DecompileFileTable(table); - break; - case "FileSFPCatalog": - this.DecompileFileSFPCatalogTable(table); - break; - case "Font": - this.DecompileFontTable(table); - break; - case "Icon": - this.DecompileIconTable(table); - break; - case "ImageFamilies": - this.DecompileImageFamiliesTable(table); - break; - case "IniFile": - this.DecompileIniFileTable(table); - break; - case "IniLocator": - this.DecompileIniLocatorTable(table); - break; - case "IsolatedComponent": - this.DecompileIsolatedComponentTable(table); - break; - case "LaunchCondition": - this.DecompileLaunchConditionTable(table); - break; - case "ListBox": - this.DecompileListBoxTable(table); - break; - case "ListView": - this.DecompileListViewTable(table); - break; - case "LockPermissions": - this.DecompileLockPermissionsTable(table); - break; - case "Media": - this.DecompileMediaTable(table); - break; - case "MIME": - this.DecompileMIMETable(table); - break; - case "ModuleAdvtUISequence": - this.core.OnMessage(WixWarnings.DeprecatedTable(table.Name)); - break; - case "ModuleComponents": - // handled by DecompileComponentTable (since the ModuleComponents table - // rows are created by nesting components under the Module element) - break; - case "ModuleConfiguration": - this.DecompileModuleConfigurationTable(table); - break; - case "ModuleDependency": - this.DecompileModuleDependencyTable(table); - break; - case "ModuleExclusion": - this.DecompileModuleExclusionTable(table); - break; - case "ModuleIgnoreTable": - this.DecompileModuleIgnoreTableTable(table); - break; - case "ModuleSignature": - this.DecompileModuleSignatureTable(table); - break; - case "ModuleSubstitution": - this.DecompileModuleSubstitutionTable(table); - break; - case "MoveFile": - this.DecompileMoveFileTable(table); - break; - case "MsiAssembly": - // handled in FinalizeFileTable - break; - case "MsiDigitalCertificate": - this.DecompileMsiDigitalCertificateTable(table); - break; - case "MsiDigitalSignature": - this.DecompileMsiDigitalSignatureTable(table); - break; - case "MsiEmbeddedChainer": - this.DecompileMsiEmbeddedChainerTable(table); - break; - case "MsiEmbeddedUI": - this.DecompileMsiEmbeddedUITable(table); - break; - case "MsiLockPermissionsEx": - this.DecompileMsiLockPermissionsExTable(table); - break; - case "MsiPackageCertificate": - this.DecompileMsiPackageCertificateTable(table); - break; - case "MsiPatchCertificate": - this.DecompileMsiPatchCertificateTable(table); - break; - case "MsiShortcutProperty": - this.DecompileMsiShortcutPropertyTable(table); - break; - case "ODBCAttribute": - this.DecompileODBCAttributeTable(table); - break; - case "ODBCDataSource": - this.DecompileODBCDataSourceTable(table); - break; - case "ODBCDriver": - this.DecompileODBCDriverTable(table); - break; - case "ODBCSourceAttribute": - this.DecompileODBCSourceAttributeTable(table); - break; - case "ODBCTranslator": - this.DecompileODBCTranslatorTable(table); - break; - case "PatchMetadata": - this.DecompilePatchMetadataTable(table); - break; - case "PatchSequence": - this.DecompilePatchSequenceTable(table); - break; - case "ProgId": - this.DecompileProgIdTable(table); - break; - case "Properties": - this.DecompilePropertiesTable(table); - break; - case "Property": - this.DecompilePropertyTable(table); - break; - case "PublishComponent": - this.DecompilePublishComponentTable(table); - break; - case "RadioButton": - this.DecompileRadioButtonTable(table); - break; - case "Registry": - this.DecompileRegistryTable(table); - break; - case "RegLocator": - this.DecompileRegLocatorTable(table); - break; - case "RemoveFile": - this.DecompileRemoveFileTable(table); - break; - case "RemoveIniFile": - this.DecompileRemoveIniFileTable(table); - break; - case "RemoveRegistry": - this.DecompileRemoveRegistryTable(table); - break; - case "ReserveCost": - this.DecompileReserveCostTable(table); - break; - case "SelfReg": - this.DecompileSelfRegTable(table); - break; - case "ServiceControl": - this.DecompileServiceControlTable(table); - break; - case "ServiceInstall": - this.DecompileServiceInstallTable(table); - break; - case "SFPCatalog": - this.DecompileSFPCatalogTable(table); - break; - case "Shortcut": - this.DecompileShortcutTable(table); - break; - case "Signature": - this.DecompileSignatureTable(table); - break; - case "TargetFiles_OptionalData": - this.DecompileTargetFiles_OptionalDataTable(table); - break; - case "TargetImages": - this.DecompileTargetImagesTable(table); - break; - case "TextStyle": - this.DecompileTextStyleTable(table); - break; - case "TypeLib": - this.DecompileTypeLibTable(table); - break; - case "Upgrade": - this.DecompileUpgradeTable(table); - break; - case "UpgradedFiles_OptionalData": - this.DecompileUpgradedFiles_OptionalDataTable(table); - break; - case "UpgradedFilesToIgnore": - this.DecompileUpgradedFilesToIgnoreTable(table); - break; - case "UpgradedImages": - this.DecompileUpgradedImagesTable(table); - break; - case "UIText": - this.DecompileUITextTable(table); - break; - case "Verb": - this.DecompileVerbTable(table); - break; - default: - DecompilerExtension extension = (DecompilerExtension)this.extensionsByTableName[table.Name]; - - if (null != extension) - { - extension.DecompileTable(table); - } - else if (!this.suppressCustomTables) - { - this.DecompileCustomTable(table); - } - break; - } - } - } - - /// - /// Determine if a particular table should be decompiled with the current settings. - /// - /// The output being decompiled. - /// The name of a table. - /// true if the table should be decompiled; false otherwise. - private bool DecompilableTable(Output output, string tableName) - { - switch (tableName) - { - case "ActionText": - case "BBControl": - case "Billboard": - case "CheckBox": - case "Control": - case "ControlCondition": - case "ControlEvent": - case "Dialog": - case "Error": - case "EventMapping": - case "RadioButton": - case "TextStyle": - case "UIText": - return !this.suppressUI; - case "ModuleAdminExecuteSequence": - case "ModuleAdminUISequence": - case "ModuleAdvtExecuteSequence": - case "ModuleAdvtUISequence": - case "ModuleComponents": - case "ModuleConfiguration": - case "ModuleDependency": - case "ModuleIgnoreTable": - case "ModuleInstallExecuteSequence": - case "ModuleInstallUISequence": - case "ModuleExclusion": - case "ModuleSignature": - case "ModuleSubstitution": - if (OutputType.Module != output.Type) - { - this.core.OnMessage(WixWarnings.SkippingMergeModuleTable(output.SourceLineNumbers, tableName)); - return false; - } - else - { - return true; - } - case "ExternalFiles": - case "FamilyFileRanges": - case "ImageFamilies": - case "PatchMetadata": - case "PatchSequence": - case "Properties": - case "TargetFiles_OptionalData": - case "TargetImages": - case "UpgradedFiles_OptionalData": - case "UpgradedFilesToIgnore": - case "UpgradedImages": - if (OutputType.PatchCreation != output.Type) - { - this.core.OnMessage(WixWarnings.SkippingPatchCreationTable(output.SourceLineNumbers, tableName)); - return false; - } - else - { - return true; - } - case "MsiPatchHeaders": - case "MsiPatchMetadata": - case "MsiPatchOldAssemblyName": - case "MsiPatchOldAssemblyFile": - case "MsiPatchSequence": - case "Patch": - case "PatchPackage": - this.core.OnMessage(WixWarnings.PatchTable(output.SourceLineNumbers, tableName)); - return false; - case "_SummaryInformation": - return true; - case "_Validation": - case "MsiAssemblyName": - case "MsiFileHash": - return false; - default: // all other tables are allowed in any output except for a patch creation package - if (OutputType.PatchCreation == output.Type) - { - this.core.OnMessage(WixWarnings.IllegalPatchCreationTable(output.SourceLineNumbers, tableName)); - return false; - } - else - { - return true; - } - } - } - - /// - /// Decompile the _SummaryInformation table. - /// - /// The table to decompile. - private void Decompile_SummaryInformationTable(Table table) - { - if (OutputType.Module == this.outputType || OutputType.Product == this.outputType) - { - Wix.Package package = new Wix.Package(); - - foreach (Row row in table.Rows) - { - string value = Convert.ToString(row[1]); - - if (null != value && 0 < value.Length) - { - switch (Convert.ToInt32(row[0])) - { - case 1: - if ("1252" != value) - { - package.SummaryCodepage = value; - } - break; - case 3: - package.Description = value; - break; - case 4: - package.Manufacturer = value; - break; - case 5: - if ("Installer" != value) - { - package.Keywords = value; - } - break; - case 6: - package.Comments = value; - break; - case 7: - string[] template = value.Split(';'); - if (0 < template.Length && 0 < template[template.Length - 1].Length) - { - package.Languages = template[template.Length - 1]; - } - - if (1 < template.Length && null != template[0] && 0 < template[0].Length) - { - switch (template[0]) - { - case "Intel": - package.Platform = WixToolset.Data.Serialize.Package.PlatformType.x86; - break; - case "Intel64": - package.Platform = WixToolset.Data.Serialize.Package.PlatformType.ia64; - break; - case "x64": - package.Platform = WixToolset.Data.Serialize.Package.PlatformType.x64; - break; - } - } - break; - case 9: - if (OutputType.Module == this.outputType) - { - this.modularizationGuid = value; - package.Id = value; - } - break; - case 14: - package.InstallerVersion = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); - break; - case 15: - int wordCount = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); - if (0x1 == (wordCount & 0x1)) - { - this.shortNames = true; - package.ShortNames = Wix.YesNoType.yes; - } - - if (0x2 == (wordCount & 0x2)) - { - this.compressed = true; - - if (OutputType.Product == this.outputType) - { - package.Compressed = Wix.YesNoType.yes; - } - } - - if (0x4 == (wordCount & 0x4)) - { - package.AdminImage = Wix.YesNoType.yes; - } - - if (0x8 == (wordCount & 0x8)) - { - package.InstallPrivileges = Wix.Package.InstallPrivilegesType.limited; - } - - break; - case 19: - int security = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); - switch (security) - { - case 0: - package.ReadOnly = Wix.YesNoDefaultType.no; - break; - case 4: - package.ReadOnly = Wix.YesNoDefaultType.yes; - break; - } - break; - } - } - } - - this.core.RootElement.AddChild(package); - } - else - { - Wix.PatchInformation patchInformation = new Wix.PatchInformation(); - - foreach (Row row in table.Rows) - { - int propertyId = Convert.ToInt32(row[0]); - string value = Convert.ToString(row[1]); - - if (null != row[1] && 0 < value.Length) - { - switch (propertyId) - { - case 1: - if ("1252" != value) - { - patchInformation.SummaryCodepage = value; - } - break; - case 3: - patchInformation.Description = value; - break; - case 4: - patchInformation.Manufacturer = value; - break; - case 5: - if ("Installer,Patching,PCP,Database" != value) - { - patchInformation.Keywords = value; - } - break; - case 6: - patchInformation.Comments = value; - break; - case 7: - string[] template = value.Split(';'); - if (0 < template.Length && 0 < template[template.Length - 1].Length) - { - patchInformation.Languages = template[template.Length - 1]; - } - - if (1 < template.Length && null != template[0] && 0 < template[0].Length) - { - patchInformation.Platforms = template[0]; - } - break; - case 15: - int wordCount = Convert.ToInt32(value, CultureInfo.InvariantCulture); - if (0x1 == (wordCount & 0x1)) - { - patchInformation.ShortNames = Wix.YesNoType.yes; - } - - if (0x2 == (wordCount & 0x2)) - { - patchInformation.Compressed = Wix.YesNoType.yes; - } - - if (0x4 == (wordCount & 0x4)) - { - patchInformation.AdminImage = Wix.YesNoType.yes; - } - break; - case 19: - int security = Convert.ToInt32(value, CultureInfo.InvariantCulture); - switch (security) - { - case 0: - patchInformation.ReadOnly = Wix.YesNoDefaultType.no; - break; - case 4: - patchInformation.ReadOnly = Wix.YesNoDefaultType.yes; - break; - } - break; - } - } - } - - this.core.RootElement.AddChild(patchInformation); - } - } - - /// - /// Decompile the ActionText table. - /// - /// The table to decompile. - private void DecompileActionTextTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ProgressText progressText = new Wix.ProgressText(); - - progressText.Action = Convert.ToString(row[0]); - - if (null != row[1]) - { - progressText.Content = Convert.ToString(row[1]); - } - - if (null != row[2]) - { - progressText.Template = Convert.ToString(row[2]); - } - - this.core.UIElement.AddChild(progressText); - } - } - - /// - /// Decompile the AppId table. - /// - /// The table to decompile. - private void DecompileAppIdTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.AppId appId = new Wix.AppId(); - - appId.Advertise = Wix.YesNoType.yes; - - appId.Id = Convert.ToString(row[0]); - - if (null != row[1]) - { - appId.RemoteServerName = Convert.ToString(row[1]); - } - - if (null != row[2]) - { - appId.LocalService = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - appId.ServiceParameters = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - appId.DllSurrogate = Convert.ToString(row[4]); - } - - if (null != row[5] && Int32.Equals(row[5], 1)) - { - appId.ActivateAtStorage = Wix.YesNoType.yes; - } - - if (null != row[6] && Int32.Equals(row[6], 1)) - { - appId.RunAsInteractiveUser = Wix.YesNoType.yes; - } - - this.core.RootElement.AddChild(appId); - this.core.IndexElement(row, appId); - } - } - - /// - /// Decompile the BBControl table. - /// - /// The table to decompile. - private void DecompileBBControlTable(Table table) - { - foreach (BBControlRow bbControlRow in table.Rows) - { - Wix.Control control = new Wix.Control(); - - control.Id = bbControlRow.BBControl; - - control.Type = bbControlRow.Type; - - control.X = bbControlRow.X; - - control.Y = bbControlRow.Y; - - control.Width = bbControlRow.Width; - - control.Height = bbControlRow.Height; - - if (null != bbControlRow[7]) - { - SetControlAttributes(bbControlRow.Attributes, control); - } - - if (null != bbControlRow.Text) - { - control.Text = bbControlRow.Text; - } - - Wix.Billboard billboard = (Wix.Billboard)this.core.GetIndexedElement("Billboard", bbControlRow.Billboard); - if (null != billboard) - { - billboard.AddChild(control); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard")); - } - } - } - - /// - /// Decompile the Billboard table. - /// - /// The table to decompile. - private void DecompileBillboardTable(Table table) - { - Hashtable billboardActions = new Hashtable(); - SortedList billboards = new SortedList(); - - foreach (Row row in table.Rows) - { - Wix.Billboard billboard = new Wix.Billboard(); - - billboard.Id = Convert.ToString(row[0]); - - billboard.Feature = Convert.ToString(row[1]); - - this.core.IndexElement(row, billboard); - billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row); - } - - foreach (Row row in billboards.Values) - { - Wix.Billboard billboard = (Wix.Billboard)this.core.GetIndexedElement(row); - Wix.BillboardAction billboardAction = (Wix.BillboardAction)billboardActions[row[2]]; - - if (null == billboardAction) - { - billboardAction = new Wix.BillboardAction(); - - billboardAction.Id = Convert.ToString(row[2]); - - this.core.UIElement.AddChild(billboardAction); - billboardActions.Add(row[2], billboardAction); - } - - billboardAction.AddChild(billboard); - } - } - - /// - /// Decompile the Binary table. - /// - /// The table to decompile. - private void DecompileBinaryTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Binary binary = new Wix.Binary(); - - binary.Id = Convert.ToString(row[0]); - - binary.SourceFile = Convert.ToString(row[1]); - - this.core.RootElement.AddChild(binary); - } - } - - /// - /// Decompile the BindImage table. - /// - /// The table to decompile. - private void DecompileBindImageTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.File file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); - - if (null != file) - { - file.BindPath = Convert.ToString(row[1]); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); - } - } - } - - /// - /// Decompile the Class table. - /// - /// The table to decompile. - private void DecompileClassTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Class wixClass = new Wix.Class(); - - wixClass.Advertise = Wix.YesNoType.yes; - - wixClass.Id = Convert.ToString(row[0]); - - switch (Convert.ToString(row[1])) - { - case "LocalServer": - wixClass.Context = Wix.Class.ContextType.LocalServer; - break; - case "LocalServer32": - wixClass.Context = Wix.Class.ContextType.LocalServer32; - break; - case "InprocServer": - wixClass.Context = Wix.Class.ContextType.InprocServer; - break; - case "InprocServer32": - wixClass.Context = Wix.Class.ContextType.InprocServer32; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - // ProgId children are handled in FinalizeProgIdTable - - if (null != row[4]) - { - wixClass.Description = Convert.ToString(row[4]); - } - - if (null != row[5]) - { - wixClass.AppId = Convert.ToString(row[5]); - } - - if (null != row[6]) - { - string[] fileTypeMaskStrings = (Convert.ToString(row[6])).Split(';'); - - try - { - foreach (string fileTypeMaskString in fileTypeMaskStrings) - { - string[] fileTypeMaskParts = fileTypeMaskString.Split(','); - - if (4 == fileTypeMaskParts.Length) - { - Wix.FileTypeMask fileTypeMask = new Wix.FileTypeMask(); - - fileTypeMask.Offset = Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture); - - fileTypeMask.Mask = fileTypeMaskParts[2]; - - fileTypeMask.Value = fileTypeMaskParts[3]; - - wixClass.AddChild(fileTypeMask); - } - else - { - // TODO: warn - } - } - } - catch (FormatException) - { - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - } - catch (OverflowException) - { - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - } - } - - if (null != row[7]) - { - wixClass.Icon = Convert.ToString(row[7]); - } - - if (null != row[8]) - { - wixClass.IconIndex = Convert.ToInt32(row[8]); - } - - if (null != row[9]) - { - wixClass.Handler = Convert.ToString(row[9]); - } - - if (null != row[10]) - { - wixClass.Argument = Convert.ToString(row[10]); - } - - if (null != row[12]) - { - if (1 == Convert.ToInt32(row[12])) - { - wixClass.RelativePath = Wix.YesNoType.yes; - } - else - { - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12])); - } - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2])); - if (null != component) - { - component.AddChild(wixClass); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[2]), "Component")); - } - - this.core.IndexElement(row, wixClass); - } - } - - /// - /// Decompile the ComboBox table. - /// - /// The table to decompile. - private void DecompileComboBoxTable(Table table) - { - Wix.ComboBox comboBox = null; - SortedList comboBoxRows = new SortedList(); - - // sort the combo boxes by their property and order - foreach (Row row in table.Rows) - { - comboBoxRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); - } - - foreach (Row row in comboBoxRows.Values) - { - if (null == comboBox || Convert.ToString(row[0]) != comboBox.Property) - { - comboBox = new Wix.ComboBox(); - - comboBox.Property = Convert.ToString(row[0]); - - this.core.UIElement.AddChild(comboBox); - } - - Wix.ListItem listItem = new Wix.ListItem(); - - listItem.Value = Convert.ToString(row[2]); - - if (null != row[3]) - { - listItem.Text = Convert.ToString(row[3]); - } - - comboBox.AddChild(listItem); - } - } - - /// - /// Decompile the Control table. - /// - /// The table to decompile. - private void DecompileControlTable(Table table) - { - foreach (ControlRow controlRow in table.Rows) - { - Wix.Control control = new Wix.Control(); - - control.Id = controlRow.Control; - - control.Type = controlRow.Type; - - control.X = controlRow.X; - - control.Y = controlRow.Y; - - control.Width = controlRow.Width; - - control.Height = controlRow.Height; - - if (null != controlRow[7]) - { - string[] specialAttributes; - - // sets various common attributes like Disabled, Indirect, Integer, ... - SetControlAttributes(controlRow.Attributes, control); - - switch (control.Type) - { - case "Bitmap": - specialAttributes = MsiInterop.BitmapControlAttributes; - break; - case "CheckBox": - specialAttributes = MsiInterop.CheckboxControlAttributes; - break; - case "ComboBox": - specialAttributes = MsiInterop.ComboboxControlAttributes; - break; - case "DirectoryCombo": - specialAttributes = MsiInterop.VolumeControlAttributes; - break; - case "Edit": - specialAttributes = MsiInterop.EditControlAttributes; - break; - case "Icon": - specialAttributes = MsiInterop.IconControlAttributes; - break; - case "ListBox": - specialAttributes = MsiInterop.ListboxControlAttributes; - break; - case "ListView": - specialAttributes = MsiInterop.ListviewControlAttributes; - break; - case "MaskedEdit": - specialAttributes = MsiInterop.EditControlAttributes; - break; - case "PathEdit": - specialAttributes = MsiInterop.EditControlAttributes; - break; - case "ProgressBar": - specialAttributes = MsiInterop.ProgressControlAttributes; - break; - case "PushButton": - specialAttributes = MsiInterop.ButtonControlAttributes; - break; - case "RadioButtonGroup": - specialAttributes = MsiInterop.RadioControlAttributes; - break; - case "Text": - specialAttributes = MsiInterop.TextControlAttributes; - break; - case "VolumeCostList": - specialAttributes = MsiInterop.VolumeControlAttributes; - break; - case "VolumeSelectCombo": - specialAttributes = MsiInterop.VolumeControlAttributes; - break; - default: - specialAttributes = null; - break; - } - - if (null != specialAttributes) - { - bool iconSizeSet = false; - - for (int i = 16; 32 > i; i++) - { - if (1 == ((controlRow.Attributes >> i) & 1)) - { - string attribute = null; - - if (specialAttributes.Length > (i - 16)) - { - attribute = specialAttributes[i - 16]; - } - - // unknown attribute - if (null == attribute) - { - this.core.OnMessage(WixWarnings.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); - continue; - } - - switch (attribute) - { - case "Bitmap": - control.Bitmap = Wix.YesNoType.yes; - break; - case "CDROM": - control.CDROM = Wix.YesNoType.yes; - break; - case "ComboList": - control.ComboList = Wix.YesNoType.yes; - break; - case "ElevationShield": - control.ElevationShield = Wix.YesNoType.yes; - break; - case "Fixed": - control.Fixed = Wix.YesNoType.yes; - break; - case "FixedSize": - control.FixedSize = Wix.YesNoType.yes; - break; - case "Floppy": - control.Floppy = Wix.YesNoType.yes; - break; - case "FormatSize": - control.FormatSize = Wix.YesNoType.yes; - break; - case "HasBorder": - control.HasBorder = Wix.YesNoType.yes; - break; - case "Icon": - control.Icon = Wix.YesNoType.yes; - break; - case "Icon16": - if (iconSizeSet) - { - control.IconSize = Wix.Control.IconSizeType.Item48; - } - else - { - iconSizeSet = true; - control.IconSize = Wix.Control.IconSizeType.Item16; - } - break; - case "Icon32": - if (iconSizeSet) - { - control.IconSize = Wix.Control.IconSizeType.Item48; - } - else - { - iconSizeSet = true; - control.IconSize = Wix.Control.IconSizeType.Item32; - } - break; - case "Image": - control.Image = Wix.YesNoType.yes; - break; - case "Multiline": - control.Multiline = Wix.YesNoType.yes; - break; - case "NoPrefix": - control.NoPrefix = Wix.YesNoType.yes; - break; - case "NoWrap": - control.NoWrap = Wix.YesNoType.yes; - break; - case "Password": - control.Password = Wix.YesNoType.yes; - break; - case "ProgressBlocks": - control.ProgressBlocks = Wix.YesNoType.yes; - break; - case "PushLike": - control.PushLike = Wix.YesNoType.yes; - break; - case "RAMDisk": - control.RAMDisk = Wix.YesNoType.yes; - break; - case "Remote": - control.Remote = Wix.YesNoType.yes; - break; - case "Removable": - control.Removable = Wix.YesNoType.yes; - break; - case "ShowRollbackCost": - control.ShowRollbackCost = Wix.YesNoType.yes; - break; - case "Sorted": - control.Sorted = Wix.YesNoType.yes; - break; - case "Transparent": - control.Transparent = Wix.YesNoType.yes; - break; - case "UserLanguage": - control.UserLanguage = Wix.YesNoType.yes; - break; - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknowControlAttribute, attribute)); - } - } - } - } - else if (0 < (controlRow.Attributes & 0xFFFF0000)) - { - this.core.OnMessage(WixWarnings.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); - } - } - - // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef - if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", control.Type)) - { - control.Property = controlRow.Property; - } - - if (null != controlRow.Text) - { - control.Text = controlRow.Text; - } - - if (null != controlRow.Help) - { - string[] help = controlRow.Help.Split('|'); - - if (2 == help.Length) - { - if (0 < help[0].Length) - { - control.ToolTip = help[0]; - } - - if (0 < help[1].Length) - { - control.Help = help[1]; - } - } - } - - this.core.IndexElement(controlRow, control); - } - } - - /// - /// Decompile the ControlCondition table. - /// - /// The table to decompile. - private void DecompileControlConditionTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Condition condition = new Wix.Condition(); - - switch (Convert.ToString(row[2])) - { - case "Default": - condition.Action = Wix.Condition.ActionType.@default; - break; - case "Disable": - condition.Action = Wix.Condition.ActionType.disable; - break; - case "Enable": - condition.Action = Wix.Condition.ActionType.enable; - break; - case "Hide": - condition.Action = Wix.Condition.ActionType.hide; - break; - case "Show": - condition.Action = Wix.Condition.ActionType.show; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - break; - } - - condition.Content = Convert.ToString(row[3]); - - Wix.Control control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1])); - if (null != control) - { - control.AddChild(condition); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control")); - } - } - } - - /// - /// Decompile the ControlEvent table. - /// - /// The table to decompile. - private void DecompileControlEventTable(Table table) - { - SortedList controlEvents = new SortedList(); - - foreach (Row row in table.Rows) - { - Wix.Publish publish = new Wix.Publish(); - - string publishEvent = Convert.ToString(row[2]); - if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal)) - { - publish.Property = publishEvent.Substring(1, publishEvent.Length - 2); - - if ("{}" != Convert.ToString(row[3])) - { - publish.Value = Convert.ToString(row[3]); - } - } - else - { - publish.Event = publishEvent; - publish.Value = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - publish.Content = Convert.ToString(row[4]); - } - - controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row[0], row[1], (null == row[5] ? 0 : Convert.ToInt32(row[5])), row[2], row[3], row[4]), row); - - this.core.IndexElement(row, publish); - } - - foreach (Row row in controlEvents.Values) - { - Wix.Control control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1])); - Wix.Publish publish = (Wix.Publish)this.core.GetIndexedElement(row); - - if (null != control) - { - control.AddChild(publish); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control")); - } - } - } - - /// - /// Decompile a custom table. - /// - /// The table to decompile. - private void DecompileCustomTable(Table table) - { - if (0 < table.Rows.Count || this.suppressDroppingEmptyTables) - { - Wix.CustomTable customTable = new Wix.CustomTable(); - - this.core.OnMessage(WixWarnings.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name)); - - customTable.Id = table.Name; - - foreach (ColumnDefinition columnDefinition in table.Definition.Columns) - { - Wix.Column column = new Wix.Column(); - - column.Id = columnDefinition.Name; - - if (ColumnCategory.Unknown != columnDefinition.Category) - { - switch (columnDefinition.Category) - { - case ColumnCategory.Text: - column.Category = Wix.Column.CategoryType.Text; - break; - case ColumnCategory.UpperCase: - column.Category = Wix.Column.CategoryType.UpperCase; - break; - case ColumnCategory.LowerCase: - column.Category = Wix.Column.CategoryType.LowerCase; - break; - case ColumnCategory.Integer: - column.Category = Wix.Column.CategoryType.Integer; - break; - case ColumnCategory.DoubleInteger: - column.Category = Wix.Column.CategoryType.DoubleInteger; - break; - case ColumnCategory.TimeDate: - column.Category = Wix.Column.CategoryType.TimeDate; - break; - case ColumnCategory.Identifier: - column.Category = Wix.Column.CategoryType.Identifier; - break; - case ColumnCategory.Property: - column.Category = Wix.Column.CategoryType.Property; - break; - case ColumnCategory.Filename: - column.Category = Wix.Column.CategoryType.Filename; - break; - case ColumnCategory.WildCardFilename: - column.Category = Wix.Column.CategoryType.WildCardFilename; - break; - case ColumnCategory.Path: - column.Category = Wix.Column.CategoryType.Path; - break; - case ColumnCategory.Paths: - column.Category = Wix.Column.CategoryType.Paths; - break; - case ColumnCategory.AnyPath: - column.Category = Wix.Column.CategoryType.AnyPath; - break; - case ColumnCategory.DefaultDir: - column.Category = Wix.Column.CategoryType.DefaultDir; - break; - case ColumnCategory.RegPath: - column.Category = Wix.Column.CategoryType.RegPath; - break; - case ColumnCategory.Formatted: - column.Category = Wix.Column.CategoryType.Formatted; - break; - case ColumnCategory.FormattedSDDLText: - column.Category = Wix.Column.CategoryType.FormattedSddl; - break; - case ColumnCategory.Template: - column.Category = Wix.Column.CategoryType.Template; - break; - case ColumnCategory.Condition: - column.Category = Wix.Column.CategoryType.Condition; - break; - case ColumnCategory.Guid: - column.Category = Wix.Column.CategoryType.Guid; - break; - case ColumnCategory.Version: - column.Category = Wix.Column.CategoryType.Version; - break; - case ColumnCategory.Language: - column.Category = Wix.Column.CategoryType.Language; - break; - case ColumnCategory.Binary: - column.Category = Wix.Column.CategoryType.Binary; - break; - case ColumnCategory.CustomSource: - column.Category = Wix.Column.CategoryType.CustomSource; - break; - case ColumnCategory.Cabinet: - column.Category = Wix.Column.CategoryType.Cabinet; - break; - case ColumnCategory.Shortcut: - column.Category = Wix.Column.CategoryType.Shortcut; - break; - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknownCustomColumnCategory, columnDefinition.Category.ToString())); - } - } - - if (null != columnDefinition.Description) - { - column.Description = columnDefinition.Description; - } - - if (columnDefinition.IsKeyColumnSet) - { - column.KeyColumn = columnDefinition.KeyColumn; - } - - if (null != columnDefinition.KeyTable) - { - column.KeyTable = columnDefinition.KeyTable; - } - - if (columnDefinition.IsLocalizable) - { - column.Localizable = Wix.YesNoType.yes; - } - - if (columnDefinition.IsMaxValueSet) - { - column.MaxValue = columnDefinition.MaxValue; - } - - if (columnDefinition.IsMinValueSet) - { - column.MinValue = columnDefinition.MinValue; - } - - if (ColumnModularizeType.None != columnDefinition.ModularizeType) - { - switch (columnDefinition.ModularizeType) - { - case ColumnModularizeType.Column: - column.Modularize = Wix.Column.ModularizeType.Column; - break; - case ColumnModularizeType.Condition: - column.Modularize = Wix.Column.ModularizeType.Condition; - break; - case ColumnModularizeType.Icon: - column.Modularize = Wix.Column.ModularizeType.Icon; - break; - case ColumnModularizeType.Property: - column.Modularize = Wix.Column.ModularizeType.Property; - break; - case ColumnModularizeType.SemicolonDelimited: - column.Modularize = Wix.Column.ModularizeType.SemicolonDelimited; - break; - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknownCustomColumnModularizationType, columnDefinition.ModularizeType.ToString())); - } - } - - if (columnDefinition.Nullable) - { - column.Nullable = Wix.YesNoType.yes; - } - - if (columnDefinition.PrimaryKey) - { - column.PrimaryKey = Wix.YesNoType.yes; - } - - if (null != columnDefinition.Possibilities) - { - column.Set = columnDefinition.Possibilities; - } - - if (ColumnType.Unknown != columnDefinition.Type) - { - switch (columnDefinition.Type) - { - case ColumnType.Localized: - column.Localizable = Wix.YesNoType.yes; - column.Type = Wix.Column.TypeType.@string; - break; - case ColumnType.Number: - column.Type = Wix.Column.TypeType.@int; - break; - case ColumnType.Object: - column.Type = Wix.Column.TypeType.binary; - break; - case ColumnType.Preserved: - case ColumnType.String: - column.Type = Wix.Column.TypeType.@string; - break; - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknownCustomColumnType, columnDefinition.Type.ToString())); - } - } - - column.Width = columnDefinition.Length; - - customTable.AddChild(column); - } - - foreach (Row row in table.Rows) - { - Wix.Row wixRow = new Wix.Row(); - - foreach (Field field in row.Fields) - { - Wix.Data data = new Wix.Data(); - - data.Column = field.Column.Name; - - data.Content = Convert.ToString(field.Data, CultureInfo.InvariantCulture); - - wixRow.AddChild(data); - } - - customTable.AddChild(wixRow); - } - - this.core.RootElement.AddChild(customTable); - } - } - - /// - /// Decompile the CreateFolder table. - /// - /// The table to decompile. - private void DecompileCreateFolderTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.CreateFolder createFolder = new Wix.CreateFolder(); - - createFolder.Directory = Convert.ToString(row[0]); - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(createFolder); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, createFolder); - } - } - - /// - /// Decompile the CustomAction table. - /// - /// The table to decompile. - private void DecompileCustomActionTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.CustomAction customAction = new Wix.CustomAction(); - - customAction.Id = Convert.ToString(row[0]); - - int type = Convert.ToInt32(row[1]); - - if (MsiInterop.MsidbCustomActionTypeHideTarget == (type & MsiInterop.MsidbCustomActionTypeHideTarget)) - { - customAction.HideTarget = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbCustomActionTypeNoImpersonate == (type & MsiInterop.MsidbCustomActionTypeNoImpersonate)) - { - customAction.Impersonate = Wix.YesNoType.no; - } - - if (MsiInterop.MsidbCustomActionTypeTSAware == (type & MsiInterop.MsidbCustomActionTypeTSAware)) - { - customAction.TerminalServerAware = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbCustomActionType64BitScript == (type & MsiInterop.MsidbCustomActionType64BitScript)) - { - customAction.Win64 = Wix.YesNoType.yes; - } - - switch (type & MsiInterop.MsidbCustomActionTypeExecuteBits) - { - case 0: - // this is the default value - break; - case MsiInterop.MsidbCustomActionTypeFirstSequence: - customAction.Execute = Wix.CustomAction.ExecuteType.firstSequence; - break; - case MsiInterop.MsidbCustomActionTypeOncePerProcess: - customAction.Execute = Wix.CustomAction.ExecuteType.oncePerProcess; - break; - case MsiInterop.MsidbCustomActionTypeClientRepeat: - customAction.Execute = Wix.CustomAction.ExecuteType.secondSequence; - break; - case MsiInterop.MsidbCustomActionTypeInScript: - customAction.Execute = Wix.CustomAction.ExecuteType.deferred; - break; - case MsiInterop.MsidbCustomActionTypeInScript + MsiInterop.MsidbCustomActionTypeRollback: - customAction.Execute = Wix.CustomAction.ExecuteType.rollback; - break; - case MsiInterop.MsidbCustomActionTypeInScript + MsiInterop.MsidbCustomActionTypeCommit: - customAction.Execute = Wix.CustomAction.ExecuteType.commit; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - switch (type & MsiInterop.MsidbCustomActionTypeReturnBits) - { - case 0: - // this is the default value - break; - case MsiInterop.MsidbCustomActionTypeContinue: - customAction.Return = Wix.CustomAction.ReturnType.ignore; - break; - case MsiInterop.MsidbCustomActionTypeAsync: - customAction.Return = Wix.CustomAction.ReturnType.asyncWait; - break; - case MsiInterop.MsidbCustomActionTypeAsync + MsiInterop.MsidbCustomActionTypeContinue: - customAction.Return = Wix.CustomAction.ReturnType.asyncNoWait; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - int source = type & MsiInterop.MsidbCustomActionTypeSourceBits; - switch (source) - { - case MsiInterop.MsidbCustomActionTypeBinaryData: - customAction.BinaryKey = Convert.ToString(row[2]); - break; - case MsiInterop.MsidbCustomActionTypeSourceFile: - if (null != row[2]) - { - customAction.FileKey = Convert.ToString(row[2]); - } - break; - case MsiInterop.MsidbCustomActionTypeDirectory: - if (null != row[2]) - { - customAction.Directory = Convert.ToString(row[2]); - } - break; - case MsiInterop.MsidbCustomActionTypeProperty: - customAction.Property = Convert.ToString(row[2]); - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - switch (type & MsiInterop.MsidbCustomActionTypeTargetBits) - { - case MsiInterop.MsidbCustomActionTypeDll: - customAction.DllEntry = Convert.ToString(row[3]); - break; - case MsiInterop.MsidbCustomActionTypeExe: - customAction.ExeCommand = Convert.ToString(row[3]); - break; - case MsiInterop.MsidbCustomActionTypeTextData: - if (MsiInterop.MsidbCustomActionTypeSourceFile == source) - { - customAction.Error = Convert.ToString(row[3]); - } - else - { - customAction.Value = Convert.ToString(row[3]); - } - break; - case MsiInterop.MsidbCustomActionTypeJScript: - if (MsiInterop.MsidbCustomActionTypeDirectory == source) - { - customAction.Script = Wix.CustomAction.ScriptType.jscript; - customAction.Content = Convert.ToString(row[3]); - } - else - { - customAction.JScriptCall = Convert.ToString(row[3]); - } - break; - case MsiInterop.MsidbCustomActionTypeVBScript: - if (MsiInterop.MsidbCustomActionTypeDirectory == source) - { - customAction.Script = Wix.CustomAction.ScriptType.vbscript; - customAction.Content = Convert.ToString(row[3]); - } - else - { - customAction.VBScriptCall = Convert.ToString(row[3]); - } - break; - case MsiInterop.MsidbCustomActionTypeInstall: - this.core.OnMessage(WixWarnings.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - continue; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - int extype = 4 < row.Fields.Length && null != row[4] ? Convert.ToInt32(row[4]) : 0; - if (MsiInterop.MsidbCustomActionTypePatchUninstall == (extype & MsiInterop.MsidbCustomActionTypePatchUninstall)) - { - customAction.PatchUninstall = Wix.YesNoType.yes; - } - - this.core.RootElement.AddChild(customAction); - this.core.IndexElement(row, customAction); - } - } - - /// - /// Decompile the CompLocator table. - /// - /// The table to decompile. - private void DecompileCompLocatorTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ComponentSearch componentSearch = new Wix.ComponentSearch(); - - componentSearch.Id = Convert.ToString(row[0]); - - componentSearch.Guid = Convert.ToString(row[1]); - - if (null != row[2]) - { - switch (Convert.ToInt32(row[2])) - { - case MsiInterop.MsidbLocatorTypeDirectory: - componentSearch.Type = Wix.ComponentSearch.TypeType.directory; - break; - case MsiInterop.MsidbLocatorTypeFileName: - // this is the default value - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - break; - } - } - - this.core.IndexElement(row, componentSearch); - } - } - - /// - /// Decompile the Complus table. - /// - /// The table to decompile. - private void DecompileComplusTable(Table table) - { - foreach (Row row in table.Rows) - { - if (null != row[1]) - { - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0])); - - if (null != component) - { - component.ComPlusFlags = Convert.ToInt32(row[1]); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[0]), "Component")); - } - } - } - } - - /// - /// Decompile the Component table. - /// - /// The table to decompile. - private void DecompileComponentTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Component component = new Wix.Component(); - - component.Id = Convert.ToString(row[0]); - - component.Guid = Convert.ToString(row[1]); - - int attributes = Convert.ToInt32(row[3]); - - if (MsiInterop.MsidbComponentAttributesSourceOnly == (attributes & MsiInterop.MsidbComponentAttributesSourceOnly)) - { - component.Location = Wix.Component.LocationType.source; - } - else if (MsiInterop.MsidbComponentAttributesOptional == (attributes & MsiInterop.MsidbComponentAttributesOptional)) - { - component.Location = Wix.Component.LocationType.either; - } - - if (MsiInterop.MsidbComponentAttributesSharedDllRefCount == (attributes & MsiInterop.MsidbComponentAttributesSharedDllRefCount)) - { - component.SharedDllRefCount = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbComponentAttributesPermanent == (attributes & MsiInterop.MsidbComponentAttributesPermanent)) - { - component.Permanent = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbComponentAttributesTransitive == (attributes & MsiInterop.MsidbComponentAttributesTransitive)) - { - component.Transitive = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbComponentAttributesNeverOverwrite == (attributes & MsiInterop.MsidbComponentAttributesNeverOverwrite)) - { - component.NeverOverwrite = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbComponentAttributes64bit == (attributes & MsiInterop.MsidbComponentAttributes64bit)) - { - component.Win64 = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbComponentAttributesDisableRegistryReflection == (attributes & MsiInterop.MsidbComponentAttributesDisableRegistryReflection)) - { - component.DisableRegistryReflection = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbComponentAttributesUninstallOnSupersedence == (attributes & MsiInterop.MsidbComponentAttributesUninstallOnSupersedence)) - { - component.UninstallWhenSuperseded = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbComponentAttributesShared == (attributes & MsiInterop.MsidbComponentAttributesShared)) - { - component.Shared = Wix.YesNoType.yes; - } - - if (null != row[4]) - { - Wix.Condition condition = new Wix.Condition(); - - condition.Content = Convert.ToString(row[4]); - - component.AddChild(condition); - } - - Wix.Directory directory = (Wix.Directory)this.core.GetIndexedElement("Directory", Convert.ToString(row[2])); - if (null != directory) - { - directory.AddChild(component); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_", Convert.ToString(row[2]), "Directory")); - } - this.core.IndexElement(row, component); - } - } - - /// - /// Decompile the Condition table. - /// - /// The table to decompile. - private void DecompileConditionTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Condition condition = new Wix.Condition(); - - condition.Level = Convert.ToInt32(row[1]); - - if (null != row[2]) - { - condition.Content = Convert.ToString(row[2]); - } - - Wix.Feature feature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); - if (null != feature) - { - feature.AddChild(condition); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", Convert.ToString(row[0]), "Feature")); - } - } - } - - /// - /// Decompile the Dialog table. - /// - /// The table to decompile. - private void DecompileDialogTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Dialog dialog = new Wix.Dialog(); - - dialog.Id = Convert.ToString(row[0]); - - dialog.X = Convert.ToInt32(row[1]); - - dialog.Y = Convert.ToInt32(row[2]); - - dialog.Width = Convert.ToInt32(row[3]); - - dialog.Height = Convert.ToInt32(row[4]); - - if (null != row[5]) - { - int attributes = Convert.ToInt32(row[5]); - - if (0 == (attributes & MsiInterop.MsidbDialogAttributesVisible)) - { - dialog.Hidden = Wix.YesNoType.yes; - } - - if (0 == (attributes & MsiInterop.MsidbDialogAttributesModal)) - { - dialog.Modeless = Wix.YesNoType.yes; - } - - if (0 == (attributes & MsiInterop.MsidbDialogAttributesMinimize)) - { - dialog.NoMinimize = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbDialogAttributesSysModal == (attributes & MsiInterop.MsidbDialogAttributesSysModal)) - { - dialog.SystemModal = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbDialogAttributesKeepModeless == (attributes & MsiInterop.MsidbDialogAttributesKeepModeless)) - { - dialog.KeepModeless = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbDialogAttributesTrackDiskSpace == (attributes & MsiInterop.MsidbDialogAttributesTrackDiskSpace)) - { - dialog.TrackDiskSpace = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbDialogAttributesUseCustomPalette == (attributes & MsiInterop.MsidbDialogAttributesUseCustomPalette)) - { - dialog.CustomPalette = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbDialogAttributesRTLRO == (attributes & MsiInterop.MsidbDialogAttributesRTLRO)) - { - dialog.RightToLeft = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbDialogAttributesRightAligned == (attributes & MsiInterop.MsidbDialogAttributesRightAligned)) - { - dialog.RightAligned = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbDialogAttributesLeftScroll == (attributes & MsiInterop.MsidbDialogAttributesLeftScroll)) - { - dialog.LeftScroll = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbDialogAttributesError == (attributes & MsiInterop.MsidbDialogAttributesError)) - { - dialog.ErrorDialog = Wix.YesNoType.yes; - } - } - - if (null != row[6]) - { - dialog.Title = Convert.ToString(row[6]); - } - - this.core.UIElement.AddChild(dialog); - this.core.IndexElement(row, dialog); - } - } - - /// - /// Decompile the Directory table. - /// - /// The table to decompile. - private void DecompileDirectoryTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Directory directory = new Wix.Directory(); - - directory.Id = Convert.ToString(row[0]); - - string[] names = Common.GetNames(Convert.ToString(row[2])); - - if (String.Equals(directory.Id, "TARGETDIR", StringComparison.Ordinal) && !String.Equals(names[0], "SourceDir", StringComparison.Ordinal)) - { - this.core.OnMessage(WixWarnings.TargetDirCorrectedDefaultDir()); - directory.Name = "SourceDir"; - } - else - { - if (null != names[0] && "." != names[0]) - { - if (null != names[1]) - { - directory.ShortName = names[0]; - } - else - { - directory.Name = names[0]; - } - } - - if (null != names[1]) - { - directory.Name = names[1]; - } - } - - if (null != names[2]) - { - if (null != names[3]) - { - directory.ShortSourceName = names[2]; - } - else - { - directory.SourceName = names[2]; - } - } - - if (null != names[3]) - { - directory.SourceName = names[3]; - } - - this.core.IndexElement(row, directory); - } - - // nest the directories - foreach (Row row in table.Rows) - { - Wix.Directory directory = (Wix.Directory)this.core.GetIndexedElement(row); - - if (null == row[1]) - { - this.core.RootElement.AddChild(directory); - } - else - { - Wix.Directory parentDirectory = (Wix.Directory)this.core.GetIndexedElement("Directory", Convert.ToString(row[1])); - - if (null == parentDirectory) - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", Convert.ToString(row[1]), "Directory")); - } - else if (parentDirectory == directory) // another way to specify a root directory - { - this.core.RootElement.AddChild(directory); - } - else - { - parentDirectory.AddChild(directory); - } - } - } - } - - /// - /// Decompile the DrLocator table. - /// - /// The table to decompile. - private void DecompileDrLocatorTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.DirectorySearch directorySearch = new Wix.DirectorySearch(); - - directorySearch.Id = Convert.ToString(row[0]); - - if (null != row[2]) - { - directorySearch.Path = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - directorySearch.Depth = Convert.ToInt32(row[3]); - } - - this.core.IndexElement(row, directorySearch); - } - } - - /// - /// Decompile the DuplicateFile table. - /// - /// The table to decompile. - private void DecompileDuplicateFileTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.CopyFile copyFile = new Wix.CopyFile(); - - copyFile.Id = Convert.ToString(row[0]); - - copyFile.FileId = Convert.ToString(row[2]); - - if (null != row[3]) - { - string[] names = Common.GetNames(Convert.ToString(row[3])); - if (null != names[0] && null != names[1]) - { - copyFile.DestinationShortName = names[0]; - copyFile.DestinationName = names[1]; - } - else if (null != names[0]) - { - copyFile.DestinationName = names[0]; - } - } - - // destination directory/property is set in FinalizeDuplicateMoveFileTables - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(copyFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, copyFile); - } - } - - /// - /// Decompile the Environment table. - /// - /// The table to decompile. - private void DecompileEnvironmentTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Environment environment = new Wix.Environment(); - - environment.Id = Convert.ToString(row[0]); - - bool done = false; - bool permanent = true; - string name = Convert.ToString(row[1]); - for (int i = 0; i < name.Length && !done; i++) - { - switch (name[i]) - { - case '=': - environment.Action = Wix.Environment.ActionType.set; - break; - case '+': - environment.Action = Wix.Environment.ActionType.create; - break; - case '-': - permanent = false; - break; - case '!': - environment.Action = Wix.Environment.ActionType.remove; - break; - case '*': - environment.System = Wix.YesNoType.yes; - break; - default: - environment.Name = name.Substring(i); - done = true; - break; - } - } - - if (permanent) - { - environment.Permanent = Wix.YesNoType.yes; - } - - if (null != row[2]) - { - string value = Convert.ToString(row[2]); - - if (value.StartsWith("[~]", StringComparison.Ordinal)) - { - environment.Part = Wix.Environment.PartType.last; - - if (3 < value.Length) - { - environment.Separator = value.Substring(3, 1); - environment.Value = value.Substring(4); - } - } - else if (value.EndsWith("[~]", StringComparison.Ordinal)) - { - environment.Part = Wix.Environment.PartType.first; - - if (3 < value.Length) - { - environment.Separator = value.Substring(value.Length - 4, 1); - environment.Value = value.Substring(0, value.Length - 4); - } - } - else - { - environment.Value = value; - } - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[3])); - if (null != component) - { - component.AddChild(environment); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[3]), "Component")); - } - } - } - - /// - /// Decompile the Error table. - /// - /// The table to decompile. - private void DecompileErrorTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Error error = new Wix.Error(); - - error.Id = Convert.ToInt32(row[0]); - - error.Content = Convert.ToString(row[1]); - - this.core.UIElement.AddChild(error); - } - } - - /// - /// Decompile the EventMapping table. - /// - /// The table to decompile. - private void DecompileEventMappingTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Subscribe subscribe = new Wix.Subscribe(); - - subscribe.Event = Convert.ToString(row[2]); - - subscribe.Attribute = Convert.ToString(row[3]); - - Wix.Control control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1])); - if (null != control) - { - control.AddChild(subscribe); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control")); - } - } - } - - /// - /// Decompile the Extension table. - /// - /// The table to decompile. - private void DecompileExtensionTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Extension extension = new Wix.Extension(); - - extension.Advertise = Wix.YesNoType.yes; - - extension.Id = Convert.ToString(row[0]); - - if (null != row[3]) - { - Wix.MIME mime = (Wix.MIME)this.core.GetIndexedElement("MIME", Convert.ToString(row[3])); - - if (null != mime) - { - mime.Default = Wix.YesNoType.yes; - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", Convert.ToString(row[3]), "MIME")); - } - } - - if (null != row[2]) - { - Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[2])); - - if (null != progId) - { - progId.AddChild(extension); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_", Convert.ToString(row[2]), "ProgId")); - } - } - else - { - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - - if (null != component) - { - component.AddChild(extension); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - } - - this.core.IndexElement(row, extension); - } - } - - /// - /// Decompile the ExternalFiles table. - /// - /// The table to decompile. - private void DecompileExternalFilesTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ExternalFile externalFile = new Wix.ExternalFile(); - - externalFile.File = Convert.ToString(row[1]); - - externalFile.Source = Convert.ToString(row[2]); - - if (null != row[3]) - { - string[] symbolPaths = (Convert.ToString(row[3])).Split(';'); - - foreach (string symbolPathString in symbolPaths) - { - Wix.SymbolPath symbolPath = new Wix.SymbolPath(); - - symbolPath.Path = symbolPathString; - - externalFile.AddChild(symbolPath); - } - } - - if (null != row[4] && null != row[5]) - { - string[] ignoreOffsets = (Convert.ToString(row[4])).Split(','); - string[] ignoreLengths = (Convert.ToString(row[5])).Split(','); - - if (ignoreOffsets.Length == ignoreLengths.Length) - { - for (int i = 0; i < ignoreOffsets.Length; i++) - { - Wix.IgnoreRange ignoreRange = new Wix.IgnoreRange(); - - if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) - { - ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i].Substring(2), 16); - } - else - { - ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture); - } - - if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) - { - ignoreRange.Length = Convert.ToInt32(ignoreLengths[i].Substring(2), 16); - } - else - { - ignoreRange.Length = Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture); - } - - externalFile.AddChild(ignoreRange); - } - } - else - { - // TODO: warn - } - } - else if (null != row[4] || null != row[5]) - { - // TODO: warn about mismatch between columns - } - - // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable - - if (null != row[7]) - { - externalFile.Order = Convert.ToInt32(row[7]); - } - - Wix.Family family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[0])); - if (null != family) - { - family.AddChild(externalFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[0]), "ImageFamilies")); - } - this.core.IndexElement(row, externalFile); - } - } - - /// - /// Decompile the Feature table. - /// - /// The table to decompile. - private void DecompileFeatureTable(Table table) - { - SortedList sortedFeatures = new SortedList(); - - foreach (Row row in table.Rows) - { - Wix.Feature feature = new Wix.Feature(); - - feature.Id = Convert.ToString(row[0]); - - if (null != row[2]) - { - feature.Title = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - feature.Description = Convert.ToString(row[3]); - } - - if (null == row[4]) - { - feature.Display = "hidden"; - } - else - { - int display = Convert.ToInt32(row[4]); - - if (0 == display) - { - feature.Display = "hidden"; - } - else if (1 == display % 2) - { - feature.Display = "expand"; - } - } - - feature.Level = Convert.ToInt32(row[5]); - - if (null != row[6]) - { - feature.ConfigurableDirectory = Convert.ToString(row[6]); - } - - int attributes = Convert.ToInt32(row[7]); - - if (MsiInterop.MsidbFeatureAttributesFavorSource == (attributes & MsiInterop.MsidbFeatureAttributesFavorSource) && MsiInterop.MsidbFeatureAttributesFollowParent == (attributes & MsiInterop.MsidbFeatureAttributesFollowParent)) - { - // TODO: display a warning for setting favor local and follow parent together - } - else if (MsiInterop.MsidbFeatureAttributesFavorSource == (attributes & MsiInterop.MsidbFeatureAttributesFavorSource)) - { - feature.InstallDefault = Wix.Feature.InstallDefaultType.source; - } - else if (MsiInterop.MsidbFeatureAttributesFollowParent == (attributes & MsiInterop.MsidbFeatureAttributesFollowParent)) - { - feature.InstallDefault = Wix.Feature.InstallDefaultType.followParent; - } - - if (MsiInterop.MsidbFeatureAttributesFavorAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesFavorAdvertise)) - { - feature.TypicalDefault = Wix.Feature.TypicalDefaultType.advertise; - } - - if (MsiInterop.MsidbFeatureAttributesDisallowAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesDisallowAdvertise) && - MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise)) - { - this.core.OnMessage(WixWarnings.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no")); - feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.no; - } - else if (MsiInterop.MsidbFeatureAttributesDisallowAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesDisallowAdvertise)) - { - feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.no; - } - else if (MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise)) - { - feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.system; - } - - if (MsiInterop.MsidbFeatureAttributesUIDisallowAbsent == (attributes & MsiInterop.MsidbFeatureAttributesUIDisallowAbsent)) - { - feature.Absent = Wix.Feature.AbsentType.disallow; - } - - this.core.IndexElement(row, feature); - - // sort the features by their display column (and append the identifier to ensure unique keys) - sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", Convert.ToInt32(row[4], CultureInfo.InvariantCulture), row[0]), row); - } - - // nest the features - foreach (Row row in sortedFeatures.Values) - { - Wix.Feature feature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); - - if (null == row[1]) - { - this.core.RootElement.AddChild(feature); - } - else - { - Wix.Feature parentFeature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[1])); - - if (null == parentFeature) - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", Convert.ToString(row[1]), "Feature")); - } - else if (parentFeature == feature) - { - // TODO: display a warning about self-nesting - } - else - { - parentFeature.AddChild(feature); - } - } - } - } - - /// - /// Decompile the FeatureComponents table. - /// - /// The table to decompile. - private void DecompileFeatureComponentsTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ComponentRef componentRef = new Wix.ComponentRef(); - - componentRef.Id = Convert.ToString(row[1]); - - Wix.Feature parentFeature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); - if (null != parentFeature) - { - parentFeature.AddChild(componentRef); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", Convert.ToString(row[0]), "Feature")); - } - this.core.IndexElement(row, componentRef); - } - } - - /// - /// Decompile the File table. - /// - /// The table to decompile. - private void DecompileFileTable(Table table) - { - foreach (FileRow fileRow in table.Rows) - { - Wix.File file = new Wix.File(); - - file.Id = fileRow.File; - - string[] names = Common.GetNames(fileRow.FileName); - if (null != names[0] && null != names[1]) - { - file.ShortName = names[0]; - file.Name = names[1]; - } - else if (null != names[0]) - { - file.Name = names[0]; - } - - if (null != fileRow.Version && 0 < fileRow.Version.Length) - { - if (!Char.IsDigit(fileRow.Version[0])) - { - file.CompanionFile = fileRow.Version; - } - } - - if (MsiInterop.MsidbFileAttributesReadOnly == (fileRow.Attributes & MsiInterop.MsidbFileAttributesReadOnly)) - { - file.ReadOnly = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbFileAttributesHidden == (fileRow.Attributes & MsiInterop.MsidbFileAttributesHidden)) - { - file.Hidden = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbFileAttributesSystem == (fileRow.Attributes & MsiInterop.MsidbFileAttributesSystem)) - { - file.System = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbFileAttributesVital != (fileRow.Attributes & MsiInterop.MsidbFileAttributesVital)) - { - file.Vital = Wix.YesNoType.no; - } - - if (MsiInterop.MsidbFileAttributesChecksum == (fileRow.Attributes & MsiInterop.MsidbFileAttributesChecksum)) - { - file.Checksum = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbFileAttributesNoncompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesNoncompressed) && - MsiInterop.MsidbFileAttributesCompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesCompressed)) - { - // TODO: error - } - else if (MsiInterop.MsidbFileAttributesNoncompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesNoncompressed)) - { - file.Compressed = Wix.YesNoDefaultType.no; - } - else if (MsiInterop.MsidbFileAttributesCompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesCompressed)) - { - file.Compressed = Wix.YesNoDefaultType.yes; - } - - this.core.IndexElement(fileRow, file); - } - } - - /// - /// Decompile the FileSFPCatalog table. - /// - /// The table to decompile. - private void DecompileFileSFPCatalogTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.SFPFile sfpFile = new Wix.SFPFile(); - - sfpFile.Id = Convert.ToString(row[0]); - - Wix.SFPCatalog sfpCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement("SFPCatalog", Convert.ToString(row[1])); - if (null != sfpCatalog) - { - sfpCatalog.AddChild(sfpFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SFPCatalog_", Convert.ToString(row[1]), "SFPCatalog")); - } - } - } - - /// - /// Decompile the Font table. - /// - /// The table to decompile. - private void DecompileFontTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.File file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); - - if (null != file) - { - if (null != row[1]) - { - file.FontTitle = Convert.ToString(row[1]); - } - else - { - file.TrueType = Wix.YesNoType.yes; - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); - } - } - } - - /// - /// Decompile the Icon table. - /// - /// The table to decompile. - private void DecompileIconTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Icon icon = new Wix.Icon(); - - icon.Id = Convert.ToString(row[0]); - - icon.SourceFile = Convert.ToString(row[1]); - - this.core.RootElement.AddChild(icon); - } - } - - /// - /// Decompile the ImageFamilies table. - /// - /// The table to decompile. - private void DecompileImageFamiliesTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Family family = new Wix.Family(); - - family.Name = Convert.ToString(row[0]); - - if (null != row[1]) - { - family.MediaSrcProp = Convert.ToString(row[1]); - } - - if (null != row[2]) - { - family.DiskId = Convert.ToString(Convert.ToInt32(row[2])); - } - - if (null != row[3]) - { - family.SequenceStart = Convert.ToInt32(row[3]); - } - - if (null != row[4]) - { - family.DiskPrompt = Convert.ToString(row[4]); - } - - if (null != row[5]) - { - family.VolumeLabel = Convert.ToString(row[5]); - } - - this.core.RootElement.AddChild(family); - this.core.IndexElement(row, family); - } - } - - /// - /// Decompile the IniFile table. - /// - /// The table to decompile. - private void DecompileIniFileTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.IniFile iniFile = new Wix.IniFile(); - - iniFile.Id = Convert.ToString(row[0]); - - string[] names = Common.GetNames(Convert.ToString(row[1])); - - if (null != names[0]) - { - if (null == names[1]) - { - iniFile.Name = names[0]; - } - else - { - iniFile.ShortName = names[0]; - } - } - - if (null != names[1]) - { - iniFile.Name = names[1]; - } - - if (null != row[2]) - { - iniFile.Directory = Convert.ToString(row[2]); - } - - iniFile.Section = Convert.ToString(row[3]); - - iniFile.Key = Convert.ToString(row[4]); - - iniFile.Value = Convert.ToString(row[5]); - - switch (Convert.ToInt32(row[6])) - { - case MsiInterop.MsidbIniFileActionAddLine: - iniFile.Action = Wix.IniFile.ActionType.addLine; - break; - case MsiInterop.MsidbIniFileActionCreateLine: - iniFile.Action = Wix.IniFile.ActionType.createLine; - break; - case MsiInterop.MsidbIniFileActionAddTag: - iniFile.Action = Wix.IniFile.ActionType.addTag; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[7])); - if (null != component) - { - component.AddChild(iniFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[7]), "Component")); - } - } - } - - /// - /// Decompile the IniLocator table. - /// - /// The table to decompile. - private void DecompileIniLocatorTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.IniFileSearch iniFileSearch = new Wix.IniFileSearch(); - - iniFileSearch.Id = Convert.ToString(row[0]); - - string[] names = Common.GetNames(Convert.ToString(row[1])); - if (null != names[0] && null != names[1]) - { - iniFileSearch.ShortName = names[0]; - iniFileSearch.Name = names[1]; - } - else if (null != names[0]) - { - iniFileSearch.Name = names[0]; - } - - iniFileSearch.Section = Convert.ToString(row[2]); - - iniFileSearch.Key = Convert.ToString(row[3]); - - if (null != row[4]) - { - int field = Convert.ToInt32(row[4]); - - if (0 != field) - { - iniFileSearch.Field = field; - } - } - - if (null != row[5]) - { - switch (Convert.ToInt32(row[5])) - { - case MsiInterop.MsidbLocatorTypeDirectory: - iniFileSearch.Type = Wix.IniFileSearch.TypeType.directory; - break; - case MsiInterop.MsidbLocatorTypeFileName: - // this is the default value - break; - case MsiInterop.MsidbLocatorTypeRawValue: - iniFileSearch.Type = Wix.IniFileSearch.TypeType.raw; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); - break; - } - } - - this.core.IndexElement(row, iniFileSearch); - } - } - - /// - /// Decompile the IsolatedComponent table. - /// - /// The table to decompile. - private void DecompileIsolatedComponentTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.IsolateComponent isolateComponent = new Wix.IsolateComponent(); - - isolateComponent.Shared = Convert.ToString(row[0]); - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(isolateComponent); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - } - } - - /// - /// Decompile the LaunchCondition table. - /// - /// The table to decompile. - private void DecompileLaunchConditionTable(Table table) - { - foreach (Row row in table.Rows) - { - if (Compiler.DowngradePreventedCondition == Convert.ToString(row[0]) || Compiler.UpgradePreventedCondition == Convert.ToString(row[0])) - { - continue; // MajorUpgrade rows processed in FinalizeUpgradeTable - } - - Wix.Condition condition = new Wix.Condition(); - - condition.Content = Convert.ToString(row[0]); - - condition.Message = Convert.ToString(row[1]); - - this.core.RootElement.AddChild(condition); - } - } - - /// - /// Decompile the ListBox table. - /// - /// The table to decompile. - private void DecompileListBoxTable(Table table) - { - Wix.ListBox listBox = null; - SortedList listBoxRows = new SortedList(); - - // sort the list boxes by their property and order - foreach (Row row in table.Rows) - { - listBoxRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); - } - - foreach (Row row in listBoxRows.Values) - { - if (null == listBox || Convert.ToString(row[0]) != listBox.Property) - { - listBox = new Wix.ListBox(); - - listBox.Property = Convert.ToString(row[0]); - - this.core.UIElement.AddChild(listBox); - } - - Wix.ListItem listItem = new Wix.ListItem(); - - listItem.Value = Convert.ToString(row[2]); - - if (null != row[3]) - { - listItem.Text = Convert.ToString(row[3]); - } - - listBox.AddChild(listItem); - } - } - - /// - /// Decompile the ListView table. - /// - /// The table to decompile. - private void DecompileListViewTable(Table table) - { - Wix.ListView listView = null; - SortedList listViewRows = new SortedList(); - - // sort the list views by their property and order - foreach (Row row in table.Rows) - { - listViewRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); - } - - foreach (Row row in listViewRows.Values) - { - if (null == listView || Convert.ToString(row[0]) != listView.Property) - { - listView = new Wix.ListView(); - - listView.Property = Convert.ToString(row[0]); - - this.core.UIElement.AddChild(listView); - } - - Wix.ListItem listItem = new Wix.ListItem(); - - listItem.Value = Convert.ToString(row[2]); - - if (null != row[3]) - { - listItem.Text = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - listItem.Icon = Convert.ToString(row[4]); - } - - listView.AddChild(listItem); - } - } - - /// - /// Decompile the LockPermissions table. - /// - /// The table to decompile. - private void DecompileLockPermissionsTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Permission permission = new Wix.Permission(); - string[] specialPermissions; - - switch (Convert.ToString(row[1])) - { - case "CreateFolder": - specialPermissions = Common.FolderPermissions; - break; - case "File": - specialPermissions = Common.FilePermissions; - break; - case "Registry": - specialPermissions = Common.RegistryPermissions; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); - return; - } - - int permissionBits = Convert.ToInt32(row[4]); - for (int i = 0; i < 32; i++) - { - if (0 != ((permissionBits >> i) & 1)) - { - string name = null; - - if (specialPermissions.Length > i) - { - name = specialPermissions[i]; - } - else if (16 > i && specialPermissions.Length <= i) - { - name = "SpecificRightsAll"; - } - else if (28 > i && Common.StandardPermissions.Length > (i - 16)) - { - name = Common.StandardPermissions[i - 16]; - } - else if (0 <= (i - 28) && Common.GenericPermissions.Length > (i - 28)) - { - name = Common.GenericPermissions[i - 28]; - } - - if (null == name) - { - this.core.OnMessage(WixWarnings.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i)); - } - else - { - switch (name) - { - case "Append": - permission.Append = Wix.YesNoType.yes; - break; - case "ChangePermission": - permission.ChangePermission = Wix.YesNoType.yes; - break; - case "CreateChild": - permission.CreateChild = Wix.YesNoType.yes; - break; - case "CreateFile": - permission.CreateFile = Wix.YesNoType.yes; - break; - case "CreateLink": - permission.CreateLink = Wix.YesNoType.yes; - break; - case "CreateSubkeys": - permission.CreateSubkeys = Wix.YesNoType.yes; - break; - case "Delete": - permission.Delete = Wix.YesNoType.yes; - break; - case "DeleteChild": - permission.DeleteChild = Wix.YesNoType.yes; - break; - case "EnumerateSubkeys": - permission.EnumerateSubkeys = Wix.YesNoType.yes; - break; - case "Execute": - permission.Execute = Wix.YesNoType.yes; - break; - case "FileAllRights": - permission.FileAllRights = Wix.YesNoType.yes; - break; - case "GenericAll": - permission.GenericAll = Wix.YesNoType.yes; - break; - case "GenericExecute": - permission.GenericExecute = Wix.YesNoType.yes; - break; - case "GenericRead": - permission.GenericRead = Wix.YesNoType.yes; - break; - case "GenericWrite": - permission.GenericWrite = Wix.YesNoType.yes; - break; - case "Notify": - permission.Notify = Wix.YesNoType.yes; - break; - case "Read": - permission.Read = Wix.YesNoType.yes; - break; - case "ReadAttributes": - permission.ReadAttributes = Wix.YesNoType.yes; - break; - case "ReadExtendedAttributes": - permission.ReadExtendedAttributes = Wix.YesNoType.yes; - break; - case "ReadPermission": - permission.ReadPermission = Wix.YesNoType.yes; - break; - case "SpecificRightsAll": - permission.SpecificRightsAll = Wix.YesNoType.yes; - break; - case "Synchronize": - permission.Synchronize = Wix.YesNoType.yes; - break; - case "TakeOwnership": - permission.TakeOwnership = Wix.YesNoType.yes; - break; - case "Traverse": - permission.Traverse = Wix.YesNoType.yes; - break; - case "Write": - permission.Write = Wix.YesNoType.yes; - break; - case "WriteAttributes": - permission.WriteAttributes = Wix.YesNoType.yes; - break; - case "WriteExtendedAttributes": - permission.WriteExtendedAttributes = Wix.YesNoType.yes; - break; - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknownPermissionAttribute, name)); - } - } - } - } - - if (null != row[2]) - { - permission.Domain = Convert.ToString(row[2]); - } - - permission.User = Convert.ToString(row[3]); - - this.core.IndexElement(row, permission); - } - } - - /// - /// Decompile the Media table. - /// - /// The table to decompile. - private void DecompileMediaTable(Table table) - { - foreach (MediaRow mediaRow in table.Rows) - { - Wix.Media media = new Wix.Media(); - - media.Id = Convert.ToString(mediaRow.DiskId); - - if (null != mediaRow.DiskPrompt) - { - media.DiskPrompt = mediaRow.DiskPrompt; - } - - if (null != mediaRow.Cabinet) - { - string cabinet = mediaRow.Cabinet; - - if (cabinet.StartsWith("#", StringComparison.Ordinal)) - { - media.EmbedCab = Wix.YesNoType.yes; - cabinet = cabinet.Substring(1); - } - - media.Cabinet = cabinet; - } - - if (null != mediaRow.VolumeLabel) - { - media.VolumeLabel = mediaRow.VolumeLabel; - } - - this.core.RootElement.AddChild(media); - this.core.IndexElement(mediaRow, media); - } - } - - /// - /// Decompile the MIME table. - /// - /// The table to decompile. - private void DecompileMIMETable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.MIME mime = new Wix.MIME(); - - mime.ContentType = Convert.ToString(row[0]); - - if (null != row[2]) - { - mime.Class = Convert.ToString(row[2]); - } - - this.core.IndexElement(row, mime); - } - } - - /// - /// Decompile the ModuleConfiguration table. - /// - /// The table to decompile. - private void DecompileModuleConfigurationTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Configuration configuration = new Wix.Configuration(); - - configuration.Name = Convert.ToString(row[0]); - - switch (Convert.ToInt32(row[1])) - { - case 0: - configuration.Format = Wix.Configuration.FormatType.Text; - break; - case 1: - configuration.Format = Wix.Configuration.FormatType.Key; - break; - case 2: - configuration.Format = Wix.Configuration.FormatType.Integer; - break; - case 3: - configuration.Format = Wix.Configuration.FormatType.Bitfield; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - if (null != row[2]) - { - configuration.Type = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - configuration.ContextData = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - configuration.DefaultValue = Convert.ToString(row[4]); - } - - if (null != row[5]) - { - int attributes = Convert.ToInt32(row[5]); - - if (MsiInterop.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & MsiInterop.MsidbMsmConfigurableOptionKeyNoOrphan)) - { - configuration.KeyNoOrphan = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbMsmConfigurableOptionNonNullable == (attributes & MsiInterop.MsidbMsmConfigurableOptionNonNullable)) - { - configuration.NonNullable = Wix.YesNoType.yes; - } - - if (3 < attributes) - { - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); - } - } - - if (null != row[6]) - { - configuration.DisplayName = Convert.ToString(row[6]); - } - - if (null != row[7]) - { - configuration.Description = Convert.ToString(row[7]); - } - - if (null != row[8]) - { - configuration.HelpLocation = Convert.ToString(row[8]); - } - - if (null != row[9]) - { - configuration.HelpKeyword = Convert.ToString(row[9]); - } - - this.core.RootElement.AddChild(configuration); - } - } - - /// - /// Decompile the ModuleDependency table. - /// - /// The table to decompile. - private void DecompileModuleDependencyTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Dependency dependency = new Wix.Dependency(); - - dependency.RequiredId = Convert.ToString(row[2]); - - dependency.RequiredLanguage = Convert.ToInt32(row[3], CultureInfo.InvariantCulture); - - if (null != row[4]) - { - dependency.RequiredVersion = Convert.ToString(row[4]); - } - - this.core.RootElement.AddChild(dependency); - } - } - - /// - /// Decompile the ModuleExclusion table. - /// - /// The table to decompile. - private void DecompileModuleExclusionTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Exclusion exclusion = new Wix.Exclusion(); - - exclusion.ExcludedId = Convert.ToString(row[2]); - - int excludedLanguage = Convert.ToInt32(Convert.ToString(row[3]), CultureInfo.InvariantCulture); - if (0 < excludedLanguage) - { - exclusion.ExcludeLanguage = excludedLanguage; - } - else if (0 > excludedLanguage) - { - exclusion.ExcludeExceptLanguage = -excludedLanguage; - } - - if (null != row[4]) - { - exclusion.ExcludedMinVersion = Convert.ToString(row[4]); - } - - if (null != row[5]) - { - exclusion.ExcludedMinVersion = Convert.ToString(row[5]); - } - - this.core.RootElement.AddChild(exclusion); - } - } - - /// - /// Decompile the ModuleIgnoreTable table. - /// - /// The table to decompile. - private void DecompileModuleIgnoreTableTable(Table table) - { - foreach (Row row in table.Rows) - { - string tableName = Convert.ToString(row[0]); - - // the linker automatically adds a ModuleIgnoreTable row for some tables - if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName) - { - Wix.IgnoreTable ignoreTable = new Wix.IgnoreTable(); - - ignoreTable.Id = tableName; - - this.core.RootElement.AddChild(ignoreTable); - } - } - } - - /// - /// Decompile the ModuleSignature table. - /// - /// The table to decompile. - private void DecompileModuleSignatureTable(Table table) - { - if (1 == table.Rows.Count) - { - Row row = table.Rows[0]; - - Wix.Module module = (Wix.Module)this.core.RootElement; - - module.Id = Convert.ToString(row[0]); - - // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability) - module.Language = Convert.ToString(row[1], CultureInfo.InvariantCulture); - - module.Version = Convert.ToString(row[2]); - } - else - { - // TODO: warn - } - } - - /// - /// Decompile the ModuleSubstitution table. - /// - /// The table to decompile. - private void DecompileModuleSubstitutionTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Substitution substitution = new Wix.Substitution(); - - substitution.Table = Convert.ToString(row[0]); - - substitution.Row = Convert.ToString(row[1]); - - substitution.Column = Convert.ToString(row[2]); - - if (null != row[3]) - { - substitution.Value = Convert.ToString(row[3]); - } - - this.core.RootElement.AddChild(substitution); - } - } - - /// - /// Decompile the MoveFile table. - /// - /// The table to decompile. - private void DecompileMoveFileTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.CopyFile copyFile = new Wix.CopyFile(); - - copyFile.Id = Convert.ToString(row[0]); - - if (null != row[2]) - { - copyFile.SourceName = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - string[] names = Common.GetNames(Convert.ToString(row[3])); - if (null != names[0] && null != names[1]) - { - copyFile.DestinationShortName = names[0]; - copyFile.DestinationName = names[1]; - } - else if (null != names[0]) - { - copyFile.DestinationName = names[0]; - } - } - - // source/destination directory/property is set in FinalizeDuplicateMoveFileTables - - switch (Convert.ToInt32(row[6])) - { - case 0: - break; - case MsiInterop.MsidbMoveFileOptionsMove: - copyFile.Delete = Wix.YesNoType.yes; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(copyFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, copyFile); - } - } - - /// - /// Decompile the MsiDigitalCertificate table. - /// - /// The table to decompile. - private void DecompileMsiDigitalCertificateTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.DigitalCertificate digitalCertificate = new Wix.DigitalCertificate(); - - digitalCertificate.Id = Convert.ToString(row[0]); - - digitalCertificate.SourceFile = Convert.ToString(row[1]); - - this.core.IndexElement(row, digitalCertificate); - } - } - - /// - /// Decompile the MsiDigitalSignature table. - /// - /// The table to decompile. - private void DecompileMsiDigitalSignatureTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.DigitalSignature digitalSignature = new Wix.DigitalSignature(); - - if (null != row[3]) - { - digitalSignature.SourceFile = Convert.ToString(row[3]); - } - - Wix.DigitalCertificate digitalCertificate = (Wix.DigitalCertificate)this.core.GetIndexedElement("MsiDigitalCertificate", Convert.ToString(row[2])); - if (null != digitalCertificate) - { - digitalSignature.AddChild(digitalCertificate); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", Convert.ToString(row[2]), "MsiDigitalCertificate")); - } - - Wix.IParentElement parentElement = (Wix.IParentElement)this.core.GetIndexedElement(Convert.ToString(row[0]), Convert.ToString(row[1])); - if (null != parentElement) - { - parentElement.AddChild(digitalSignature); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", Convert.ToString(row[1]), Convert.ToString(row[0]))); - } - } - } - - /// - /// Decompile the MsiEmbeddedChainer table. - /// - /// The table to decompile. - private void DecompileMsiEmbeddedChainerTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.EmbeddedChainer embeddedChainer = new Wix.EmbeddedChainer(); - - embeddedChainer.Id = Convert.ToString(row[0]); - - embeddedChainer.Content = Convert.ToString(row[1]); - - if (null != row[2]) - { - embeddedChainer.CommandLine = Convert.ToString(row[2]); - } - - switch (Convert.ToInt32(row[4])) - { - case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeBinaryData: - embeddedChainer.BinarySource = Convert.ToString(row[3]); - break; - case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeSourceFile: - embeddedChainer.FileSource = Convert.ToString(row[3]); - break; - case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeProperty: - embeddedChainer.PropertySource = Convert.ToString(row[3]); - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - this.core.RootElement.AddChild(embeddedChainer); - } - } - - /// - /// Decompile the MsiEmbeddedUI table. - /// - /// The table to decompile. - private void DecompileMsiEmbeddedUITable(Table table) - { - Wix.EmbeddedUI embeddedUI = new Wix.EmbeddedUI(); - bool foundEmbeddedUI = false; - bool foundEmbeddedResources = false; - - foreach (Row row in table.Rows) - { - int attributes = Convert.ToInt32(row[2]); - - if (MsiInterop.MsidbEmbeddedUI == (attributes & MsiInterop.MsidbEmbeddedUI)) - { - if (foundEmbeddedUI) - { - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - } - else - { - embeddedUI.Id = Convert.ToString(row[0]); - embeddedUI.Name = Convert.ToString(row[1]); - - int messageFilter = Convert.ToInt32(row[3]); - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_FATALEXIT)) - { - embeddedUI.IgnoreFatalExit = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ERROR)) - { - embeddedUI.IgnoreError = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_WARNING)) - { - embeddedUI.IgnoreWarning = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_USER)) - { - embeddedUI.IgnoreUser = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INFO)) - { - embeddedUI.IgnoreInfo = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_FILESINUSE)) - { - embeddedUI.IgnoreFilesInUse = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_RESOLVESOURCE)) - { - embeddedUI.IgnoreResolveSource = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_OUTOFDISKSPACE)) - { - embeddedUI.IgnoreOutOfDiskSpace = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ACTIONSTART)) - { - embeddedUI.IgnoreActionStart = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ACTIONDATA)) - { - embeddedUI.IgnoreActionData = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_PROGRESS)) - { - embeddedUI.IgnoreProgress = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_COMMONDATA)) - { - embeddedUI.IgnoreCommonData = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INITIALIZE)) - { - embeddedUI.IgnoreInitialize = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_TERMINATE)) - { - embeddedUI.IgnoreTerminate = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_SHOWDIALOG)) - { - embeddedUI.IgnoreShowDialog = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_RMFILESINUSE)) - { - embeddedUI.IgnoreRMFilesInUse = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INSTALLSTART)) - { - embeddedUI.IgnoreInstallStart = Wix.YesNoType.yes; - } - - if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INSTALLEND)) - { - embeddedUI.IgnoreInstallEnd = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbEmbeddedHandlesBasic == (attributes & MsiInterop.MsidbEmbeddedHandlesBasic)) - { - embeddedUI.SupportBasicUI = Wix.YesNoType.yes; - } - - embeddedUI.SourceFile = Convert.ToString(row[4]); - - this.core.UIElement.AddChild(embeddedUI); - foundEmbeddedUI = true; - } - } - else - { - Wix.EmbeddedUIResource embeddedResource = new Wix.EmbeddedUIResource(); - - embeddedResource.Id = Convert.ToString(row[0]); - embeddedResource.Name = Convert.ToString(row[1]); - embeddedResource.SourceFile = Convert.ToString(row[4]); - - embeddedUI.AddChild(embeddedResource); - foundEmbeddedResources = true; - } - } - - if (!foundEmbeddedUI && foundEmbeddedResources) - { - // TODO: warn - } - } - - /// - /// Decompile the MsiLockPermissionsEx table. - /// - /// The table to decompile. - private void DecompileMsiLockPermissionsExTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.PermissionEx permissionEx = new Wix.PermissionEx(); - permissionEx.Id = Convert.ToString(row[0]); - permissionEx.Sddl = Convert.ToString(row[3]); - - if (null != row[4]) - { - Wix.Condition condition = new Wix.Condition(); - condition.Content = Convert.ToString(row[4]); - permissionEx.AddChild(condition); - } - - switch (Convert.ToString(row[2])) - { - case "CreateFolder": - case "File": - case "Registry": - case "ServiceInstall": - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); - return; - } - - this.core.IndexElement(row, permissionEx); - } - } - - /// - /// Decompile the MsiPackageCertificate table. - /// - /// The table to decompile. - private void DecompileMsiPackageCertificateTable(Table table) - { - if (0 < table.Rows.Count) - { - Wix.PackageCertificates packageCertificates = new Wix.PackageCertificates(); - this.core.RootElement.AddChild(packageCertificates); - AddCertificates(table, packageCertificates); - } - } - - /// - /// Decompile the MsiPatchCertificate table. - /// - /// The table to decompile. - private void DecompileMsiPatchCertificateTable(Table table) - { - if (0 < table.Rows.Count) - { - Wix.PatchCertificates patchCertificates = new Wix.PatchCertificates(); - this.core.RootElement.AddChild(patchCertificates); - AddCertificates(table, patchCertificates); - } - } - - /// - /// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table. - /// - /// The table being decompiled. - /// DigitalCertificate parent - private void AddCertificates(Table table, Wix.IParentElement parent) - { - foreach (Row row in table.Rows) - { - Wix.DigitalCertificate digitalCertificate = (Wix.DigitalCertificate)this.core.GetIndexedElement("MsiDigitalCertificate", Convert.ToString(row[1])); - - if (null != digitalCertificate) - { - parent.AddChild(digitalCertificate); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", Convert.ToString(row[1]), "MsiDigitalCertificate")); - } - } - } - - /// - /// Decompile the MsiShortcutProperty table. - /// - /// The table to decompile. - private void DecompileMsiShortcutPropertyTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ShortcutProperty property = new Wix.ShortcutProperty(); - property.Id = Convert.ToString(row[0]); - property.Key = Convert.ToString(row[2]); - property.Value = Convert.ToString(row[3]); - - Wix.Shortcut shortcut = (Wix.Shortcut)this.core.GetIndexedElement("Shortcut", Convert.ToString(row[1])); - if (null != shortcut) - { - shortcut.AddChild(property); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Shortcut_", Convert.ToString(row[1]), "Shortcut")); - } - } - } - - /// - /// Decompile the ODBCAttribute table. - /// - /// The table to decompile. - private void DecompileODBCAttributeTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Property property = new Wix.Property(); - - property.Id = Convert.ToString(row[1]); - - if (null != row[2]) - { - property.Value = Convert.ToString(row[2]); - } - - Wix.ODBCDriver odbcDriver = (Wix.ODBCDriver)this.core.GetIndexedElement("ODBCDriver", Convert.ToString(row[0])); - if (null != odbcDriver) - { - odbcDriver.AddChild(property); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Driver_", Convert.ToString(row[0]), "ODBCDriver")); - } - } - } - - /// - /// Decompile the ODBCDataSource table. - /// - /// The table to decompile. - private void DecompileODBCDataSourceTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ODBCDataSource odbcDataSource = new Wix.ODBCDataSource(); - - odbcDataSource.Id = Convert.ToString(row[0]); - - odbcDataSource.Name = Convert.ToString(row[2]); - - odbcDataSource.DriverName = Convert.ToString(row[3]); - - switch (Convert.ToInt32(row[4])) - { - case MsiInterop.MsidbODBCDataSourceRegistrationPerMachine: - odbcDataSource.Registration = Wix.ODBCDataSource.RegistrationType.machine; - break; - case MsiInterop.MsidbODBCDataSourceRegistrationPerUser: - odbcDataSource.Registration = Wix.ODBCDataSource.RegistrationType.user; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - this.core.IndexElement(row, odbcDataSource); - } - } - - /// - /// Decompile the ODBCDriver table. - /// - /// The table to decompile. - private void DecompileODBCDriverTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ODBCDriver odbcDriver = new Wix.ODBCDriver(); - - odbcDriver.Id = Convert.ToString(row[0]); - - odbcDriver.Name = Convert.ToString(row[2]); - - odbcDriver.File = Convert.ToString(row[3]); - - if (null != row[4]) - { - odbcDriver.SetupFile = Convert.ToString(row[4]); - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(odbcDriver); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, odbcDriver); - } - } - - /// - /// Decompile the ODBCSourceAttribute table. - /// - /// The table to decompile. - private void DecompileODBCSourceAttributeTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Property property = new Wix.Property(); - - property.Id = Convert.ToString(row[1]); - - if (null != row[2]) - { - property.Value = Convert.ToString(row[2]); - } - - Wix.ODBCDataSource odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement("ODBCDataSource", Convert.ToString(row[0])); - if (null != odbcDataSource) - { - odbcDataSource.AddChild(property); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DataSource_", Convert.ToString(row[0]), "ODBCDataSource")); - } - } - } - - /// - /// Decompile the ODBCTranslator table. - /// - /// The table to decompile. - private void DecompileODBCTranslatorTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ODBCTranslator odbcTranslator = new Wix.ODBCTranslator(); - - odbcTranslator.Id = Convert.ToString(row[0]); - - odbcTranslator.Name = Convert.ToString(row[2]); - - odbcTranslator.File = Convert.ToString(row[3]); - - if (null != row[4]) - { - odbcTranslator.SetupFile = Convert.ToString(row[4]); - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(odbcTranslator); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - } - } - - /// - /// Decompile the PatchMetadata table. - /// - /// The table to decompile. - private void DecompilePatchMetadataTable(Table table) - { - if (0 < table.Rows.Count) - { - Wix.PatchMetadata patchMetadata = new Wix.PatchMetadata(); - - foreach (Row row in table.Rows) - { - string value = Convert.ToString(row[2]); - - switch (Convert.ToString(row[1])) - { - case "AllowRemoval": - if ("1" == value) - { - patchMetadata.AllowRemoval = Wix.YesNoType.yes; - } - break; - case "Classification": - if (null != value) - { - patchMetadata.Classification = value; - } - break; - case "CreationTimeUTC": - if (null != value) - { - patchMetadata.CreationTimeUTC = value; - } - break; - case "Description": - if (null != value) - { - patchMetadata.Description = value; - } - break; - case "DisplayName": - if (null != value) - { - patchMetadata.DisplayName = value; - } - break; - case "ManufacturerName": - if (null != value) - { - patchMetadata.ManufacturerName = value; - } - break; - case "MinorUpdateTargetRTM": - if (null != value) - { - patchMetadata.MinorUpdateTargetRTM = value; - } - break; - case "MoreInfoURL": - if (null != value) - { - patchMetadata.MoreInfoURL = value; - } - break; - case "OptimizeCA": - Wix.OptimizeCustomActions optimizeCustomActions = new Wix.OptimizeCustomActions(); - int optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture); - if (0 != (Convert.ToInt32(OptimizeCA.SkipAssignment) & optimizeCA)) - { - optimizeCustomActions.SkipAssignment = Wix.YesNoType.yes; - } - - if (0 != (Convert.ToInt32(OptimizeCA.SkipImmediate) & optimizeCA)) - { - optimizeCustomActions.SkipImmediate = Wix.YesNoType.yes; - } - - if (0 != (Convert.ToInt32(OptimizeCA.SkipDeferred) & optimizeCA)) - { - optimizeCustomActions.SkipDeferred = Wix.YesNoType.yes; - } - - patchMetadata.AddChild(optimizeCustomActions); - break; - case "OptimizedInstallMode": - if ("1" == value) - { - patchMetadata.OptimizedInstallMode = Wix.YesNoType.yes; - } - break; - case "TargetProductName": - if (null != value) - { - patchMetadata.TargetProductName = value; - } - break; - default: - Wix.CustomProperty customProperty = new Wix.CustomProperty(); - - if (null != row[0]) - { - customProperty.Company = Convert.ToString(row[0]); - } - - customProperty.Property = Convert.ToString(row[1]); - - if (null != row[2]) - { - customProperty.Value = Convert.ToString(row[2]); - } - - patchMetadata.AddChild(customProperty); - break; - } - } - - this.core.RootElement.AddChild(patchMetadata); - } - } - - /// - /// Decompile the PatchSequence table. - /// - /// The table to decompile. - private void DecompilePatchSequenceTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.PatchSequence patchSequence = new Wix.PatchSequence(); - - patchSequence.PatchFamily = Convert.ToString(row[0]); - - if (null != row[1]) - { - try - { - Guid guid = new Guid(Convert.ToString(row[1])); - - patchSequence.ProductCode = Convert.ToString(row[1]); - } - catch // non-guid value - { - patchSequence.TargetImage = Convert.ToString(row[1]); - } - } - - if (null != row[2]) - { - patchSequence.Sequence = Convert.ToString(row[2]); - } - - if (null != row[3] && 0x1 == Convert.ToInt32(row[3])) - { - patchSequence.Supersede = Wix.YesNoType.yes; - } - - this.core.RootElement.AddChild(patchSequence); - } - } - - /// - /// Decompile the ProgId table. - /// - /// The table to decompile. - private void DecompileProgIdTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ProgId progId = new Wix.ProgId(); - - progId.Advertise = Wix.YesNoType.yes; - - progId.Id = Convert.ToString(row[0]); - - if (null != row[3]) - { - progId.Description = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - progId.Icon = Convert.ToString(row[4]); - } - - if (null != row[5]) - { - progId.IconIndex = Convert.ToInt32(row[5]); - } - - this.core.IndexElement(row, progId); - } - - // nest the ProgIds - foreach (Row row in table.Rows) - { - Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement(row); - - if (null != row[1]) - { - Wix.ProgId parentProgId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[1])); - - if (null != parentProgId) - { - parentProgId.AddChild(progId); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Parent", Convert.ToString(row[1]), "ProgId")); - } - } - else if (null != row[2]) - { - // nesting is handled in FinalizeProgIdTable - } - else - { - // TODO: warn for orphaned ProgId - } - } - } - - /// - /// Decompile the Properties table. - /// - /// The table to decompile. - private void DecompilePropertiesTable(Table table) - { - Wix.PatchCreation patchCreation = (Wix.PatchCreation)this.core.RootElement; - - foreach (Row row in table.Rows) - { - string name = Convert.ToString(row[0]); - string value = Convert.ToString(row[1]); - - switch (name) - { - case "AllowProductCodeMismatches": - if ("1" == value) - { - patchCreation.AllowProductCodeMismatches = Wix.YesNoType.yes; - } - break; - case "AllowProductVersionMajorMismatches": - if ("1" == value) - { - patchCreation.AllowMajorVersionMismatches = Wix.YesNoType.yes; - } - break; - case "ApiPatchingSymbolFlags": - if (null != value) - { - try - { - // remove the leading "0x" if its present - if (value.StartsWith("0x", StringComparison.Ordinal)) - { - value = value.Substring(2); - } - - patchCreation.SymbolFlags = Convert.ToInt32(value, 16); - } - catch - { - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - } - } - break; - case "DontRemoveTempFolderWhenFinished": - if ("1" == value) - { - patchCreation.CleanWorkingFolder = Wix.YesNoType.no; - } - break; - case "IncludeWholeFilesOnly": - if ("1" == value) - { - patchCreation.WholeFilesOnly = Wix.YesNoType.yes; - } - break; - case "ListOfPatchGUIDsToReplace": - if (null != value) - { - Regex guidRegex = 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}\}"); - MatchCollection guidMatches = guidRegex.Matches(value); - - foreach (Match guidMatch in guidMatches) - { - Wix.ReplacePatch replacePatch = new Wix.ReplacePatch(); - - replacePatch.Id = guidMatch.Value; - - this.core.RootElement.AddChild(replacePatch); - } - } - break; - case "ListOfTargetProductCodes": - if (null != value) - { - string[] targetProductCodes = value.Split(';'); - - foreach (string targetProductCodeString in targetProductCodes) - { - Wix.TargetProductCode targetProductCode = new Wix.TargetProductCode(); - - targetProductCode.Id = targetProductCodeString; - - this.core.RootElement.AddChild(targetProductCode); - } - } - break; - case "PatchGUID": - patchCreation.Id = value; - break; - case "PatchSourceList": - patchCreation.SourceList = value; - break; - case "PatchOutputPath": - patchCreation.OutputPath = value; - break; - default: - Wix.PatchProperty patchProperty = new Wix.PatchProperty(); - - patchProperty.Name = name; - - patchProperty.Value = value; - - this.core.RootElement.AddChild(patchProperty); - break; - } - } - } - - /// - /// Decompile the Property table. - /// - /// The table to decompile. - private void DecompilePropertyTable(Table table) - { - foreach (Row row in table.Rows) - { - string id = Convert.ToString(row[0]); - string value = Convert.ToString(row[1]); - - if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id) - { - if (0 < value.Length) - { - foreach (string propertyId in value.Split(';')) - { - string property = propertyId; - bool suppressModulularization = false; - if (OutputType.Module == this.outputType) - { - if (propertyId.EndsWith(this.modularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal)) - { - property = propertyId.Substring(0, propertyId.Length - this.modularizationGuid.Length + 1); - } - else - { - suppressModulularization = true; - } - } - - Wix.Property specialProperty = this.EnsureProperty(property); - if (suppressModulularization) - { - specialProperty.SuppressModularization = Wix.YesNoType.yes; - } - - switch (id) - { - case "AdminProperties": - specialProperty.Admin = Wix.YesNoType.yes; - break; - case "MsiHiddenProperties": - specialProperty.Hidden = Wix.YesNoType.yes; - break; - case "SecureCustomProperties": - specialProperty.Secure = Wix.YesNoType.yes; - break; - } - } - } - - continue; - } - else if (OutputType.Product == this.outputType) - { - Wix.Product product = (Wix.Product)this.core.RootElement; - - switch (id) - { - case "Manufacturer": - product.Manufacturer = value; - continue; - case "ProductCode": - product.Id = value.ToUpper(CultureInfo.InvariantCulture); - continue; - case "ProductLanguage": - product.Language = value; - continue; - case "ProductName": - product.Name = value; - continue; - case "ProductVersion": - product.Version = value; - continue; - case "UpgradeCode": - product.UpgradeCode = value; - continue; - } - } - - if (!this.suppressUI || "ErrorDialog" != id) - { - Wix.Property property = this.EnsureProperty(id); - - property.Value = value; - } - } - } - - /// - /// Decompile the PublishComponent table. - /// - /// The table to decompile. - private void DecompilePublishComponentTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Category category = new Wix.Category(); - - category.Id = Convert.ToString(row[0]); - - category.Qualifier = Convert.ToString(row[1]); - - if (null != row[3]) - { - category.AppData = Convert.ToString(row[3]); - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2])); - if (null != component) - { - component.AddChild(category); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[2]), "Component")); - } - } - } - - /// - /// Decompile the RadioButton table. - /// - /// The table to decompile. - private void DecompileRadioButtonTable(Table table) - { - SortedList radioButtons = new SortedList(); - Hashtable radioButtonGroups = new Hashtable(); - - foreach (Row row in table.Rows) - { - Wix.RadioButton radioButton = new Wix.RadioButton(); - - radioButton.Value = Convert.ToString(row[2]); - - radioButton.X = Convert.ToString(row[3], CultureInfo.InvariantCulture); - - radioButton.Y = Convert.ToString(row[4], CultureInfo.InvariantCulture); - - radioButton.Width = Convert.ToString(row[5], CultureInfo.InvariantCulture); - - radioButton.Height = Convert.ToString(row[6], CultureInfo.InvariantCulture); - - if (null != row[7]) - { - radioButton.Text = Convert.ToString(row[7]); - } - - if (null != row[8]) - { - string[] help = (Convert.ToString(row[8])).Split('|'); - - if (2 == help.Length) - { - if (0 < help[0].Length) - { - radioButton.ToolTip = help[0]; - } - - if (0 < help[1].Length) - { - radioButton.Help = help[1]; - } - } - } - - radioButtons.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[1]), row); - this.core.IndexElement(row, radioButton); - } - - // nest the radio buttons - foreach (Row row in radioButtons.Values) - { - Wix.RadioButton radioButton = (Wix.RadioButton)this.core.GetIndexedElement(row); - Wix.RadioButtonGroup radioButtonGroup = (Wix.RadioButtonGroup)radioButtonGroups[Convert.ToString(row[0])]; - - if (null == radioButtonGroup) - { - radioButtonGroup = new Wix.RadioButtonGroup(); - - radioButtonGroup.Property = Convert.ToString(row[0]); - - this.core.UIElement.AddChild(radioButtonGroup); - radioButtonGroups.Add(Convert.ToString(row[0]), radioButtonGroup); - } - - radioButtonGroup.AddChild(radioButton); - } - } - - /// - /// Decompile the Registry table. - /// - /// The table to decompile. - private void DecompileRegistryTable(Table table) - { - foreach (Row row in table.Rows) - { - if (("-" == Convert.ToString(row[3]) || "+" == Convert.ToString(row[3]) || "*" == Convert.ToString(row[3])) && null == row[4]) - { - Wix.RegistryKey registryKey = new Wix.RegistryKey(); - - registryKey.Id = Convert.ToString(row[0]); - - Wix.RegistryRootType registryRootType; - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType)) - { - registryKey.Root = registryRootType; - } - - registryKey.Key = Convert.ToString(row[2]); - - switch (Convert.ToString(row[3])) - { - case "+": - registryKey.ForceCreateOnInstall = Wix.YesNoType.yes; - break; - case "-": - registryKey.ForceDeleteOnUninstall = Wix.YesNoType.yes; - break; - case "*": - registryKey.ForceDeleteOnUninstall = Wix.YesNoType.yes; - registryKey.ForceCreateOnInstall = Wix.YesNoType.yes; - break; - } - - this.core.IndexElement(row, registryKey); - } - else - { - Wix.RegistryValue registryValue = new Wix.RegistryValue(); - - registryValue.Id = Convert.ToString(row[0]); - - Wix.RegistryRootType registryRootType; - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType)) - { - registryValue.Root = registryRootType; - } - - registryValue.Key = Convert.ToString(row[2]); - - if (null != row[3]) - { - registryValue.Name = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - string value = Convert.ToString(row[4]); - - if (value.StartsWith("#x", StringComparison.Ordinal)) - { - registryValue.Type = Wix.RegistryValue.TypeType.binary; - registryValue.Value = value.Substring(2); - } - else if (value.StartsWith("#%", StringComparison.Ordinal)) - { - registryValue.Type = Wix.RegistryValue.TypeType.expandable; - registryValue.Value = value.Substring(2); - } - else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal)) - { - registryValue.Type = Wix.RegistryValue.TypeType.integer; - registryValue.Value = value.Substring(1); - } - else - { - if (value.StartsWith("##", StringComparison.Ordinal)) - { - value = value.Substring(1); - } - - if (0 <= value.IndexOf("[~]", StringComparison.Ordinal)) - { - registryValue.Type = Wix.RegistryValue.TypeType.multiString; - - if ("[~]" == value) - { - value = string.Empty; - } - else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal)) - { - value = value.Substring(3, value.Length - 6); - } - else if (value.StartsWith("[~]", StringComparison.Ordinal)) - { - registryValue.Action = Wix.RegistryValue.ActionType.append; - value = value.Substring(3); - } - else if (value.EndsWith("[~]", StringComparison.Ordinal)) - { - registryValue.Action = Wix.RegistryValue.ActionType.prepend; - value = value.Substring(0, value.Length - 3); - } - - string[] multiValues = NullSplitter.Split(value); - foreach (string multiValue in multiValues) - { - Wix.MultiStringValue multiStringValue = new Wix.MultiStringValue(); - - multiStringValue.Content = multiValue; - - registryValue.AddChild(multiStringValue); - } - } - else - { - registryValue.Type = Wix.RegistryValue.TypeType.@string; - registryValue.Value = value; - } - } - } - else - { - registryValue.Type = Wix.RegistryValue.TypeType.@string; - registryValue.Value = String.Empty; - } - - this.core.IndexElement(row, registryValue); - } - } - } - - /// - /// Decompile the RegLocator table. - /// - /// The table to decompile. - private void DecompileRegLocatorTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.RegistrySearch registrySearch = new Wix.RegistrySearch(); - - registrySearch.Id = Convert.ToString(row[0]); - - switch (Convert.ToInt32(row[1])) - { - case MsiInterop.MsidbRegistryRootClassesRoot: - registrySearch.Root = Wix.RegistrySearch.RootType.HKCR; - break; - case MsiInterop.MsidbRegistryRootCurrentUser: - registrySearch.Root = Wix.RegistrySearch.RootType.HKCU; - break; - case MsiInterop.MsidbRegistryRootLocalMachine: - registrySearch.Root = Wix.RegistrySearch.RootType.HKLM; - break; - case MsiInterop.MsidbRegistryRootUsers: - registrySearch.Root = Wix.RegistrySearch.RootType.HKU; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - registrySearch.Key = Convert.ToString(row[2]); - - if (null != row[3]) - { - registrySearch.Name = Convert.ToString(row[3]); - } - - if (null == row[4]) - { - registrySearch.Type = Wix.RegistrySearch.TypeType.file; - } - else - { - int type = Convert.ToInt32(row[4]); - - if (MsiInterop.MsidbLocatorType64bit == (type & MsiInterop.MsidbLocatorType64bit)) - { - registrySearch.Win64 = Wix.YesNoType.yes; - type &= ~MsiInterop.MsidbLocatorType64bit; - } - - switch (type) - { - case MsiInterop.MsidbLocatorTypeDirectory: - registrySearch.Type = Wix.RegistrySearch.TypeType.directory; - break; - case MsiInterop.MsidbLocatorTypeFileName: - registrySearch.Type = Wix.RegistrySearch.TypeType.file; - break; - case MsiInterop.MsidbLocatorTypeRawValue: - registrySearch.Type = Wix.RegistrySearch.TypeType.raw; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - } - - this.core.IndexElement(row, registrySearch); - } - } - - /// - /// Decompile the RemoveFile table. - /// - /// The table to decompile. - private void DecompileRemoveFileTable(Table table) - { - foreach (Row row in table.Rows) - { - if (null == row[2]) - { - Wix.RemoveFolder removeFolder = new Wix.RemoveFolder(); - - removeFolder.Id = Convert.ToString(row[0]); - - // directory/property is set in FinalizeDecompile - - switch (Convert.ToInt32(row[4])) - { - case MsiInterop.MsidbRemoveFileInstallModeOnInstall: - removeFolder.On = Wix.InstallUninstallType.install; - break; - case MsiInterop.MsidbRemoveFileInstallModeOnRemove: - removeFolder.On = Wix.InstallUninstallType.uninstall; - break; - case MsiInterop.MsidbRemoveFileInstallModeOnBoth: - removeFolder.On = Wix.InstallUninstallType.both; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(removeFolder); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, removeFolder); - } - else - { - Wix.RemoveFile removeFile = new Wix.RemoveFile(); - - removeFile.Id = Convert.ToString(row[0]); - - string[] names = Common.GetNames(Convert.ToString(row[2])); - if (null != names[0] && null != names[1]) - { - removeFile.ShortName = names[0]; - removeFile.Name = names[1]; - } - else if (null != names[0]) - { - removeFile.Name = names[0]; - } - - // directory/property is set in FinalizeDecompile - - switch (Convert.ToInt32(row[4])) - { - case MsiInterop.MsidbRemoveFileInstallModeOnInstall: - removeFile.On = Wix.InstallUninstallType.install; - break; - case MsiInterop.MsidbRemoveFileInstallModeOnRemove: - removeFile.On = Wix.InstallUninstallType.uninstall; - break; - case MsiInterop.MsidbRemoveFileInstallModeOnBoth: - removeFile.On = Wix.InstallUninstallType.both; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(removeFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, removeFile); - } - } - } - - /// - /// Decompile the RemoveIniFile table. - /// - /// The table to decompile. - private void DecompileRemoveIniFileTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.IniFile iniFile = new Wix.IniFile(); - - iniFile.Id = Convert.ToString(row[0]); - - string[] names = Common.GetNames(Convert.ToString(row[1])); - if (null != names[0] && null != names[1]) - { - iniFile.ShortName = names[0]; - iniFile.Name = names[1]; - } - else if (null != names[0]) - { - iniFile.Name = names[0]; - } - - if (null != row[2]) - { - iniFile.Directory = Convert.ToString(row[2]); - } - - iniFile.Section = Convert.ToString(row[3]); - - iniFile.Key = Convert.ToString(row[4]); - - if (null != row[5]) - { - iniFile.Value = Convert.ToString(row[5]); - } - - switch (Convert.ToInt32(row[6])) - { - case MsiInterop.MsidbIniFileActionRemoveLine: - iniFile.Action = Wix.IniFile.ActionType.removeLine; - break; - case MsiInterop.MsidbIniFileActionRemoveTag: - iniFile.Action = Wix.IniFile.ActionType.removeTag; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[7])); - if (null != component) - { - component.AddChild(iniFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[7]), "Component")); - } - } - } - - /// - /// Decompile the RemoveRegistry table. - /// - /// The table to decompile. - private void DecompileRemoveRegistryTable(Table table) - { - foreach (Row row in table.Rows) - { - if ("-" == Convert.ToString(row[3])) - { - Wix.RemoveRegistryKey removeRegistryKey = new Wix.RemoveRegistryKey(); - - removeRegistryKey.Id = Convert.ToString(row[0]); - - Wix.RegistryRootType registryRootType; - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType)) - { - removeRegistryKey.Root = registryRootType; - } - - removeRegistryKey.Key = Convert.ToString(row[2]); - - removeRegistryKey.Action = Wix.RemoveRegistryKey.ActionType.removeOnInstall; - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[4])); - if (null != component) - { - component.AddChild(removeRegistryKey); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[4]), "Component")); - } - } - else - { - Wix.RemoveRegistryValue removeRegistryValue = new Wix.RemoveRegistryValue(); - - removeRegistryValue.Id = Convert.ToString(row[0]); - - Wix.RegistryRootType registryRootType; - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType)) - { - removeRegistryValue.Root = registryRootType; - } - - removeRegistryValue.Key = Convert.ToString(row[2]); - - if (null != row[3]) - { - removeRegistryValue.Name = Convert.ToString(row[3]); - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[4])); - if (null != component) - { - component.AddChild(removeRegistryValue); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[4]), "Component")); - } - } - } - } - - /// - /// Decompile the ReserveCost table. - /// - /// The table to decompile. - private void DecompileReserveCostTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ReserveCost reserveCost = new Wix.ReserveCost(); - - reserveCost.Id = Convert.ToString(row[0]); - - if (null != row[2]) - { - reserveCost.Directory = Convert.ToString(row[2]); - } - - reserveCost.RunLocal = Convert.ToInt32(row[3]); - - reserveCost.RunFromSource = Convert.ToInt32(row[4]); - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(reserveCost); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - } - } - - /// - /// Decompile the SelfReg table. - /// - /// The table to decompile. - private void DecompileSelfRegTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.File file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); - - if (null != file) - { - if (null != row[1]) - { - file.SelfRegCost = Convert.ToInt32(row[1]); - } - else - { - file.SelfRegCost = 0; - } - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); - } - } - } - - /// - /// Decompile the ServiceControl table. - /// - /// The table to decompile. - private void DecompileServiceControlTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ServiceControl serviceControl = new Wix.ServiceControl(); - - serviceControl.Id = Convert.ToString(row[0]); - - serviceControl.Name = Convert.ToString(row[1]); - - int eventValue = Convert.ToInt32(row[2]); - if (MsiInterop.MsidbServiceControlEventStart == (eventValue & MsiInterop.MsidbServiceControlEventStart) && - MsiInterop.MsidbServiceControlEventUninstallStart == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStart)) - { - serviceControl.Start = Wix.InstallUninstallType.both; - } - else if (MsiInterop.MsidbServiceControlEventStart == (eventValue & MsiInterop.MsidbServiceControlEventStart)) - { - serviceControl.Start = Wix.InstallUninstallType.install; - } - else if (MsiInterop.MsidbServiceControlEventUninstallStart == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStart)) - { - serviceControl.Start = Wix.InstallUninstallType.uninstall; - } - - if (MsiInterop.MsidbServiceControlEventStop == (eventValue & MsiInterop.MsidbServiceControlEventStop) && - MsiInterop.MsidbServiceControlEventUninstallStop == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStop)) - { - serviceControl.Stop = Wix.InstallUninstallType.both; - } - else if (MsiInterop.MsidbServiceControlEventStop == (eventValue & MsiInterop.MsidbServiceControlEventStop)) - { - serviceControl.Stop = Wix.InstallUninstallType.install; - } - else if (MsiInterop.MsidbServiceControlEventUninstallStop == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStop)) - { - serviceControl.Stop = Wix.InstallUninstallType.uninstall; - } - - if (MsiInterop.MsidbServiceControlEventDelete == (eventValue & MsiInterop.MsidbServiceControlEventDelete) && - MsiInterop.MsidbServiceControlEventUninstallDelete == (eventValue & MsiInterop.MsidbServiceControlEventUninstallDelete)) - { - serviceControl.Remove = Wix.InstallUninstallType.both; - } - else if (MsiInterop.MsidbServiceControlEventDelete == (eventValue & MsiInterop.MsidbServiceControlEventDelete)) - { - serviceControl.Remove = Wix.InstallUninstallType.install; - } - else if (MsiInterop.MsidbServiceControlEventUninstallDelete == (eventValue & MsiInterop.MsidbServiceControlEventUninstallDelete)) - { - serviceControl.Remove = Wix.InstallUninstallType.uninstall; - } - - if (null != row[3]) - { - string[] arguments = NullSplitter.Split(Convert.ToString(row[3])); - - foreach (string argument in arguments) - { - Wix.ServiceArgument serviceArgument = new Wix.ServiceArgument(); - - serviceArgument.Content = argument; - - serviceControl.AddChild(serviceArgument); - } - } - - if (null != row[4]) - { - if (0 == Convert.ToInt32(row[4])) - { - serviceControl.Wait = Wix.YesNoType.no; - } - else - { - serviceControl.Wait = Wix.YesNoType.yes; - } - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[5])); - if (null != component) - { - component.AddChild(serviceControl); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[5]), "Component")); - } - } - } - - /// - /// Decompile the ServiceInstall table. - /// - /// The table to decompile. - private void DecompileServiceInstallTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.ServiceInstall serviceInstall = new Wix.ServiceInstall(); - - serviceInstall.Id = Convert.ToString(row[0]); - - serviceInstall.Name = Convert.ToString(row[1]); - - if (null != row[2]) - { - serviceInstall.DisplayName = Convert.ToString(row[2]); - } - - int serviceType = Convert.ToInt32(row[3]); - if (MsiInterop.MsidbServiceInstallInteractive == (serviceType & MsiInterop.MsidbServiceInstallInteractive)) - { - serviceInstall.Interactive = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbServiceInstallOwnProcess == (serviceType & MsiInterop.MsidbServiceInstallOwnProcess) && - MsiInterop.MsidbServiceInstallShareProcess == (serviceType & MsiInterop.MsidbServiceInstallShareProcess)) - { - // TODO: warn - } - else if (MsiInterop.MsidbServiceInstallOwnProcess == (serviceType & MsiInterop.MsidbServiceInstallOwnProcess)) - { - serviceInstall.Type = Wix.ServiceInstall.TypeType.ownProcess; - } - else if (MsiInterop.MsidbServiceInstallShareProcess == (serviceType & MsiInterop.MsidbServiceInstallShareProcess)) - { - serviceInstall.Type = Wix.ServiceInstall.TypeType.shareProcess; - } - - int startType = Convert.ToInt32(row[4]); - if (MsiInterop.MsidbServiceInstallDisabled == startType) - { - serviceInstall.Start = Wix.ServiceInstall.StartType.disabled; - } - else if (MsiInterop.MsidbServiceInstallDemandStart == startType) - { - serviceInstall.Start = Wix.ServiceInstall.StartType.demand; - } - else if (MsiInterop.MsidbServiceInstallAutoStart == startType) - { - serviceInstall.Start = Wix.ServiceInstall.StartType.auto; - } - else - { - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - } - - int errorControl = Convert.ToInt32(row[5]); - if (MsiInterop.MsidbServiceInstallErrorCritical == (errorControl & MsiInterop.MsidbServiceInstallErrorCritical)) - { - serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.critical; - } - else if (MsiInterop.MsidbServiceInstallErrorNormal == (errorControl & MsiInterop.MsidbServiceInstallErrorNormal)) - { - serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.normal; - } - else - { - serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.ignore; - } - - if (MsiInterop.MsidbServiceInstallErrorControlVital == (errorControl & MsiInterop.MsidbServiceInstallErrorControlVital)) - { - serviceInstall.Vital = Wix.YesNoType.yes; - } - - if (null != row[6]) - { - serviceInstall.LoadOrderGroup = Convert.ToString(row[6]); - } - - if (null != row[7]) - { - string[] dependencies = NullSplitter.Split(Convert.ToString(row[7])); - - foreach (string dependency in dependencies) - { - if (0 < dependency.Length) - { - Wix.ServiceDependency serviceDependency = new Wix.ServiceDependency(); - - if (dependency.StartsWith("+", StringComparison.Ordinal)) - { - serviceDependency.Group = Wix.YesNoType.yes; - serviceDependency.Id = dependency.Substring(1); - } - else - { - serviceDependency.Id = dependency; - } - - serviceInstall.AddChild(serviceDependency); - } - } - } - - if (null != row[8]) - { - serviceInstall.Account = Convert.ToString(row[8]); - } - - if (null != row[9]) - { - serviceInstall.Password = Convert.ToString(row[9]); - } - - if (null != row[10]) - { - serviceInstall.Arguments = Convert.ToString(row[10]); - } - - if (null != row[12]) - { - serviceInstall.Description = Convert.ToString(row[12]); - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[11])); - if (null != component) - { - component.AddChild(serviceInstall); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[11]), "Component")); - } - this.core.IndexElement(row, serviceInstall); - } - } - - /// - /// Decompile the SFPCatalog table. - /// - /// The table to decompile. - private void DecompileSFPCatalogTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.SFPCatalog sfpCatalog = new Wix.SFPCatalog(); - - sfpCatalog.Name = Convert.ToString(row[0]); - - sfpCatalog.SourceFile = Convert.ToString(row[1]); - - this.core.IndexElement(row, sfpCatalog); - } - - // nest the SFPCatalog elements - foreach (Row row in table.Rows) - { - Wix.SFPCatalog sfpCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement(row); - - if (null != row[2]) - { - Wix.SFPCatalog parentSFPCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement("SFPCatalog", Convert.ToString(row[2])); - - if (null != parentSFPCatalog) - { - parentSFPCatalog.AddChild(sfpCatalog); - } - else - { - sfpCatalog.Dependency = Convert.ToString(row[2]); - - this.core.RootElement.AddChild(sfpCatalog); - } - } - else - { - this.core.RootElement.AddChild(sfpCatalog); - } - } - } - - /// - /// Decompile the Shortcut table. - /// - /// The table to decompile. - private void DecompileShortcutTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Shortcut shortcut = new Wix.Shortcut(); - - shortcut.Id = Convert.ToString(row[0]); - - shortcut.Directory = Convert.ToString(row[1]); - - string[] names = Common.GetNames(Convert.ToString(row[2])); - if (null != names[0] && null != names[1]) - { - shortcut.ShortName = names[0]; - shortcut.Name = names[1]; - } - else if (null != names[0]) - { - shortcut.Name = names[0]; - } - - string target = Convert.ToString(row[4]); - if (target.StartsWith("[", StringComparison.Ordinal) && target.EndsWith("]", StringComparison.Ordinal)) - { - // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element - shortcut.Target = target; - } - else - { - shortcut.Advertise = Wix.YesNoType.yes; - - // primary feature is set in FinalizeFeatureComponentsTable - } - - if (null != row[5]) - { - shortcut.Arguments = Convert.ToString(row[5]); - } - - if (null != row[6]) - { - shortcut.Description = Convert.ToString(row[6]); - } - - if (null != row[7]) - { - shortcut.Hotkey = Convert.ToInt32(row[7]); - } - - if (null != row[8]) - { - shortcut.Icon = Convert.ToString(row[8]); - } - - if (null != row[9]) - { - shortcut.IconIndex = Convert.ToInt32(row[9]); - } - - if (null != row[10]) - { - switch (Convert.ToInt32(row[10])) - { - case 1: - shortcut.Show = Wix.Shortcut.ShowType.normal; - break; - case 3: - shortcut.Show = Wix.Shortcut.ShowType.maximized; - break; - case 7: - shortcut.Show = Wix.Shortcut.ShowType.minimized; - break; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10])); - break; - } - } - - if (null != row[11]) - { - shortcut.WorkingDirectory = Convert.ToString(row[11]); - } - - // Only try to read the MSI 4.0-specific columns if they actually exist - if (15 < row.Fields.Length) - { - if (null != row[12]) - { - shortcut.DisplayResourceDll = Convert.ToString(row[12]); - } - - if (null != row[13]) - { - shortcut.DisplayResourceId = Convert.ToInt32(row[13]); - } - - if (null != row[14]) - { - shortcut.DescriptionResourceDll = Convert.ToString(row[14]); - } - - if (null != row[15]) - { - shortcut.DescriptionResourceId = Convert.ToInt32(row[15]); - } - } - - Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[3])); - if (null != component) - { - component.AddChild(shortcut); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[3]), "Component")); - } - - this.core.IndexElement(row, shortcut); - } - } - - /// - /// Decompile the Signature table. - /// - /// The table to decompile. - private void DecompileSignatureTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.FileSearch fileSearch = new Wix.FileSearch(); - - fileSearch.Id = Convert.ToString(row[0]); - - string[] names = Common.GetNames(Convert.ToString(row[1])); - if (null != names[0]) - { - // it is permissable to just have a long name - if (!this.core.IsValidShortFilename(names[0], false) && null == names[1]) - { - fileSearch.Name = names[0]; - } - else - { - fileSearch.ShortName = names[0]; - } - } - - if (null != names[1]) - { - fileSearch.Name = names[1]; - } - - if (null != row[2]) - { - fileSearch.MinVersion = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - fileSearch.MaxVersion = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - fileSearch.MinSize = Convert.ToInt32(row[4]); - } - - if (null != row[5]) - { - fileSearch.MaxSize = Convert.ToInt32(row[5]); - } - - if (null != row[6]) - { - fileSearch.MinDate = this.core.ConvertIntegerToDateTime(Convert.ToInt32(row[6])); - } - - if (null != row[7]) - { - fileSearch.MaxDate = this.core.ConvertIntegerToDateTime(Convert.ToInt32(row[7])); - } - - if (null != row[8]) - { - fileSearch.Languages = Convert.ToString(row[8]); - } - - this.core.IndexElement(row, fileSearch); - } - } - - /// - /// Decompile the TargetFiles_OptionalData table. - /// - /// The table to decompile. - private void DecompileTargetFiles_OptionalDataTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.TargetFile targetFile = (Wix.TargetFile)this.patchTargetFiles[row[0]]; - if (null == targetFile) - { - targetFile = new Wix.TargetFile(); - - targetFile.Id = Convert.ToString(row[1]); - - Wix.TargetImage targetImage = (Wix.TargetImage)this.core.GetIndexedElement("TargetImages", Convert.ToString(row[0])); - if (null != targetImage) - { - targetImage.AddChild(targetFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", Convert.ToString(row[0]), "TargetImages")); - } - this.patchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), targetFile); - } - - if (null != row[2]) - { - string[] symbolPaths = (Convert.ToString(row[2])).Split(';'); - - foreach (string symbolPathString in symbolPaths) - { - Wix.SymbolPath symbolPath = new Wix.SymbolPath(); - - symbolPath.Path = symbolPathString; - - targetFile.AddChild(symbolPath); - } - } - - if (null != row[3] && null != row[4]) - { - string[] ignoreOffsets = (Convert.ToString(row[3])).Split(','); - string[] ignoreLengths = (Convert.ToString(row[4])).Split(','); - - if (ignoreOffsets.Length == ignoreLengths.Length) - { - for (int i = 0; i < ignoreOffsets.Length; i++) - { - Wix.IgnoreRange ignoreRange = new Wix.IgnoreRange(); - - if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) - { - ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i].Substring(2), 16); - } - else - { - ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture); - } - - if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) - { - ignoreRange.Length = Convert.ToInt32(ignoreLengths[i].Substring(2), 16); - } - else - { - ignoreRange.Length = Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture); - } - - targetFile.AddChild(ignoreRange); - } - } - else - { - // TODO: warn - } - } - else if (null != row[3] || null != row[4]) - { - // TODO: warn about mismatch between columns - } - - // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable - } - } - - /// - /// Decompile the TargetImages table. - /// - /// The table to decompile. - private void DecompileTargetImagesTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.TargetImage targetImage = new Wix.TargetImage(); - - targetImage.Id = Convert.ToString(row[0]); - - targetImage.SourceFile = Convert.ToString(row[1]); - - if (null != row[2]) - { - string[] symbolPaths = (Convert.ToString(row[3])).Split(';'); - - foreach (string symbolPathString in symbolPaths) - { - Wix.SymbolPath symbolPath = new Wix.SymbolPath(); - - symbolPath.Path = symbolPathString; - - targetImage.AddChild(symbolPath); - } - } - - targetImage.Order = Convert.ToInt32(row[4]); - - if (null != row[5]) - { - targetImage.Validation = Convert.ToString(row[5]); - } - - if (0 != Convert.ToInt32(row[6])) - { - targetImage.IgnoreMissingFiles = Wix.YesNoType.yes; - } - - Wix.UpgradeImage upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[3])); - if (null != upgradeImage) - { - upgradeImage.AddChild(targetImage); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[3]), "UpgradedImages")); - } - this.core.IndexElement(row, targetImage); - } - } - - /// - /// Decompile the TextStyle table. - /// - /// The table to decompile. - private void DecompileTextStyleTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.TextStyle textStyle = new Wix.TextStyle(); - - textStyle.Id = Convert.ToString(row[0]); - - textStyle.FaceName = Convert.ToString(row[1]); - - textStyle.Size = Convert.ToString(row[2]); - - if (null != row[3]) - { - int color = Convert.ToInt32(row[3]); - - textStyle.Red = color & 0xFF; - - textStyle.Green = (color & 0xFF00) >> 8; - - textStyle.Blue = (color & 0xFF0000) >> 16; - } - - if (null != row[4]) - { - int styleBits = Convert.ToInt32(row[4]); - - if (MsiInterop.MsidbTextStyleStyleBitsBold == (styleBits & MsiInterop.MsidbTextStyleStyleBitsBold)) - { - textStyle.Bold = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbTextStyleStyleBitsItalic == (styleBits & MsiInterop.MsidbTextStyleStyleBitsItalic)) - { - textStyle.Italic = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbTextStyleStyleBitsUnderline == (styleBits & MsiInterop.MsidbTextStyleStyleBitsUnderline)) - { - textStyle.Underline = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbTextStyleStyleBitsStrike == (styleBits & MsiInterop.MsidbTextStyleStyleBitsStrike)) - { - textStyle.Strike = Wix.YesNoType.yes; - } - } - - this.core.UIElement.AddChild(textStyle); - } - } - - /// - /// Decompile the TypeLib table. - /// - /// The table to decompile. - private void DecompileTypeLibTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.TypeLib typeLib = new Wix.TypeLib(); - - typeLib.Id = Convert.ToString(row[0]); - - typeLib.Advertise = Wix.YesNoType.yes; - - typeLib.Language = Convert.ToInt32(row[1]); - - if (null != row[3]) - { - int version = Convert.ToInt32(row[3]); - - if (65536 == version) - { - this.core.OnMessage(WixWarnings.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, typeLib.Id)); - } - - typeLib.MajorVersion = ((version & 0xFFFF00) >> 8); - typeLib.MinorVersion = (version & 0xFF); - } - - if (null != row[4]) - { - typeLib.Description = Convert.ToString(row[4]); - } - - if (null != row[5]) - { - typeLib.HelpDirectory = Convert.ToString(row[5]); - } - - if (null != row[7]) - { - typeLib.Cost = Convert.ToInt32(row[7]); - } - - // nested under the appropriate File element in FinalizeFileTable - this.core.IndexElement(row, typeLib); - } - } - - /// - /// Decompile the Upgrade table. - /// - /// The table to decompile. - private void DecompileUpgradeTable(Table table) - { - Hashtable upgradeElements = new Hashtable(); - - foreach (UpgradeRow upgradeRow in table.Rows) - { - if (Compiler.UpgradeDetectedProperty == upgradeRow.ActionProperty || Compiler.DowngradeDetectedProperty == upgradeRow.ActionProperty) - { - continue; // MajorUpgrade rows processed in FinalizeUpgradeTable - } - - Wix.Upgrade upgrade = (Wix.Upgrade)upgradeElements[upgradeRow.UpgradeCode]; - - // create the parent Upgrade element if it doesn't already exist - if (null == upgrade) - { - upgrade = new Wix.Upgrade(); - - upgrade.Id = upgradeRow.UpgradeCode; - - this.core.RootElement.AddChild(upgrade); - upgradeElements.Add(upgrade.Id, upgrade); - } - - Wix.UpgradeVersion upgradeVersion = new Wix.UpgradeVersion(); - - if (null != upgradeRow.VersionMin) - { - upgradeVersion.Minimum = upgradeRow.VersionMin; - } - - if (null != upgradeRow.VersionMax) - { - upgradeVersion.Maximum = upgradeRow.VersionMax; - } - - if (null != upgradeRow.Language) - { - upgradeVersion.Language = upgradeRow.Language; - } - - if (MsiInterop.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesMigrateFeatures)) - { - upgradeVersion.MigrateFeatures = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect)) - { - upgradeVersion.OnlyDetect = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure)) - { - upgradeVersion.IgnoreRemoveFailure = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive)) - { - upgradeVersion.IncludeMinimum = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive)) - { - upgradeVersion.IncludeMaximum = Wix.YesNoType.yes; - } - - if (MsiInterop.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive)) - { - upgradeVersion.ExcludeLanguages = Wix.YesNoType.yes; - } - - if (null != upgradeRow.Remove) - { - upgradeVersion.RemoveFeatures = upgradeRow.Remove; - } - - upgradeVersion.Property = upgradeRow.ActionProperty; - - upgrade.AddChild(upgradeVersion); - } - } - - /// - /// Decompile the UpgradedFiles_OptionalData table. - /// - /// The table to decompile. - private void DecompileUpgradedFiles_OptionalDataTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.UpgradeFile upgradeFile = new Wix.UpgradeFile(); - - upgradeFile.File = Convert.ToString(row[1]); - - if (null != row[2]) - { - string[] symbolPaths = (Convert.ToString(row[2])).Split(';'); - - foreach (string symbolPathString in symbolPaths) - { - Wix.SymbolPath symbolPath = new Wix.SymbolPath(); - - symbolPath.Path = symbolPathString; - - upgradeFile.AddChild(symbolPath); - } - } - - if (null != row[3] && 1 == Convert.ToInt32(row[3])) - { - upgradeFile.AllowIgnoreOnError = Wix.YesNoType.yes; - } - - if (null != row[4] && 0 != Convert.ToInt32(row[4])) - { - upgradeFile.WholeFile = Wix.YesNoType.yes; - } - - upgradeFile.Ignore = Wix.YesNoType.no; - - Wix.UpgradeImage upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[0])); - if (null != upgradeImage) - { - upgradeImage.AddChild(upgradeFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[0]), "UpgradedImages")); - } - } - } - - /// - /// Decompile the UpgradedFilesToIgnore table. - /// - /// The table to decompile. - private void DecompileUpgradedFilesToIgnoreTable(Table table) - { - foreach (Row row in table.Rows) - { - if ("*" != Convert.ToString(row[0])) - { - Wix.UpgradeFile upgradeFile = new Wix.UpgradeFile(); - - upgradeFile.File = Convert.ToString(row[1]); - - upgradeFile.Ignore = Wix.YesNoType.yes; - - Wix.UpgradeImage upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[0])); - if (null != upgradeImage) - { - upgradeImage.AddChild(upgradeFile); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[0]), "UpgradedImages")); - } - } - else - { - this.core.OnMessage(WixWarnings.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0])); - } - } - } - - /// - /// Decompile the UpgradedImages table. - /// - /// The table to decompile. - private void DecompileUpgradedImagesTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.UpgradeImage upgradeImage = new Wix.UpgradeImage(); - - upgradeImage.Id = Convert.ToString(row[0]); - - upgradeImage.SourceFile = Convert.ToString(row[1]); - - if (null != row[2]) - { - upgradeImage.SourcePatch = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - string[] symbolPaths = (Convert.ToString(row[3])).Split(';'); - - foreach (string symbolPathString in symbolPaths) - { - Wix.SymbolPath symbolPath = new Wix.SymbolPath(); - - symbolPath.Path = symbolPathString; - - upgradeImage.AddChild(symbolPath); - } - } - - Wix.Family family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[4])); - if (null != family) - { - family.AddChild(upgradeImage); - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[4]), "ImageFamilies")); - } - this.core.IndexElement(row, upgradeImage); - } - } - - /// - /// Decompile the UIText table. - /// - /// The table to decompile. - private void DecompileUITextTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.UIText uiText = new Wix.UIText(); - - uiText.Id = Convert.ToString(row[0]); - - uiText.Content = Convert.ToString(row[1]); - - this.core.UIElement.AddChild(uiText); - } - } - - /// - /// Decompile the Verb table. - /// - /// The table to decompile. - private void DecompileVerbTable(Table table) - { - foreach (Row row in table.Rows) - { - Wix.Verb verb = new Wix.Verb(); - - verb.Id = Convert.ToString(row[1]); - - if (null != row[2]) - { - verb.Sequence = Convert.ToInt32(row[2]); - } - - if (null != row[3]) - { - verb.Command = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - verb.Argument = Convert.ToString(row[4]); - } - - this.core.IndexElement(row, verb); - } - } - - /// - /// Gets the RegistryRootType from an integer representation of the root. - /// - /// The source line information for the root. - /// The name of the table containing the field. - /// The field containing the root value. - /// The strongly-typed representation of the root. - /// true if the value could be converted; false otherwise. - private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out Wix.RegistryRootType registryRootType) - { - switch (Convert.ToInt32(field.Data)) - { - case (-1): - registryRootType = Wix.RegistryRootType.HKMU; - return true; - case MsiInterop.MsidbRegistryRootClassesRoot: - registryRootType = Wix.RegistryRootType.HKCR; - return true; - case MsiInterop.MsidbRegistryRootCurrentUser: - registryRootType = Wix.RegistryRootType.HKCU; - return true; - case MsiInterop.MsidbRegistryRootLocalMachine: - registryRootType = Wix.RegistryRootType.HKLM; - return true; - case MsiInterop.MsidbRegistryRootUsers: - registryRootType = Wix.RegistryRootType.HKU; - return true; - default: - this.core.OnMessage(WixWarnings.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data)); - registryRootType = Wix.RegistryRootType.HKCR; // assign anything to satisfy the out parameter - return false; - } - } - - /// - /// Set the primary feature for a component. - /// - /// The row which specifies a primary feature. - /// The index of the column contaning the feature identifier. - /// The index of the column containing the component identifier. - private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex) - { - // only products contain primary features - if (OutputType.Product == this.outputType) - { - Field featureField = row.Fields[featureColumnIndex]; - Field componentField = row.Fields[componentColumnIndex]; - - Wix.ComponentRef componentRef = (Wix.ComponentRef)this.core.GetIndexedElement("FeatureComponents", Convert.ToString(featureField.Data), Convert.ToString(componentField.Data)); - - if (null != componentRef) - { - componentRef.Primary = Wix.YesNoType.yes; - } - else - { - this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), featureField.Column.Name, Convert.ToString(featureField.Data), componentField.Column.Name, Convert.ToString(componentField.Data), "FeatureComponents")); - } - } - } - - /// - /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it. - /// - /// The collection of all tables. - private static Wix.MajorUpgrade.ScheduleType DetermineMajorUpgradeScheduling(TableIndexedCollection tables) - { - int sequenceRemoveExistingProducts = 0; - int sequenceInstallValidate = 0; - int sequenceInstallInitialize = 0; - int sequenceInstallFinalize = 0; - int sequenceInstallExecute = 0; - int sequenceInstallExecuteAgain = 0; - - Table installExecuteSequenceTable = tables["InstallExecuteSequence"]; - if (null != installExecuteSequenceTable) - { - int removeExistingProductsRow = -1; - for (int i = 0; i < installExecuteSequenceTable.Rows.Count; i++) - { - Row row = installExecuteSequenceTable.Rows[i]; - string action = Convert.ToString(row[0]); - int sequence = Convert.ToInt32(row[2]); - - switch (action) - { - case "RemoveExistingProducts": - sequenceRemoveExistingProducts = sequence; - removeExistingProductsRow = i; - break; - case "InstallValidate": - sequenceInstallValidate = sequence; - break; - case "InstallInitialize": - sequenceInstallInitialize = sequence; - break; - case "InstallExecute": - sequenceInstallExecute = sequence; - break; - case "InstallExecuteAgain": - sequenceInstallExecuteAgain = sequence; - break; - case "InstallFinalize": - sequenceInstallFinalize = sequence; - break; - } - } - - installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow); - } - - if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize) - { - return Wix.MajorUpgrade.ScheduleType.afterInstallValidate; - } - else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute) - { - return Wix.MajorUpgrade.ScheduleType.afterInstallInitialize; - } - else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain) - { - return Wix.MajorUpgrade.ScheduleType.afterInstallExecute; - } - else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize) - { - return Wix.MajorUpgrade.ScheduleType.afterInstallExecuteAgain; - } - else - { - return Wix.MajorUpgrade.ScheduleType.afterInstallFinalize; - } - } -#endif - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/DecompilerCore.cs b/src/WixToolset.Core.WindowsInstaller/DecompilerCore.cs deleted file mode 100644 index 2be986fc..00000000 --- a/src/WixToolset.Core.WindowsInstaller/DecompilerCore.cs +++ /dev/null @@ -1,154 +0,0 @@ -// 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 -{ - using System; - using System.Collections; - using WixToolset.Data; - using WixToolset.Extensibility; - using Wix = WixToolset.Data.Serialize; - -#if TODO - /// - /// The base of the decompiler. Holds some variables used by the decompiler and extensions, - /// as well as some utility methods. - /// - internal class DecompilerCore : IDecompilerCore - { - private Hashtable elements; - private Wix.IParentElement rootElement; - private bool showPedanticMessages; - private Wix.UI uiElement; - - /// - /// Instantiate a new decompiler core. - /// - /// The root element of the decompiled database. - /// The message handler. - internal DecompilerCore(Wix.IParentElement rootElement) - { - this.elements = new Hashtable(); - this.rootElement = rootElement; - } - - /// - /// Gets whether the decompiler core encountered an error while processing. - /// - /// Flag if core encountered an error during processing. - public bool EncounteredError - { - get { return Messaging.Instance.EncounteredError; } - } - - /// - /// Gets the root element of the decompiled output. - /// - /// The root element of the decompiled output. - public Wix.IParentElement RootElement - { - get { return this.rootElement; } - } - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - public bool ShowPedanticMessages - { - get { return this.showPedanticMessages; } - set { this.showPedanticMessages = value; } - } - - /// - /// Gets the UI element. - /// - /// The UI element. - public Wix.UI UIElement - { - get - { - if (null == this.uiElement) - { - this.uiElement = new Wix.UI(); - this.rootElement.AddChild(this.uiElement); - } - - return this.uiElement; - } - } - - /// - /// Verifies if a filename is a valid short filename. - /// - /// Filename to verify. - /// true if wildcards are allowed in the filename. - /// True if the filename is a valid short filename - public virtual bool IsValidShortFilename(string filename, bool allowWildcards) - { - return false; - } - - /// - /// Convert an Int32 into a DateTime. - /// - /// The Int32 value. - /// The DateTime. - public DateTime ConvertIntegerToDateTime(int value) - { - int date = value / 65536; - int time = value % 65536; - - return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2); - } - - /// - /// Gets the element corresponding to the row it came from. - /// - /// The row corresponding to the element. - /// The indexed element. - public Wix.ISchemaElement GetIndexedElement(Row row) - { - return this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); - } - - /// - /// Gets the element corresponding to the primary key of the given table. - /// - /// The table corresponding to the element. - /// The primary key corresponding to the element. - /// The indexed element. - public Wix.ISchemaElement GetIndexedElement(string table, params string[] primaryKey) - { - return (Wix.ISchemaElement)this.elements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))]; - } - - /// - /// Index an element by its corresponding row. - /// - /// The row corresponding to the element. - /// The element to index. - public void IndexElement(Row row, Wix.ISchemaElement element) - { - this.elements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element); - } - - /// - /// Indicates the decompiler encountered and unexpected table to decompile. - /// - /// Unknown decompiled table. - public void UnexpectedTable(Table table) - { - this.OnMessage(WixErrors.TableDecompilationUnimplemented(table.Name)); - } - - /// - /// Sends a message to the message delegate if there is one. - /// - /// Message event arguments. - public void OnMessage(MessageEventArgs e) - { - Messaging.Instance.OnMessage(e); - } - } -#endif -} diff --git a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs index 579977fe..b633ea31 100644 --- a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs @@ -1,4 +1,4 @@ -// 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. +// 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 { @@ -38,9 +38,26 @@ namespace WixToolset.Core.WindowsInstaller return result; } - public BindResult Decompile(IDecompileContext context) + public DecompileResult Decompile(IDecompileContext context) { - throw new NotImplementedException(); + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.Create(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendDecompile(context); + } + + var command = new DecompileMsiOrMsmCommand(context, backendExtensions); + var result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendDecompile(result); + } + + return result; } public bool Inscribe(IInscribeContext context) diff --git a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs index de9c4162..84588572 100644 --- a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs @@ -1,4 +1,4 @@ -// 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. +// 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 { @@ -43,9 +43,26 @@ namespace WixToolset.Core.WindowsInstaller return result; } - public BindResult Decompile(IDecompileContext context) + public DecompileResult Decompile(IDecompileContext context) { - throw new NotImplementedException(); + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.Create(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendDecompile(context); + } + + var command = new DecompileMsiOrMsmCommand(context, backendExtensions); + var result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendDecompile(result); + } + + return result; } public bool Inscribe(IInscribeContext context) diff --git a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs index c6a05b20..df4eb44c 100644 --- a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs @@ -1,4 +1,4 @@ -// 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. +// 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 { @@ -21,7 +21,7 @@ namespace WixToolset.Core.WindowsInstaller throw new NotImplementedException(); } - public BindResult Decompile(IDecompileContext context) + public DecompileResult Decompile(IDecompileContext context) { throw new NotImplementedException(); } diff --git a/src/WixToolset.Core.WindowsInstaller/MstBackend.cs b/src/WixToolset.Core.WindowsInstaller/MstBackend.cs index 3e105963..6460821a 100644 --- a/src/WixToolset.Core.WindowsInstaller/MstBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MstBackend.cs @@ -1,4 +1,4 @@ -// 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. +// 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 { @@ -25,7 +25,7 @@ namespace WixToolset.Core.WindowsInstaller throw new NotImplementedException(); } - public BindResult Decompile(IDecompileContext context) + public DecompileResult Decompile(IDecompileContext context) { throw new NotImplementedException(); } diff --git a/src/WixToolset.Core/CommandLine/DecompileCommand.cs b/src/WixToolset.Core/CommandLine/DecompileCommand.cs new file mode 100644 index 00000000..87cead80 --- /dev/null +++ b/src/WixToolset.Core/CommandLine/DecompileCommand.cs @@ -0,0 +1,212 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class DecompileCommand : ICommandLineCommand + { + private readonly CommandLine commandLine; + + public DecompileCommand(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + this.commandLine = new CommandLine(this.Messaging); + } + + public bool ShowLogo => this.commandLine.ShowLogo; + + public bool StopParsing => this.commandLine.ShowHelp; + + private IServiceProvider ServiceProvider { get; } + + public IMessaging Messaging { get; } + + private IEnumerable SourceFiles { get; } + + private string OutputPath { get; } + + public int Execute() + { + if (this.commandLine.ShowHelp) + { + Console.WriteLine("TODO: Show decompile command help"); + return -1; + } + + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ServiceProvider.GetService().Create(); + context.DecompilePath = this.commandLine.DecompileFilePath; + context.DecompileType = this.commandLine.CalculateDecompileType(); + context.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); + context.OutputPath = this.commandLine.CalculateOutputPath(); + + try + { + var decompiler = this.ServiceProvider.GetService(); + var result = decompiler.Decompile(context); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + if (this.Messaging.EncounteredError) + { + return 1; + } + + return 0; + } + + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + return this.commandLine.TryParseArgument(argument, parser); + } + + private class CommandLine + { + public CommandLine(IMessaging messaging) + { + this.Messaging = messaging; + } + + private IMessaging Messaging { get; } + + public string DecompileFilePath { get; private set; } + + public string DecompileType { get; private set; } + + public Platform Platform { get; private set; } + + public bool ShowLogo { get; private set; } + + public bool ShowHelp { get; private set; } + + public string IntermediateFolder { get; private set; } + + public string OutputFile { get; private set; } + + public bool TryParseArgument(string arg, ICommandLineParser parser) + { + if (parser.IsSwitch(arg)) + { + var parameter = arg.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "?": + case "h": + case "help": + this.ShowHelp = true; + return true; + + case "intermediatefolder": + this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); + return true; + + case "o": + case "out": + this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "nologo": + this.ShowLogo = false; + return true; + + case "v": + case "verbose": + this.Messaging.ShowVerboseMessages = true; + return true; + + case "sw": + case "suppresswarning": + var warning = parser.GetNextArgumentOrError(arg); + if (!String.IsNullOrEmpty(warning)) + { + var warningNumber = Convert.ToInt32(warning); + this.Messaging.SuppressWarningMessage(warningNumber); + } + return true; + } + } + else + { + if (String.IsNullOrEmpty(this.DecompileFilePath)) + { + this.DecompileFilePath = parser.GetArgumentAsFilePathOrError(arg, "decompile file"); + return true; + } + else if (String.IsNullOrEmpty(this.OutputFile)) + { + this.OutputFile = parser.GetArgumentAsFilePathOrError(arg, "output file"); + return true; + } + } + + return false; + } + + public OutputType CalculateDecompileType() + { + if (String.IsNullOrEmpty(this.DecompileType)) + { + this.DecompileType = Path.GetExtension(this.DecompileFilePath); + } + + switch (this.DecompileType.ToLowerInvariant()) + { + case "bundle": + case ".exe": + return OutputType.Bundle; + + case "library": + case ".wixlib": + return OutputType.Library; + + case "module": + case ".msm": + return OutputType.Module; + + case "patch": + case ".msp": + return OutputType.Patch; + + case ".pcp": + return OutputType.PatchCreation; + + case "product": + case "package": + case ".msi": + return OutputType.Product; + + case "transform": + case ".mst": + return OutputType.Transform; + + case "intermediatepostlink": + case ".wixipl": + return OutputType.IntermediatePostLink; + } + + return OutputType.Unknown; + } + + public string CalculateIntermedateFolder() + { + return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; + } + + public string CalculateOutputPath() + { + return String.IsNullOrEmpty(this.OutputFile) ? Path.ChangeExtension(this.DecompileFilePath, ".wxs") : this.OutputFile; + } + } + } +} diff --git a/src/WixToolset.Core/DecompileContext.cs b/src/WixToolset.Core/DecompileContext.cs index a9f0640a..b697c3cf 100644 --- a/src/WixToolset.Core/DecompileContext.cs +++ b/src/WixToolset.Core/DecompileContext.cs @@ -1,4 +1,4 @@ -// 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. +// 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 { @@ -17,12 +17,30 @@ namespace WixToolset.Core public IServiceProvider ServiceProvider { get; } + public string DecompilePath { get; set; } + public OutputType DecompileType { get; set; } public IEnumerable Extensions { get; set; } + public string ExtractFolder { get; set; } + + public string BaseSourcePath { get; set; } + public string IntermediateFolder { get; set; } + public bool IsAdminImage { get; set; } + public string OutputPath { get; set; } + + public bool SuppressCustomTables { get; set; } + + public bool SuppressDroppingEmptyTables { get; set; } + + public bool SuppressExtractCabinets { get; set; } + + public bool SuppressUI { get; set; } + + public bool TreatProductAsModule { get; set; } } } diff --git a/src/WixToolset.Core/Decompiler.cs b/src/WixToolset.Core/Decompiler.cs index 45cfbea0..685722a8 100644 --- a/src/WixToolset.Core/Decompiler.cs +++ b/src/WixToolset.Core/Decompiler.cs @@ -19,7 +19,7 @@ namespace WixToolset.Core public IServiceProvider ServiceProvider { get; } - public BindResult Decompile(IDecompileContext context) + public DecompileResult Decompile(IDecompileContext context) { // Pre-decompile. // @@ -30,22 +30,22 @@ namespace WixToolset.Core // Decompile. // - var bindResult = this.BackendDecompile(context); + var result = this.BackendDecompile(context); - if (bindResult != null) + if (result != null) { // Post-decompile. // foreach (var extension in context.Extensions) { - extension.PostDecompile(bindResult); + extension.PostDecompile(result); } } - return bindResult; + return result; } - private BindResult BackendDecompile(IDecompileContext context) + private DecompileResult BackendDecompile(IDecompileContext context) { var extensionManager = context.ServiceProvider.GetService(); diff --git a/src/WixToolset.Core/IDecompiler.cs b/src/WixToolset.Core/IDecompiler.cs index b9bb7ed8..82b02943 100644 --- a/src/WixToolset.Core/IDecompiler.cs +++ b/src/WixToolset.Core/IDecompiler.cs @@ -1,4 +1,4 @@ -// 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. +// 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 { @@ -6,6 +6,6 @@ namespace WixToolset.Core public interface IDecompiler { - BindResult Decompile(IDecompileContext context); + DecompileResult Decompile(IDecompileContext context); } } diff --git a/src/WixToolset.Core/OptimizeCA.cs b/src/WixToolset.Core/OptimizeCA.cs index ba17604d..0d7b5e1a 100644 --- a/src/WixToolset.Core/OptimizeCA.cs +++ b/src/WixToolset.Core/OptimizeCA.cs @@ -1,4 +1,4 @@ -// 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. +// 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 { @@ -8,7 +8,7 @@ namespace WixToolset.Core /// Values for the OptimizeCA MsiPatchMetdata property, which indicates whether custom actions can be skipped when applying the patch. ///
[Flags] - internal enum OptimizeCA + public enum OptimizeCA // TODO: review where to place this data so it can not be exposed by WixToolset.Core { /// /// No custom actions are skipped. diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs new file mode 100644 index 00000000..66ce98c0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -0,0 +1,41 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Xml.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class DecompileFixture + { + [Fact] + public void CanDecompileSingleFileCompressed() + { + var folder = TestData.Get(@"TestData\DecompileSingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); + + var result = WixRunner.Execute(new[] + { + "decompile", + Path.Combine(folder, "example.msi"), + "-intermediateFolder", intermediateFolder, + "-o", outputPath + }); + + result.AssertSuccess(); + + var actual = File.ReadAllText(outputPath); + var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + var expected = XDocument.Load(Path.Combine(folder, "Expected.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + + Assert.Equal(expected, actualFormatted); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs new file mode 100644 index 00000000..b2bb6050 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab new file mode 100644 index 00000000..125eeb2c Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi new file mode 100644 index 00000000..9cb6d6bc Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi differ diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 38b7dc81..7f1337e0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -20,6 +20,9 @@ + + + -- cgit v1.2.3-55-g6feb From 135dad52ea93d65e9cfe1490f34d12bec510c836 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Wed, 14 Nov 2018 20:23:35 -0500 Subject: Explicitly mark bitness of entities that support 64-bit attribute. This ensures that you can build a 64-bit package with 32-bit entities and not lose the "forced" 32-bit-ness in the decompiled output. --- .../Decompile/Decompiler.cs | 13 ++++++++++ .../DecompileFixture.cs | 28 +++++++++++++++++++++ .../DecompileSingleFileCompressed/Expected.wxs | 2 +- .../DecompileSingleFileCompressed64/Expected.wxs | 21 ++++++++++++++++ .../DecompileSingleFileCompressed64/example.cab | Bin 0 -> 137 bytes .../DecompileSingleFileCompressed64/example.msi | Bin 0 -> 32768 bytes .../WixToolsetTest.CoreIntegration.csproj | 3 +++ 7 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index 26e1f399..3b193a4a 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -4663,6 +4663,11 @@ namespace WixToolset.Core.WindowsInstaller { customAction.Win64 = Wix.YesNoType.yes; } + else if (MsiInterop.MsidbCustomActionTypeVBScript == (type & MsiInterop.MsidbCustomActionTypeVBScript) || + MsiInterop.MsidbCustomActionTypeJScript == (type & MsiInterop.MsidbCustomActionTypeJScript)) + { + customAction.Win64 = Wix.YesNoType.no; + } switch (type & MsiInterop.MsidbCustomActionTypeExecuteBits) { @@ -4903,6 +4908,10 @@ namespace WixToolset.Core.WindowsInstaller { component.Win64 = Wix.YesNoType.yes; } + else + { + component.Win64 = Wix.YesNoType.no; + } if (MsiInterop.MsidbComponentAttributesDisableRegistryReflection == (attributes & MsiInterop.MsidbComponentAttributesDisableRegistryReflection)) { @@ -7810,6 +7819,10 @@ namespace WixToolset.Core.WindowsInstaller registrySearch.Win64 = Wix.YesNoType.yes; type &= ~MsiInterop.MsidbLocatorType64bit; } + else + { + registrySearch.Win64 = Wix.YesNoType.no; + } switch (type) { diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs index 66ce98c0..3a9781df 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -37,5 +37,33 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(expected, actualFormatted); } } + + [Fact] + public void CanDecompile64BitSingleFileCompressed() + { + var folder = TestData.Get(@"TestData\DecompileSingleFileCompressed64"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); + + var result = WixRunner.Execute(new[] + { + "decompile", + Path.Combine(folder, "example.msi"), + "-intermediateFolder", intermediateFolder, + "-o", outputPath + }); + + result.AssertSuccess(); + + var actual = File.ReadAllText(outputPath); + var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + var expected = XDocument.Load(Path.Combine(folder, "Expected.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + + Assert.Equal(expected, actualFormatted); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs index b2bb6050..fd6f81ca 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs @@ -5,7 +5,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs new file mode 100644 index 00000000..b7f5ad07 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab new file mode 100644 index 00000000..125eeb2c Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi new file mode 100644 index 00000000..762b136c Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi differ diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 7f1337e0..9cea0f4c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -23,6 +23,9 @@ + + + -- cgit v1.2.3-55-g6feb From 41622a07f82c57cf3d80393b0b6914f5ccb76e33 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 1 Jan 2019 00:26:30 -0800 Subject: Load .wixlib intermediates with single creator Using a single creator ensures definitions are shared and consistent across the entire build. --- src/WixToolset.Core/CommandLine/BuildCommand.cs | 31 ++-- .../TupleDefinitionCreator.cs | 7 +- .../ComplexExampleExtension/OtherComponents.wxs | 12 ++ .../ComplexExampleExtension/Package.en-us.wxl | 11 ++ .../TestData/ComplexExampleExtension/Package.wxs | 26 ++++ .../ComplexExampleExtension/PackageComponents.wxs | 12 ++ .../ComplexExampleExtension/data/example.txt | 1 + .../ComplexExampleExtension/data/other.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 6 + .../WixlibFixture.cs | 173 +++++++++++++++++++++ 10 files changed, 259 insertions(+), 21 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index 87a3cd30..f754c876 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -331,27 +331,20 @@ namespace WixToolset.Core.CommandLine private IEnumerable LoadLibraries(IEnumerable libraryFiles, ITupleDefinitionCreator creator) { - var libraries = new List(); - - foreach (var libraryFile in libraryFiles) + try { - try - { - var library = Intermediate.Load(libraryFile, creator); - - libraries.Add(library); - } - catch (WixCorruptFileException e) - { - this.Messaging.Write(e.Error); - } - catch (WixUnexpectedFileFormatException e) - { - this.Messaging.Write(e.Error); - } + return Intermediate.Load(libraryFiles, creator); + } + catch (WixCorruptFileException e) + { + this.Messaging.Write(e.Error); + } + catch (WixUnexpectedFileFormatException e) + { + this.Messaging.Write(e.Error); } - return libraries; + return Array.Empty(); } private IEnumerable LoadLocalizationFiles(IEnumerable locFiles, IDictionary preprocessorVariables) @@ -437,7 +430,7 @@ namespace WixToolset.Core.CommandLine public string OutputsFile { get; private set; } public string BuiltOutputsFile { get; private set; } - + public CommandLine(IMessaging messaging) { this.Messaging = messaging; diff --git a/src/WixToolset.Core/ExtensibilityServices/TupleDefinitionCreator.cs b/src/WixToolset.Core/ExtensibilityServices/TupleDefinitionCreator.cs index b442da2b..e223e3a4 100644 --- a/src/WixToolset.Core/ExtensibilityServices/TupleDefinitionCreator.cs +++ b/src/WixToolset.Core/ExtensibilityServices/TupleDefinitionCreator.cs @@ -1,4 +1,4 @@ -// 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. +// 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.ExtensibilityServices { @@ -23,7 +23,10 @@ namespace WixToolset.Core.ExtensibilityServices public void AddCustomTupleDefinition(IntermediateTupleDefinition definition) { - this.CustomDefinitionByName.Add(definition.Name, definition); + if (!this.CustomDefinitionByName.TryGetValue(definition.Name, out var existing) || definition.Revision > existing.Revision) + { + this.CustomDefinitionByName[definition.Name] = definition; + } } public bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition) diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs new file mode 100644 index 00000000..15a9a0ce --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs new file mode 100644 index 00000000..0e8e9795 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs new file mode 100644 index 00000000..7f17b538 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt new file mode 100644 index 00000000..8c874ae7 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt @@ -0,0 +1 @@ +This is other.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 2a20fcff..53c5ceea 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -38,6 +38,12 @@ + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs new file mode 100644 index 00000000..532f158d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using Xunit; + + public class WixlibFixture + { + [Fact] + public void CanBuildSingleFileUsingWixlib() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"obj\test.wir")); + var section = intermediate.Sections.Single(); + + var wixFile = section.Tuples.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CanBuildWithExtensionUsingWixlib() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-ext", extensionPath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + var wixFile = section.Tuples.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + + var example = section.Tuples.Where(t => t.Definition.Type == TupleDefinitionType.MustBeFromAnExtension).Single(); + Assert.Equal("Foo", example.Id.Id); + Assert.Equal("Foo", example[0].AsString()); + Assert.Equal("Bar", example[1].AsString()); + } + } + + [Fact] + public void CanBuildWithExtensionUsingMultipleWixlibs() + { + var folder = TestData.Get(@"TestData\ComplexExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-ext", extensionPath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"components.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "OtherComponents.wxs"), + "-ext", extensionPath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"other.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"components.wixlib"), + "-lib", Path.Combine(intermediateFolder, @"other.wixlib"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + var wixFiles = section.Tuples.OfType().OrderBy(t => Path.GetFileName(t.Source.Path)).ToArray(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), wixFiles[0][WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", wixFiles[0][WixFileTupleFields.Source].PreviousValue.AsPath().Path); + Assert.Equal(Path.Combine(folder, @"data\other.txt"), wixFiles[1][WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"other.txt", wixFiles[1][WixFileTupleFields.Source].PreviousValue.AsPath().Path); + + var examples = section.Tuples.Where(t => t.Definition.Type == TupleDefinitionType.MustBeFromAnExtension).ToArray(); + Assert.Equal(new[] { "Foo", "Other" }, examples.Select(t => t.Id.Id).ToArray()); + Assert.Equal(new[] { "Foo", "Other" }, examples.Select(t => t.AsString(0)).ToArray()); + Assert.Equal(new[] { "Bar", "Value" }, examples.Select(t => t[1].AsString()).ToArray()); + } + } + } +} -- cgit v1.2.3-55-g6feb From a8e31958d7e1b0ef10ea8035abf1e3bf07170eb8 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 12 Jan 2019 16:33:59 -0600 Subject: Add failing test for issues exposed by UIExtension. --- .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 41 ++++++++++++++++++++++ .../DialogsInInstallUISequence/Package.en-us.wxl | 13 +++++++ .../DialogsInInstallUISequence/Package.wxs | 21 +++++++++++ .../PackageComponents.wxs | 38 ++++++++++++++++++++ .../DialogsInInstallUISequence/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 +++ 6 files changed, 118 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 76f57676..b5e1ec3d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -206,6 +206,47 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Currently fails")] + public void CanBuildDialogsInInstallUISequence() + { + var folder = TestData.Get(@"TestData\DialogsInInstallUISequence"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + var textStyle = section.Tuples.OfType().Single(); + Assert.Equal("Tahoma", textStyle.FaceName); + Assert.Equal(8, textStyle.Size); + + var installUIActions = section.Tuples.OfType() + .Where(t => t.SequenceTable == SequenceTable.InstallUISequence) + .ToList(); + Assert.Equal(10, installUIActions.Count); + } + } + [Fact] public void CanLoadPdbGeneratedByBuild() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.en-us.wxl new file mode 100644 index 00000000..77d46861 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.en-us.wxl @@ -0,0 +1,13 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + Tahoma + 8 + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.wxs new file mode 100644 index 00000000..6da3dcbe --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs new file mode 100644 index 00000000..724c46ed --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + NOT Installed + + + + + + + + + Installed AND PATCH + + + Installed AND PATCH + NOT Installed + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 53c5ceea..7365f140 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -13,6 +13,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 214f53de1c6500aa8dd46e9604c90178807fda1a Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Wed, 16 Jan 2019 16:25:46 -0500 Subject: Fix overridable actions being tagged as duplicates. --- src/WixToolset.Core.TestPackage/WixRunnerResult.cs | 24 ++++++++-- .../Bind/SequenceActionsCommand.cs | 2 +- .../Link/ResolveReferencesCommand.cs | 20 +++++--- .../LinkerFixture.cs | 54 ++++++++++++++++++++++ .../TestData/OverridableActions/Package.en-us.wxl | 11 +++++ .../TestData/OverridableActions/Package.wxs | 49 ++++++++++++++++++++ .../OverridableActions/PackageComponents.wxs | 10 ++++ .../TestData/OverridableActions/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 ++ 9 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.TestPackage/WixRunnerResult.cs b/src/WixToolset.Core.TestPackage/WixRunnerResult.cs index 8fc7e14f..f4870ae3 100644 --- a/src/WixToolset.Core.TestPackage/WixRunnerResult.cs +++ b/src/WixToolset.Core.TestPackage/WixRunnerResult.cs @@ -1,9 +1,9 @@ -// 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. +// 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.TestPackage { using System; - using System.Linq; + using System.Collections.Generic; using WixToolset.Data; using Xunit; @@ -15,8 +15,26 @@ namespace WixToolset.Core.TestPackage public WixRunnerResult AssertSuccess() { - Assert.True(0 == this.ExitCode, $"\r\n\r\nWixRunner failed with exit code: {this.ExitCode}\r\n Output: {String.Join("\r\n ", this.Messages.Select(m => m.ToString()).ToArray())}\r\n"); + Assert.True(0 == this.ExitCode, $"\r\n\r\nWixRunner failed with exit code: {this.ExitCode}\r\n Output: {String.Join("\r\n ", FormatMessages(this.Messages))}\r\n"); return this; } + + private static IEnumerable FormatMessages(IEnumerable messages) + { + foreach (var message in messages) + { + var filename = message.SourceLineNumbers?.FileName ?? "TEST"; + var line = message.SourceLineNumbers?.LineNumber ?? -1; + var type = message.Level.ToString().ToLowerInvariant(); + var output = message.Level >= MessageLevel.Warning ? Console.Out : Console.Error; + + if (line > 0) + { + filename = String.Concat(filename, "(", line, ")"); + } + + yield return String.Format("{0} : {1} {2}{3:0000}: {4}", filename, type, "TEST", message.Id, message.ToString()); + } + } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs index 0a356ba9..20df1fe8 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs @@ -92,7 +92,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - if (overridableActionRows.TryGetValue(actionRow.Id.Id, out var collidingActionRow)) + if (requiredActionRows.TryGetValue(actionRow.Id.Id, out var collidingActionRow)) { this.Messaging.Write(ErrorMessages.ActionCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action)); if (null != collidingActionRow.SourceLineNumbers) diff --git a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs index 07eabbd6..ed11095f 100644 --- a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs +++ b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs @@ -66,7 +66,7 @@ namespace WixToolset.Core.Link { // If we're building a Merge Module, ignore all references to the Media table // because Merge Modules don't have Media tables. - if (this.BuildingMergeModule && wixSimpleReferenceRow.Table== "Media") + if (this.BuildingMergeModule && wixSimpleReferenceRow.Table == "Media") { continue; } @@ -77,7 +77,7 @@ namespace WixToolset.Core.Link } else // see if the symbol (and any of its duplicates) are appropriately accessible. { - IList accessible = DetermineAccessibleSymbols(section, symbol); + IList accessible = this.DetermineAccessibleSymbols(section, symbol); if (!accessible.Any()) { this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbol.Access)); @@ -89,7 +89,7 @@ namespace WixToolset.Core.Link if (null != accessibleSymbol.Section) { - RecursivelyResolveReferences(accessibleSymbol.Section); + this.RecursivelyResolveReferences(accessibleSymbol.Section); } } else // display errors for the duplicate symbols. @@ -125,14 +125,22 @@ namespace WixToolset.Core.Link { List symbols = new List(); - if (AccessibleSymbol(referencingSection, symbol)) + if (this.AccessibleSymbol(referencingSection, symbol)) { symbols.Add(symbol); } foreach (Symbol dupe in symbol.PossiblyConflictingSymbols) { - if (AccessibleSymbol(referencingSection, dupe)) + // don't count overridable WixActionTuples + WixActionTuple symbolAction = symbol.Row as WixActionTuple; + WixActionTuple dupeAction = dupe.Row as WixActionTuple; + if (symbolAction?.Overridable != dupeAction?.Overridable) + { + continue; + } + + if (this.AccessibleSymbol(referencingSection, dupe)) { symbols.Add(dupe); } @@ -140,7 +148,7 @@ namespace WixToolset.Core.Link foreach (Symbol dupe in symbol.RedundantSymbols) { - if (AccessibleSymbol(referencingSection, dupe)) + if (this.AccessibleSymbol(referencingSection, dupe)) { symbols.Add(dupe); } diff --git a/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs b/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs new file mode 100644 index 00000000..fcbff1aa --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs @@ -0,0 +1,54 @@ + +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class LinkerFixture + { + [Fact] + public void CanBuildWithOverridableActions() + { + var folder = TestData.Get(@"TestData\OverridableActions"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + var actions = section.Tuples.OfType().Where(wat => wat.Action.StartsWith("Set")).ToList(); + Assert.Equal(2, actions.Count); + //Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + //Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs new file mode 100644 index 00000000..d8743747 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 7365f140..2b202350 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -77,6 +77,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 191027c6fb6bf50d2d4825a7b3b9cd7bcfaea0cd Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Wed, 20 Mar 2019 12:48:26 -0400 Subject: Fix missing symbol for WixSuppressAction. Exposes non-overridable standard actions so WixToolsetTest.CoreIntegration.LinkerFixture.CanBuildWithOverridableActions now fails. --- src/WixToolset.Core/Compiler.cs | 2 +- .../TestData/OverridableActions/Package.wxs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index a175bc77..ef8d68fd 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -13445,7 +13445,7 @@ namespace WixToolset.Core { if (suppress) { - var row = this.Core.CreateRow(childSourceLineNumbers, TupleDefinitionType.WixSuppressAction); + var row = this.Core.CreateRow(childSourceLineNumbers, TupleDefinitionType.WixSuppressAction, new Identifier(AccessModifier.Public, sequenceTable, actionName)); row.Set(0, sequenceTable); row.Set(1, actionName); } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs index d8743747..db773d17 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs @@ -7,6 +7,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 42727c42248d1cb12ef8d9bc6f8ce7dda3f404c9 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Wed, 20 Mar 2019 21:18:37 -0400 Subject: Bug: Wixipl references to extension libraries Add test demonstrating that a persisted .wixipl file has broken references to bits in an extension's library. Same project built via wix.exe in one pass works as expected. --- .../TestData/Wixipl/Package.en-us.wxl | 11 +++ .../TestData/Wixipl/Package.wxs | 23 +++++ .../TestData/Wixipl/PackageComponents.wxs | 10 ++ .../TestData/Wixipl/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 + .../WixiplFixture.cs | 101 +++++++++++++++++++++ 6 files changed, 150 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs new file mode 100644 index 00000000..15807698 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 2b202350..874ee70b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -81,6 +81,10 @@ + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs index df6542e2..60706948 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs @@ -2,12 +2,14 @@ namespace WixToolsetTest.CoreIntegration { + using System; using System.IO; using System.Linq; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using WixToolset.Data; using WixToolset.Data.Tuples; + using Example.Extension; using Xunit; public class WixiplFixture @@ -88,5 +90,104 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal((int)ErrorMessages.Ids.WixiplSourceFileIsExclusive, result.ExitCode); } } + + [Fact] + public void CanBuildMsiUsingExtensionLibrary() + { + var folder = TestData.Get(@"TestData\Wixipl"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + "-ext", extensionPath, + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi"), + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"obj\test.wir")); + var section = intermediate.Sections.Single(); + + { + var wixFile = section.Tuples.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + } + + { + var binary = section.Tuples.OfType().Single(); + var path = binary[BinaryTupleFields.Data].AsPath().Path; + Assert.Contains("Example.Extension", path); + Assert.EndsWith(@"\0", path); + Assert.Equal(@"BinFromWir", binary[BinaryTupleFields.Name].AsString()); + } + } + } + + [Fact] + public void CanBuildWixiplUsingExtensionLibrary() + { + var folder = TestData.Get(@"TestData\Wixipl"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + "-ext", extensionPath, + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixipl"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(intermediateFolder, @"test.wixipl"), + "-ext", extensionPath, + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi"), + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"obj\test.wir")); + var section = intermediate.Sections.Single(); + + { + var wixFile = section.Tuples.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); + } + + { + var binary = section.Tuples.OfType().Single(); + var path = binary[BinaryTupleFields.Data].AsPath().Path; + Assert.Contains("Example.Extension", path); + Assert.EndsWith(@"\0", path); + Assert.Equal(@"BinFromWir", binary[BinaryTupleFields.Name].AsString()); + } + } + } } } -- cgit v1.2.3-55-g6feb From d1dbe29f3856d012acf5f96e8e66c43b74ab490d Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Tue, 2 Apr 2019 19:23:16 -0400 Subject: Add failing test to demonstrate ProgId bug. When a parent ProgId has Advertise="yes" and a child ProgId omits Advertise, the compiler assumes it's a non-advertised ProgId. It should be advertised instead. --- .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 39 ++++++++++++++++++++++ .../TestData/ProgId/Package.en-us.wxl | 11 ++++++ .../TestData/ProgId/Package.wxs | 21 ++++++++++++ .../TestData/ProgId/PackageComponents.wxs | 16 +++++++++ .../TestData/ProgId/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 +++ 6 files changed, 92 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index b5e1ec3d..c70d276d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -539,6 +539,45 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildVersionIndependentProgId() + { + var folder = TestData.Get(@"TestData\ProgId"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\Foo.exe"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + var progids = section.Tuples.OfType().OrderBy(tuple => tuple.ProgId).ToList(); + Assert.Equal(2, progids.Count); + Assert.Equal("Foo.File.hol", progids[0].ProgId); + Assert.Equal("Foo.File.hol.15", progids[0].ProgId_Parent); + Assert.Equal("Foo.File.hol.15", progids[1].ProgId); + Assert.Null(progids[1].ProgId_Parent); + } + } + [Fact(Skip = "Not implemented yet.")] public void CanBuildInstanceTransform() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs new file mode 100644 index 00000000..d4b53cd6 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs new file mode 100644 index 00000000..5166be16 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 874ee70b..8f78725a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -85,6 +85,10 @@ + + + + -- cgit v1.2.3-55-g6feb From c9c347bfb659878ce43c60daadac490e230ee17a Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sun, 12 May 2019 14:41:26 -0700 Subject: Fix inscript CA bit handling --- .../Bind/CreateOutputFromIRCommand.cs | 10 +++++-- .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 32 ++++++++++++++++++++++ .../TestData/SetProperty/Package.en-us.wxl | 11 ++++++++ .../TestData/SetProperty/Package.wxs | 23 ++++++++++++++++ .../TestData/SetProperty/PackageComponents.wxs | 10 +++++++ .../TestData/SetProperty/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 +++ 7 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 1b29fc9c..4b02b3aa 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -250,10 +250,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void AddCustomActionTuple(CustomActionTuple tuple, Output output) { var type = tuple.Win64 ? WindowsInstallerConstants.MsidbCustomActionType64BitScript : 0; - type |= tuple.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0; - type |= tuple.Impersonate ? 0 : WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate; type |= tuple.IgnoreResult ? WindowsInstallerConstants.MsidbCustomActionTypeContinue : 0; - type |= tuple.Hidden ? 0 : WindowsInstallerConstants.MsidbCustomActionTypeHideTarget; + type |= tuple.Hidden ? WindowsInstallerConstants.MsidbCustomActionTypeHideTarget : 0; type |= tuple.Async ? WindowsInstallerConstants.MsidbCustomActionTypeAsync : 0; type |= CustomActionExecutionType.FirstSequence == tuple.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence : 0; type |= CustomActionExecutionType.OncePerProcess == tuple.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess : 0; @@ -270,6 +268,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind type |= CustomActionTargetType.JScript == tuple.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeJScript : 0; type |= CustomActionTargetType.VBScript == tuple.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeVBScript : 0; + if (WindowsInstallerConstants.MsidbCustomActionTypeInScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeInScript)) + { + type |= tuple.Impersonate ? 0 : WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate; + type |= tuple.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0; + } + var table = output.EnsureTable(this.TableDefinitions["CustomAction"]); var row = table.CreateRow(tuple.SourceLineNumbers); row[0] = tuple.Id.Id; diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index f8f9180f..3e66ad0a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -539,6 +539,38 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildSetProperty() + { + var folder = TestData.Get(@"TestData\SetProperty"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var pdb = Pdb.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"), false); + var caRows = pdb.Output.Tables["CustomAction"].Rows.Single(); + Assert.Equal("SetINSTALLLOCATION", caRows.FieldAsString(0)); + Assert.Equal("51", caRows.FieldAsString(1)); + Assert.Equal("INSTALLLOCATION", caRows.FieldAsString(2)); + Assert.Equal("[INSTALLFOLDER]", caRows.FieldAsString(3)); + } + } + [Fact(Skip = "Test demonstrates failure")] public void CanBuildVersionIndependentProgId() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs new file mode 100644 index 00000000..eb907569 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 4ea650f9..03f67b19 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -48,6 +48,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 44fb31d655bc5860d45e3acd4cd0cbfaaf5f12eb Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Tue, 14 May 2019 14:19:34 -0400 Subject: Add Component/@Shared and fix UninstallWhenSuperseded --- .../Bind/CreateOutputFromIRCommand.cs | 4 +-- src/WixToolset.Core/Compiler.cs | 1 + .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 39 +++++++++++++++++++++- .../TestData/SingleFile/PackageComponents.wxs | 17 ++++++---- .../WixiplFixture.cs | 2 +- .../WixlibFixture.cs | 2 +- 6 files changed, 53 insertions(+), 12 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 4b02b3aa..6f33080d 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -232,9 +232,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind attributes |= tuple.NeverOverwrite ? WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite : 0; attributes |= tuple.Permanent ? WindowsInstallerConstants.MsidbComponentAttributesPermanent : 0; attributes |= tuple.SharedDllRefCount ? WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount : 0; + attributes |= tuple.Shared ? WindowsInstallerConstants.MsidbComponentAttributesShared : 0; attributes |= tuple.Transitive ? WindowsInstallerConstants.MsidbComponentAttributesTransitive : 0; - attributes |= tuple.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0; - + attributes |= tuple.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence : 0; attributes |= tuple.Win64 ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0; var table = output.EnsureTable(this.TableDefinitions["Component"]); diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 48e22f6d..25cc095b 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -2469,6 +2469,7 @@ namespace WixToolset.Core NeverOverwrite = neverOverwrite, Permanent = permanent, SharedDllRefCount = sharedDllRefCount, + Shared = shared, Transitive = transitive, UninstallWhenSuperseded = uninstallWhenSuperseded, Win64 = win64, diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 3e66ad0a..0aabc5a9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -43,7 +43,7 @@ namespace WixToolsetTest.CoreIntegration var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); var section = intermediate.Sections.Single(); - var wixFile = section.Tuples.OfType().Single(); + var wixFile = section.Tuples.OfType().First(); Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); } @@ -539,6 +539,43 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildSharedComponent() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + // Only one component is shared. + var sharedComponentTuples = section.Tuples.OfType(); + Assert.Equal(1, sharedComponentTuples.Sum(t => t.Shared ? 1 : 0)); + + // And it is this one. + var sharedComponentTuple = sharedComponentTuples.Single(t => t.Id.Id == "Shared.dll"); + Assert.True(sharedComponentTuple.Shared); + } + } + [Fact] public void CanBuildSetProperty() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs index e26c4509..b8e9f59c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs @@ -1,10 +1,13 @@ - - - - - - - + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs index c52fa68f..391b7021 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs @@ -50,7 +50,7 @@ namespace WixToolsetTest.CoreIntegration var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"obj\test.wir")); var section = intermediate.Sections.Single(); - var wixFile = section.Tuples.OfType().Single(); + var wixFile = section.Tuples.OfType().First(); Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); } diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs index 532f158d..07044a1f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs @@ -50,7 +50,7 @@ namespace WixToolsetTest.CoreIntegration var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"obj\test.wir")); var section = intermediate.Sections.Single(); - var wixFile = section.Tuples.OfType().Single(); + var wixFile = section.Tuples.OfType().First(); Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileTupleFields.Source].AsPath().Path); Assert.Equal(@"test.txt", wixFile[WixFileTupleFields.Source].PreviousValue.AsPath().Path); } -- cgit v1.2.3-55-g6feb From 3051bf2fc300df125115c9538a0bfc8256bfde6a Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 22 May 2019 16:28:02 -0700 Subject: Integrate short and source name changes to Directory and Shortcut tuples --- .../Bind/CalculateComponentGuids.cs | 7 ++- .../Bind/CreateOutputFromIRCommand.cs | 38 +++++++++++++-- src/WixToolset.Core/Compiler.cs | 8 ++-- src/WixToolset.Core/Compiler_2.cs | 55 +--------------------- .../ExtensibilityServices/ParseHelper.cs | 36 ++++---------- .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 2 +- .../TestData/SingleFileCompressed/Package.wxs | 2 +- 7 files changed, 55 insertions(+), 93 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs index f66ff375..835d9b8d 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs @@ -76,18 +76,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind targetPathsByDirectoryId = new Dictionary(directories.Count); // Get the target paths for all directories. - foreach (var row in directories) + foreach (var directory in directories) { // If the directory Id already exists, we will skip it here since // checking for duplicate primary keys is done later when importing tables // into database - if (targetPathsByDirectoryId.ContainsKey(row.Id.Id)) + if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) { continue; } - var targetName = Common.GetName(row.DefaultDir, false, true); - targetPathsByDirectoryId.Add(row.Id.Id, new ResolvedDirectory(row.ParentDirectoryRef, targetName)); + targetPathsByDirectoryId.Add(directory.Id.Id, new ResolvedDirectory(directory.ParentDirectoryRef, directory.Name)); } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 65958d0a..57861502 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -146,7 +146,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind break; case TupleDefinitionType.Shortcut: - this.AddTupleDefaultly(tuple, output, true); + this.AddShortcutTuple((ShortcutTuple)tuple, output); break; case TupleDefinitionType.TextStyle: @@ -338,11 +338,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void AddDirectoryTuple(DirectoryTuple tuple, Output output) { + var shortName = GetMsiFilenameValue(tuple.SourceShortName, tuple.SourceName); + var targetName = GetMsiFilenameValue(tuple.ShortName, tuple.Name); + + if (String.IsNullOrEmpty(targetName)) + { + targetName = "."; + } + + var defaultDir = String.IsNullOrEmpty(shortName)? targetName : shortName + ":" + targetName; + var table = output.EnsureTable(this.TableDefinitions["Directory"]); var row = table.CreateRow(tuple.SourceLineNumbers); row[0] = tuple.Id.Id; row[1] = tuple.ParentDirectoryRef; - row[2] = tuple.DefaultDir; + row[2] = defaultDir; } private void AddEnvironmentTuple(EnvironmentTuple tuple, Output output) @@ -447,7 +457,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var table = output.EnsureTable(this.TableDefinitions["Media"]); var row = (MediaRow)table.CreateRow(tuple.SourceLineNumbers); row.DiskId = tuple.DiskId; - row.LastSequence = tuple.LastSequence; + row.LastSequence = tuple.LastSequence ?? 0; row.DiskPrompt = tuple.DiskPrompt; row.Cabinet = tuple.Cabinet; row.VolumeLabel = tuple.VolumeLabel; @@ -695,6 +705,28 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[12] = tuple.Description; } + private void AddShortcutTuple(ShortcutTuple tuple, Output output) + { + var table = output.EnsureTable(this.TableDefinitions["Shortcut"]); + var row = table.CreateRow(tuple.SourceLineNumbers); + row[0] = tuple.Id.Id; + row[1] = tuple.DirectoryRef; + row[2] = GetMsiFilenameValue(tuple.ShortName, tuple.Name); + row[3] = tuple.ComponentRef; + row[4] = tuple.Target; + row[5] = tuple.Arguments; + row[6] = tuple.Description; + row[7] = tuple.Hotkey; + row[8] = tuple.IconRef; + row[9] = tuple.IconIndex; + row[10] = (int)tuple.Show; + row[11] = tuple.WorkingDirectory; + row[12] = tuple.DisplayResourceDll; + row[13] = tuple.DisplayResourceId; + row[14] = tuple.DescriptionResourceDll; + row[15] = tuple.DescriptionResourceId; + } + private void AddTextStyleTuple(TextStyleTuple tuple, Output output) { var styleBits = tuple.Bold ? WindowsInstallerConstants.MsidbTextStyleStyleBitsBold : 0; diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index ea018d54..d543c6b8 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -4028,7 +4028,6 @@ namespace WixToolset.Core string shortName = null; string sourceName = null; string shortSourceName = null; - string defaultDir = null; string symbols = null; foreach (var attrib in node.Attributes()) @@ -4208,7 +4207,7 @@ namespace WixToolset.Core } // Calculate the DefaultDir for the directory row. - defaultDir = String.IsNullOrEmpty(shortName) ? name : String.Concat(shortName, "|", name); + var defaultDir = String.IsNullOrEmpty(shortName) ? name : String.Concat(shortName, "|", name); if (!String.IsNullOrEmpty(sourceName)) { defaultDir = String.Concat(defaultDir, ":", String.IsNullOrEmpty(shortSourceName) ? sourceName : String.Concat(shortSourceName, "|", sourceName)); @@ -4260,7 +4259,10 @@ namespace WixToolset.Core var tuple = new DirectoryTuple(sourceLineNumbers, id) { ParentDirectoryRef = parentId, - DefaultDir = defaultDir, + Name = name, + ShortName = shortName, + SourceName = sourceName, + SourceShortName = shortSourceName, ComponentGuidGenerationSeed = componentGuidGenerationSeed }; diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index beebd4f8..9e965465 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -4401,7 +4401,8 @@ namespace WixToolset.Core var tuple = new ShortcutTuple(sourceLineNumbers, id) { DirectoryRef = directory, - Name = this.GetMsiFilenameValue(shortName, name), + Name = name, + ShortName = shortName, ComponentRef = componentId, Target = target, Arguments = arguments, @@ -4418,58 +4419,6 @@ namespace WixToolset.Core }; this.Core.AddTuple(tuple); - - //var row = this.Core.CreateRow(sourceLineNumbers, TupleDefinitionType.Shortcut, id); - //row.Set(1, directory); - //row.Set(2, this.GetMsiFilenameValue(shortName, name)); - //row.Set(3, componentId); - //if (advertise) - //{ - // if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName) - // { - // this.Core.Write(WarningMessages.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget)); - // } - // row.Set(4, Guid.Empty.ToString("B")); - //} - //else if (null != target) - //{ - // row.Set(4, target); - //} - //else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) - //{ - // row.Set(4, String.Format(CultureInfo.InvariantCulture, "[{0}]", defaultTarget)); - //} - //else if ("File" == parentElementLocalName) - //{ - // row.Set(4, String.Format(CultureInfo.InvariantCulture, "[#{0}]", defaultTarget)); - //} - //row.Set(5, arguments); - //row.Set(6, description); - //if (CompilerConstants.IntegerNotSet != hotkey) - //{ - // row.Set(7, hotkey); - //} - //row.Set(8, icon); - //if (CompilerConstants.IntegerNotSet != iconIndex) - //{ - // row.Set(9, iconIndex); - //} - - //if (show.HasValue) - //{ - // row.Set(10, show.Value); - //} - //row.Set(11, workingDirectory); - //row.Set(12, displayResourceDll); - //if (CompilerConstants.IntegerNotSet != displayResourceId) - //{ - // row.Set(13, displayResourceId); - //} - //row.Set(14, descriptionResourceDll); - //if (CompilerConstants.IntegerNotSet != descriptionResourceId) - //{ - // row.Set(15, descriptionResourceId); - //} } } diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs index 0e80100b..3318b914 100644 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs @@ -77,37 +77,14 @@ namespace WixToolset.Core.ExtensibilityServices public Identifier CreateDirectoryTuple(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, ISet sectionInlinedDirectoryIds, string shortName = null, string sourceName = null, string shortSourceName = null) { - string defaultDir; - - if (name.Equals("SourceDir") || this.IsValidShortFilename(name, false)) - { - defaultDir = name; - } - else + if (String.IsNullOrEmpty(shortName) && !name.Equals("SourceDir") && !this.IsValidShortFilename(name)) { - if (String.IsNullOrEmpty(shortName)) - { - shortName = this.CreateShortName(name, false, false, "Directory", parentId); - } - - defaultDir = String.Concat(shortName, "|", name); + shortName = this.CreateShortName(name, false, false, "Directory", parentId); } - if (!String.IsNullOrEmpty(sourceName)) + if (String.IsNullOrEmpty(shortSourceName) && !String.IsNullOrEmpty(sourceName) && !this.IsValidShortFilename(sourceName)) { - if (this.IsValidShortFilename(sourceName, false)) - { - defaultDir = String.Concat(defaultDir, ":", sourceName); - } - else - { - if (String.IsNullOrEmpty(shortSourceName)) - { - shortSourceName = this.CreateShortName(sourceName, false, false, "Directory", parentId); - } - - defaultDir = String.Concat(defaultDir, ":", shortSourceName, "|", sourceName); - } + shortSourceName = this.CreateShortName(sourceName, false, false, "Directory", parentId); } // For anonymous directories, create the identifier. If this identifier already exists in the @@ -126,7 +103,10 @@ namespace WixToolset.Core.ExtensibilityServices var tuple = new DirectoryTuple(sourceLineNumbers, id) { ParentDirectoryRef = parentId, - DefaultDir = defaultDir, + Name = name, + ShortName = shortName, + SourceName = sourceName, + SourceShortName = shortSourceName }; section.Tuples.Add(tuple); diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 8228ebfa..a329c16a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -137,7 +137,7 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\lowcab1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab"))); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs index 8bb1f6af..c21a669c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs @@ -10,7 +10,7 @@ - + -- cgit v1.2.3-55-g6feb From 6fc21f5c28fec195f8f6eccfed27109a4098d97f Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 27 Sep 2019 16:21:40 +1000 Subject: Add failing test for DefaultDir. --- .../MsiQueryFixture.cs | 54 ++++++++++++++++++++++ .../TestData/DefaultDir/DefaultDir.wxs | 21 +++++++++ .../ProductWithComponentGroupRef/Product.wxs | 21 +++++++++ .../WixToolsetTest.CoreIntegration.csproj | 4 +- 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs new file mode 100644 index 00000000..82934b9a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -0,0 +1,54 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class MsiQueryFixture + { + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesDirectoryTableWithValidDefaultDir() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DefaultDir", "DefaultDir.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Directory" }); + Assert.Equal(new[] + { + "Directory:INSTALLFOLDER\tProgramFilesFolder\toekcr5lq|MsiPackage", + "Directory:NAMEANDSHORTNAME\tINSTALLFOLDER\tSHORTNAM|NameAndShortName", + "Directory:NAMEANDSHORTSOURCENAME\tINSTALLFOLDER\tNAMEASSN|NameAndShortSourceName", + "Directory:NAMEWITHSHORTVALUE\tINSTALLFOLDER\tSHORTVAL", + "Directory:ProgramFilesFolder\tTARGETDIR\t.", + "Directory:SHORTNAMEANDLONGSOURCENAME\tINSTALLFOLDER\tSHNALSNM:6ukthv5q|ShortNameAndLongSourceName", + "Directory:SHORTNAMEONLY\tINSTALLFOLDER\tSHORTONL", + "Directory:SOURCENAME\tINSTALLFOLDER\ts2s5bq-i|NameAndSourceName:dhnqygng|SourceNameWithName", + "Directory:SOURCENAMESONLY\tINSTALLFOLDER\t.:SRCNAMON|SourceNameOnly", + "Directory:SOURCENAMEWITHSHORTVALUE\tINSTALLFOLDER\t.:SRTSRCVL", + "Directory:TARGETDIR\t\tSourceDir", + }, results); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs new file mode 100644 index 00000000..aeb3d554 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs new file mode 100644 index 00000000..0d1e89e6 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 03f67b19..8e7f1b8f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -1,4 +1,4 @@ - + @@ -13,10 +13,12 @@ + + -- cgit v1.2.3-55-g6feb From a44207c9296c3d5d18f07455f919781b88424c54 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 27 Sep 2019 16:51:30 +1000 Subject: Add failing test for FeatureGroup and parent Features. --- .../MsiQueryFixture.cs | 35 ++++++++++++++++++++++ .../TestData/FeatureGroup/FeatureGroup.wxs | 14 +++++++++ .../MinimalComponentGroup.wxs | 10 +++++++ .../WixToolsetTest.CoreIntegration.csproj | 2 ++ 4 files changed, 61 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 82934b9a..4fb136d5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -50,5 +50,40 @@ namespace WixToolsetTest.CoreIntegration }, results); } } + + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesFeatureTableWithParent() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "FeatureGroup", "FeatureGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Feature" }); + Assert.Equal(new[] + { + "Feature:ChildFeature\tParentFeature\tChildFeatureTitle\t\t2\t1\t\t0", + "Feature:ParentFeature\t\tParentFeatureTitle\t\t2\t1\t\t0", + "Feature:ProductFeature\t\tMsiPackageTitle\t\t2\t1\t\t0", + }, results); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs new file mode 100644 index 00000000..be302720 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs new file mode 100644 index 00000000..f62bbd0e --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 8e7f1b8f..8a11e531 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -18,6 +18,8 @@ + + -- cgit v1.2.3-55-g6feb From 2091dd91f20e37e4a1c93aab04386c89d55a37c9 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 27 Sep 2019 17:30:18 +1000 Subject: Add failing tests for Upgrade. --- .../MsiQueryFixture.cs | 67 ++++++++++++++++++++++ .../TestData/Upgrade/DetectOnly.wxs | 12 ++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 80 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 4fb136d5..edaf25cb 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -85,5 +85,72 @@ namespace WixToolsetTest.CoreIntegration }, results); } } + + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesUpgradeTableFromManualUpgrade() + { + var folder = TestData.Get(@"TestData\ManualUpgrade"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }, out var messages); + + Assert.Equal(0, result); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); + Assert.Equal(new[] + { + "Upgrade:{01120000-00E0-0000-0000-0000000FF1CE}\t12.0.0\t13.0.0\t\t260\t\tBLAHBLAHBLAH", + }, results); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesUpgradeTableFromDetectOnlyUpgrade() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); + Assert.Equal(new[] + { + "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", + "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t1.0.0.0\t1033\t\t2\t\tWIX_DOWNGRADE_DETECTED", + "Upgrade:{B05772EA-82B8-4DE0-B7EB-45B5F0CCFE6D}\t1.0.0\t\t\t256\t\tRELPRODFOUND", + }, results); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs new file mode 100644 index 00000000..587d8e95 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 8a11e531..e4da8b14 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -81,6 +81,7 @@ + -- cgit v1.2.3-55-g6feb From 1b266e62a450813718d0ff1c78f4470055adc5f3 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 30 Sep 2019 09:34:40 +1000 Subject: Add failing tests for AppSearch tables. --- .../MsiQueryFixture.cs | 137 +++++++++++++++++++++ .../TestData/AppSearch/ComponentSearch.wxs | 12 ++ .../TestData/AppSearch/DirectorySearch.wxs | 12 ++ .../TestData/AppSearch/FileSearch.wxs | 14 +++ .../TestData/AppSearch/RegistrySearch.wxs | 12 ++ .../WixToolsetTest.CoreIntegration.csproj | 4 + 6 files changed, 191 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 7c53d72f..e7443f35 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -9,6 +9,143 @@ namespace WixToolsetTest.CoreIntegration public class MsiQueryFixture { + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesAppSearchTablesFromComponentSearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "ComponentSearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "CompLocator" }); + Assert.Equal(new[] + { + "AppSearch:SAMPLECOMPFOUND\tSampleCompSearch", + "CompLocator:SampleCompSearch\t{4D9A0D20-D0CC-40DE-B580-EAD38B985217}\t1", + }, results); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesAppSearchTablesFromDirectorySearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "DirectorySearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator" }); + Assert.Equal(new[] + { + "AppSearch:SAMPLECOMPFOUND\tSampleCompSearch", + "DrLocator:SampleDirSearch\t\tC:\\SampleDir\t", + }, results); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesAppSearchTablesFromFileSearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "FileSearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator", "IniLocator" }); + Assert.Equal(new[] + { + "AppSearch:SAMPLEFILEFOUND\tSampleFileSearch", + "DrLocator:SampleFileSearch\tSampleIniFileSearch\t\t", + "IniLocator:SampleFileSearch\tsample.fil\tMySection\tMyKey\t\t1", + }, results); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesAppSearchTablesFromRegistrySearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "RegistrySearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); + Assert.Equal(new[] + { + "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", + "RegLocator:SampleRegSearch\t2\tSampleReg\t\t2", + }, results); + } + } + [Fact(Skip = "Test demonstrates failure")] public void PopulatesDirectoryTableWithValidDefaultDir() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs new file mode 100644 index 00000000..4dd701f0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs new file mode 100644 index 00000000..e255c83d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs new file mode 100644 index 00000000..c17d9848 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs new file mode 100644 index 00000000..f800264d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index e4da8b14..16f200c3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -13,6 +13,10 @@ + + + + -- cgit v1.2.3-55-g6feb From a96ae75e256712829ac2174688c71e6a14ba1943 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 30 Sep 2019 10:01:55 +1000 Subject: Add failing test for CustomAction. --- .../MsiQueryFixture.cs | 34 ++++++++++++++++++++++ .../CustomAction/UnscheduledCustomAction.wxs | 11 +++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 46 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index e7443f35..fb42d8fc 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -146,6 +146,40 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesCustomActionTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomAction", "UnscheduledCustomAction.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Binary", "CustomAction" }); + Assert.Equal(new[] + { + "Binary:Binary1\t[Binary data]", + "CustomAction:CustomAction1\t1\tBinary1\tInvalidEntryPoint\t", + }, results); + } + } + [Fact(Skip = "Test demonstrates failure")] public void PopulatesDirectoryTableWithValidDefaultDir() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs new file mode 100644 index 00000000..d9633869 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 16f200c3..1bbf2aab 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -17,6 +17,7 @@ + -- cgit v1.2.3-55-g6feb From d1ef3d5de29cdedce930f70e34b0e2b764f07269 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 30 Sep 2019 10:26:16 +1000 Subject: Add failing test for LockPermissions when Permissions is 0. --- .../MsiQueryFixture.cs | 33 ++++++++++++++++++++++ .../TestData/LockPermissions/EmptyPermissions.wxs | 13 +++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 47 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index fb42d8fc..01f30825 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -308,6 +308,39 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesLockPermissionsTableWithEmptyPermissions() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "LockPermissions", "EmptyPermissions.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "LockPermissions" }); + Assert.Equal(new[] + { + "LockPermissions:INSTALLFOLDER\tCreateFolder\t\tAdministrator\t0", + }, results); + } + } + [Fact(Skip = "Test demonstrates failure")] public void PopulatesUpgradeTableFromManualUpgrade() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs new file mode 100644 index 00000000..dfae2157 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 1bbf2aab..a8284a95 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -24,6 +24,7 @@ + -- cgit v1.2.3-55-g6feb From cc17da12830c5707949bfb4a9cd916b3e05eb5bc Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 30 Sep 2019 11:05:51 +1000 Subject: Add failing test for nesting a ProgId under an advertised Class. --- .../MsiQueryFixture.cs | 37 ++++++++++++++++++++++ .../TestData/ProgId/NestedUnderClass.wxs | 13 ++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 51 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 01f30825..880ccdb2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -146,6 +146,43 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesClassTablesWhenProgIdIsNestedUnderAdvertisedClass() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ProgId", "NestedUnderClass.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Class", "ProgId", "Registry" }); + Assert.Equal(new[] + { + "Class:{F12A6F69-117F-471F-AE73-F8E74218F498}\tLocalServer32\tProgIdComp\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tFakeClassF12A\t\t\t\t\t\t\tProductFeature\t", + "ProgId:73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\t\t{F12A6F69-117F-471F-AE73-F8E74218F498}\tFakeClassF12A\t\t", + "Registry:regUIIK326nDZpkWHuexeF58EikQvA\t0\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tNoOpen\tNoOpen73E7\tProgIdComp", + "Registry:regvrhMurMp98anbQJkpgA8yJCefdM\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\Version\t\t0.0.0.1\tProgIdComp", + "Registry:regY1F4E2lvu_Up6gV6c3jeN5ukn8s\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\LocalServer32\tThreadingModel\tApartment\tProgIdComp", + }, results); + } + } + [Fact(Skip = "Test demonstrates failure")] public void PopulatesCustomActionTable() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs new file mode 100644 index 00000000..0621eb8d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index a8284a95..a6337dce 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -101,6 +101,7 @@ + -- cgit v1.2.3-55-g6feb From 5baccaff10f10ae135a1de20ce22608c7dafbb11 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 30 Sep 2019 11:23:59 +1000 Subject: Add failing test for MsiShortcutProperty. --- .../MsiQueryFixture.cs | 33 ++++++++++++++++++++++ .../TestData/Shortcut/ShortcutProperty.wxs | 14 +++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 48 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 880ccdb2..c3b8d08b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -378,6 +378,39 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesMsiShortcutPropertyTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Shortcut", "ShortcutProperty.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty" }); + Assert.Equal(new[] + { + "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue", + }, results); + } + } + [Fact(Skip = "Test demonstrates failure")] public void PopulatesUpgradeTableFromManualUpgrade() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs new file mode 100644 index 00000000..d0a041b8 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index a6337dce..90d1f809 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -27,6 +27,7 @@ + -- cgit v1.2.3-55-g6feb From 4d52ab54b8ea64507ffe94910cbcfdf07d7d93c8 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 30 Sep 2019 11:36:49 +1000 Subject: Add failing test for ReserveCost. --- .../MsiQueryFixture.cs | 33 ++++++++++++++++++++++ .../TestData/ReserveCost/ReserveCost.wxs | 11 ++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 45 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index c3b8d08b..1e934421 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -411,6 +411,39 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesReserveCostTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ReserveCost", "ReserveCost.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "ReserveCost" }); + Assert.Equal(new[] + { + "ReserveCost:TestCost\tReserveCostComp\tINSTALLFOLDER\t100\t200", + }, results); + } + } + [Fact(Skip = "Test demonstrates failure")] public void PopulatesUpgradeTableFromManualUpgrade() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs new file mode 100644 index 00000000..3218295b --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 90d1f809..0ae6cd8c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -27,6 +27,7 @@ + -- cgit v1.2.3-55-g6feb From e23ee409c762dbc8d5f63007a15765b12706a1f0 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 30 Sep 2019 11:43:19 +1000 Subject: Add failing test for Font. --- .../MsiQueryFixture.cs | 33 ++++++++++++++++++++++ .../TestData/Font/FontTitle.wxs | 10 +++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 44 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 1e934421..e60f74fb 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -294,6 +294,39 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesFontTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Font", "FontTitle.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Font" }); + Assert.Equal(new[] + { + "Font:test.txt\tFakeFont", + }, results); + } + } + [Fact(Skip = "Test demonstrates failure")] public void PopulatesInstallExecuteSequenceTable() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs new file mode 100644 index 00000000..b7899b87 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 0ae6cd8c..e8eaf970 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -24,6 +24,7 @@ + -- cgit v1.2.3-55-g6feb From 6e691be7e8276b3a44a6631d7da8b3e09c8b103d Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 30 Sep 2019 11:58:11 +1000 Subject: Add failing test for MsiAssembly. --- .../MsiQueryFixture.cs | 36 ++++++++++ .../TestData/Assembly/Win32Assembly.wxs | 13 ++++ .../TestData/Assembly/data/test.manifest | 76 ++++++++++++++++++++++ .../WixToolsetTest.CoreIntegration.csproj | 2 + 4 files changed, 127 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index e60f74fb..2c064a58 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -411,6 +411,42 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesMsiAssemblyTables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Assembly", "Win32Assembly.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "Assembly", "data"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "MsiAssembly", "MsiAssemblyName" }); + Assert.Equal(new[] + { + "MsiAssembly:test.txt\tProductFeature\ttest.dll.manifest\t\t1", + "MsiAssemblyName:test.txt\tname\tMyApplication.app", + "MsiAssemblyName:test.txt\tversion\t1.0.0.0", + }, results); + } + } + [Fact(Skip = "Test demonstrates failure")] public void PopulatesMsiShortcutPropertyTable() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs new file mode 100644 index 00000000..980c5ca4 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest new file mode 100644 index 00000000..0da1f6d0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index e8eaf970..fce9b05b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -87,9 +87,11 @@ + + -- cgit v1.2.3-55-g6feb From e115df736067e5d765350f5335b1766663d91a9b Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 30 Sep 2019 12:09:20 +1000 Subject: Add failing test for ServiceInstall. --- .../MsiQueryFixture.cs | 33 ++++++++++++++++++++++ .../TestData/ServiceInstall/OwnProcess.wxs | 11 ++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 45 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 2c064a58..826d8985 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -513,6 +513,39 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesServiceInstallTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ServiceInstall", "OwnProcess.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "ServiceInstall" }); + Assert.Equal(new[] + { + "ServiceInstall:SampleService\tSampleService\t\t16\t4\t0\t\t\t\t\t\ttest.txt\t", + }, results); + } + } + [Fact(Skip = "Test demonstrates failure")] public void PopulatesUpgradeTableFromManualUpgrade() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs new file mode 100644 index 00000000..f308335e --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index fce9b05b..a5eadae3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -29,6 +29,7 @@ + -- cgit v1.2.3-55-g6feb From 860676fa5b40a1904478151e9b4934c004e7db63 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 7 Oct 2019 11:18:13 -0700 Subject: Implement Bundle build --- appveyor.cmd | 4 +- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 860 ++++++--------------- .../Bind/ProcessDependencyProvidersCommand.cs | 166 ++++ .../Bind/ProvidesDependency.cs | 110 --- .../Bind/ProvidesDependencyCollection.cs | 64 -- src/WixToolset.Core.Burn/Bind/SearchFacade.cs | 197 +++++ .../Bind/WixComponentSearchInfo.cs | 65 -- src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs | 56 -- .../Bind/WixProductSearchInfo.cs | 69 -- .../Bind/WixRegistrySearchInfo.cs | 93 --- src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs | 55 -- src/WixToolset.Core.Burn/BundleBackend.cs | 28 +- .../AutomaticallySlipstreamPatchesCommand.cs | 121 +-- .../Bundles/BundleHashAlgorithm.cs | 30 + src/WixToolset.Core.Burn/Bundles/BurnCommon.cs | 6 +- src/WixToolset.Core.Burn/Bundles/BurnWriter.cs | 10 +- ...CreateBootstrapperApplicationManifestCommand.cs | 312 ++++---- .../Bundles/CreateBundleExeCommand.cs | 171 ++++ .../Bundles/CreateBurnManifestCommand.cs | 399 +++++----- .../Bundles/CreateContainerCommand.cs | 59 +- .../Bundles/CreateNonUXContainers.cs | 134 ++++ .../Bundles/GetPackageFacadesCommand.cs | 78 +- .../OrderPackagesAndRollbackBoundariesCommand.cs | 66 +- src/WixToolset.Core.Burn/Bundles/PackageFacade.cs | 55 +- .../Bundles/ProcessExePackageCommand.cs | 23 +- .../Bundles/ProcessMsiPackageCommand.cs | 425 +++++----- .../Bundles/ProcessMspPackageCommand.cs | 102 +-- .../Bundles/ProcessMsuPackageCommand.cs | 20 +- .../Bundles/ProcessPayloadsCommand.cs | 89 ++- .../Bundles/VerifyPayloadsWithCatalogCommand.cs | 49 +- src/WixToolset.Core.Burn/BurnBackendFactory.cs | 3 +- src/WixToolset.Core.Burn/VerifyInterop.cs | 2 - .../WixToolset.Core.Burn.csproj | 1 + .../Bind/BindDatabaseCommand.cs | 8 +- .../Bind/CalculateComponentGuids.cs | 16 +- .../Bind/CreateCabinetsCommand.cs | 1 - .../Bind/CreateOutputFromIRCommand.cs | 16 + .../Bind/PathResolver.cs | 106 --- .../Bind/ProcessUncompressedFilesCommand.cs | 13 +- .../Bind/ResolvedDirectory.cs | 31 - .../Bind/UpdateFileFacadesCommand.cs | 1 - src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | 8 +- src/WixToolset.Core/Bind/FileResolver.cs | 8 +- .../Bind/ResolveDelayedFieldsCommand.cs | 2 +- src/WixToolset.Core/BindContext.cs | 4 +- src/WixToolset.Core/CommandLine/BuildCommand.cs | 11 +- src/WixToolset.Core/Compiler.cs | 2 +- src/WixToolset.Core/Compiler_Bundle.cs | 139 ++-- .../ExtensibilityServices/BackendHelper.cs | 11 +- .../ExtensibilityServices/PathResolver.cs | 91 +++ .../ExtensibilityServices/ResolvedDirectory.cs | 15 + .../WindowsInstallerBackendHelper.cs | 32 +- src/WixToolset.Core/Link/WixGroupingOrdering.cs | 31 +- src/WixToolset.Core/WixToolsetServiceProvider.cs | 4 +- .../BundleFixture.cs | 52 ++ .../TestData/.Data/burn.exe | Bin 0 -> 463360 bytes .../TestData/SimpleBundle/Bundle.en-us.wxl | 10 + .../TestData/SimpleBundle/Bundle.wxs | 11 + .../SimpleBundle/data/MsiPackage/Shared.dll | 1 + .../TestData/SimpleBundle/data/MsiPackage/test.txt | 1 + .../TestData/SimpleBundle/data/fakeba.dll | 1 + .../TestData/SimpleBundle/data/test.msi | Bin 0 -> 32768 bytes .../WixToolsetTest.CoreIntegration.csproj | 7 + 63 files changed, 2322 insertions(+), 2233 deletions(-) create mode 100644 src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs create mode 100644 src/WixToolset.Core.Burn/Bind/SearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ResolvedDirectory.cs create mode 100644 src/WixToolset.Core/ExtensibilityServices/PathResolver.cs create mode 100644 src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/appveyor.cmd b/appveyor.cmd index 75d75d99..27374ae5 100644 --- a/appveyor.cmd +++ b/appveyor.cmd @@ -2,13 +2,13 @@ @pushd %~dp0 @set _P=%~dp0build\Release\publish +dotnet test -c Release src\test\WixToolsetTest.CoreIntegration + dotnet pack -c Release src\WixToolset.Core dotnet pack -c Release src\WixToolset.Core.Burn dotnet pack -c Release src\WixToolset.Core.WindowsInstaller dotnet pack -c Release src\WixToolset.Core.TestPackage -dotnet build -c Release src\test\WixToolsetTest.CoreIntegration - @popd @endlocal diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 61aa5189..6b4b9d68 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -8,78 +8,51 @@ namespace WixToolset.Core.Burn using System.Globalization; using System.IO; using System.Linq; - using System.Reflection; using WixToolset.Core.Bind; + using WixToolset.Core.Burn.Bind; using WixToolset.Core.Burn.Bundles; using WixToolset.Data; - using WixToolset.Data.Bind; + using WixToolset.Data.Burn; + using WixToolset.Data.Tuples; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; - // TODO: (4.0) Refactor so that these don't need to be copied. - // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs - [Flags] - internal enum WixFileSearchAttributes - { - Default = 0x001, - MinVersionInclusive = 0x002, - MaxVersionInclusive = 0x004, - MinSizeInclusive = 0x008, - MaxSizeInclusive = 0x010, - MinDateInclusive = 0x020, - MaxDateInclusive = 0x040, - WantVersion = 0x080, - WantExists = 0x100, - IsDirectory = 0x200, - } - - [Flags] - internal enum WixRegistrySearchAttributes - { - Raw = 0x01, - Compatible = 0x02, - ExpandEnvironmentVariables = 0x04, - WantValue = 0x08, - WantExists = 0x10, - Win64 = 0x20, - } - - internal enum WixComponentSearchAttributes - { - KeyPath = 0x1, - State = 0x2, - WantDirectory = 0x4, - } - - [Flags] - internal enum WixProductSearchAttributes - { - Version = 0x1, - Language = 0x2, - State = 0x4, - Assignment = 0x8, - UpgradeCode = 0x10, - } - /// /// Binds a this.bundle. /// internal class BindBundleCommand { - public BindBundleCommand(IBindContext context) + public BindBundleCommand(IBindContext context, IEnumerable backedExtensions) { - //this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); + this.ServiceProvider = context.ServiceProvider; + + this.Messaging = context.ServiceProvider.GetService(); + + this.BackendHelper = context.ServiceProvider.GetService(); + this.BurnStubPath = context.BurnStubPath; + this.DefaultCompressionLevel = context.DefaultCompressionLevel; this.DelayedFields = context.DelayedFields; this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; + this.IntermediateFolder = context.IntermediateFolder; + this.Output = context.IntermediateRepresentation; + this.OutputPath = context.OutputPath; + this.OutputPdbPath = context.OutputPdbPath; + //this.VariableResolver = context.VariableResolver; - var extensionManager = context.ServiceProvider.GetService(); - - this.BackendExtensions = extensionManager.GetServices(); + this.BackendExtensions = backedExtensions; } - public CompressionLevel DefaultCompressionLevel { private get; set; } + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private string BurnStubPath { get; } + + private CompressionLevel? DefaultCompressionLevel { get; } public IEnumerable DelayedFields { get; } @@ -87,17 +60,15 @@ namespace WixToolset.Core.Burn private IEnumerable BackendExtensions { get; } - public Intermediate Output { private get; set; } - - public string OutputPath { private get; set; } + private Intermediate Output { get; } - public string PdbFile { private get; set; } + private string OutputPath { get; } - //public TableDefinitionCollection TableDefinitions { private get; set; } + private string OutputPdbPath { get; } - public string IntermediateFolder { private get; set; } + private string IntermediateFolder { get; } - public IVariableResolver VariableResolver { private get; set; } + private IVariableResolver VariableResolver { get; } public IEnumerable FileTransfers { get; private set; } @@ -105,196 +76,177 @@ namespace WixToolset.Core.Burn public void Execute() { - throw new NotImplementedException(); -#if TODO - this.FileTransfers = Enumerable.Empty(); - this.ContentFilePaths = Enumerable.Empty(); + var section = this.Output.Sections.Single(); + + var fileTransfers = new List(); + var trackedFiles = new List(); // First look for data we expect to find... Chain, WixGroups, etc. // We shouldn't really get past the linker phase if there are // no group items... that means that there's no UX, no Chain, // *and* no Containers! - Table chainPackageTable = this.GetRequiredTable("WixBundlePackage"); + var chainPackageTuples = this.GetRequiredTuples(); - Table wixGroupTable = this.GetRequiredTable("WixGroup"); + var wixGroupTuples = this.GetRequiredTuples(); // Ensure there is one and only one row in the WixBundle table. // The compiler and linker behavior should have colluded to get // this behavior. - WixBundleRow bundleRow = (WixBundleRow)this.GetSingleRowTable("WixBundle"); + var bundleTuple = this.GetSingleTuple(); + + bundleTuple.BundleId = Guid.NewGuid().ToString("B").ToUpperInvariant(); - bundleRow.PerMachine = true; // default to per-machine but the first-per user package wil flip the bundle per-user. + bundleTuple.Attributes |= WixBundleAttributes.PerMachine; // default to per-machine but the first-per user package wil flip the bundle per-user. // Ensure there is one and only one row in the WixBootstrapperApplication table. // The compiler and linker behavior should have colluded to get // this behavior. - Row baRow = this.GetSingleRowTable("WixBootstrapperApplication"); + var bundleApplicationTuple = this.GetSingleTuple(); // Ensure there is one and only one row in the WixChain table. // The compiler and linker behavior should have colluded to get // this behavior. - WixChainRow chainRow = (WixChainRow)this.GetSingleRowTable("WixChain"); + var chainTuple = this.GetSingleTuple(); - if (Messaging.Instance.EncounteredError) + if (this.Messaging.EncounteredError) { return; } // If there are any fields to resolve later, create the cache to populate during bind. - IDictionary variableCache = null; - if (this.DelayedFields.Any()) - { - variableCache = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - } + var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; // TODO: Although the WixSearch tables are defined in the Util extension, // the Bundle Binder has to know all about them. We hope to revisit all // of this in the 4.0 timeframe. - IEnumerable orderedSearches = this.OrderSearches(); + var orderedSearches = this.OrderSearches(section); +#if THIS_SHOULD_BE_DELETED_SINCE_RESOLVE_DOES_THIS_NOW // Extract files that come from cabinet files (this does not extract files from merge modules). { var extractEmbeddedFilesCommand = new ExtractEmbeddedFilesCommand(); extractEmbeddedFilesCommand.FilesWithEmbeddedFiles = ExpectedEmbeddedFiles; extractEmbeddedFilesCommand.Execute(); } +#endif // Get the explicit payloads. - RowDictionary payloads = new RowDictionary(this.Output.Tables["WixBundlePayload"]); + var payloadTuples = section.Tuples.OfType().ToDictionary(t => t.Id.Id); // Update explicitly authored payloads with their parent package and container (as appropriate) // to make it easier to gather the payloads later. - foreach (WixGroupRow row in wixGroupTable.RowsAs()) + foreach (var groupTuple in wixGroupTuples) { - if (ComplexReferenceChildType.Payload == row.ChildType) + if (ComplexReferenceChildType.Payload == groupTuple.ChildType) { - WixBundlePayloadRow payload = payloads.Get(row.ChildId); + var payloadTuple = payloadTuples[groupTuple.ChildId]; - if (ComplexReferenceParentType.Package == row.ParentType) + if (ComplexReferenceParentType.Package == groupTuple.ParentType) { - Debug.Assert(String.IsNullOrEmpty(payload.Package)); - payload.Package = row.ParentId; + Debug.Assert(String.IsNullOrEmpty(payloadTuple.PackageRef)); + payloadTuple.PackageRef = groupTuple.ParentId; } - else if (ComplexReferenceParentType.Container == row.ParentType) + else if (ComplexReferenceParentType.Container == groupTuple.ParentType) { - Debug.Assert(String.IsNullOrEmpty(payload.Container)); - payload.Container = row.ParentId; + Debug.Assert(String.IsNullOrEmpty(payloadTuple.ContainerRef)); + payloadTuple.ContainerRef = groupTuple.ParentId; } - else if (ComplexReferenceParentType.Layout == row.ParentType) + else if (ComplexReferenceParentType.Layout == groupTuple.ParentType) { - payload.LayoutOnly = true; + payloadTuple.LayoutOnly = true; } } } - List fileTransfers = new List(); - string layoutDirectory = Path.GetDirectoryName(this.OutputPath); + var layoutDirectory = Path.GetDirectoryName(this.OutputPath); // Process the explicitly authored payloads. ISet processedPayloads; { - ProcessPayloadsCommand command = new ProcessPayloadsCommand(); - command.Payloads = payloads.Values; - command.DefaultPackaging = bundleRow.DefaultPackagingType; - command.LayoutDirectory = layoutDirectory; + var command = new ProcessPayloadsCommand(this.ServiceProvider, this.BackendHelper, payloadTuples.Values, bundleTuple.DefaultPackagingType, layoutDirectory); command.Execute(); fileTransfers.AddRange(command.FileTransfers); - processedPayloads = new HashSet(payloads.Keys); + processedPayloads = new HashSet(payloadTuples.Keys); } IDictionary facades; { - GetPackageFacadesCommand command = new GetPackageFacadesCommand(); - command.PackageTable = chainPackageTable; - command.ExePackageTable = this.Output.Tables["WixBundleExePackage"]; - command.MsiPackageTable = this.Output.Tables["WixBundleMsiPackage"]; - command.MspPackageTable = this.Output.Tables["WixBundleMspPackage"]; - command.MsuPackageTable = this.Output.Tables["WixBundleMsuPackage"]; + var command = new GetPackageFacadesCommand(chainPackageTuples, section); command.Execute(); facades = command.PackageFacades; } - // Process each package facade. Note this is likely to add payloads and other rows to tables so + // Process each package facade. Note this is likely to add payloads and other tuples so // note that any indexes created above may be out of date now. - foreach (PackageFacade facade in facades.Values) + foreach (var facade in facades.Values) { - switch (facade.Package.Type) + switch (facade.PackageTuple.Type) { - case WixBundlePackageType.Exe: - { - ProcessExePackageCommand command = new ProcessExePackageCommand(); - command.AuthoredPayloads = payloads; - command.Facade = facade; - command.Execute(); - - // ? variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.ExePackage.Manufacturer); - } - break; + case WixBundlePackageType.Exe: + { + var command = new ProcessExePackageCommand(facade, payloadTuples); + command.Execute(); + } + break; - case WixBundlePackageType.Msi: - { - var command = new ProcessMsiPackageCommand(); - command.AuthoredPayloads = payloads; - command.Facade = facade; - command.BackendExtensions = this.BackendExtensions; - command.MsiFeatureTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiFeature"]); - command.MsiPropertyTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiProperty"]); - command.PayloadTable = this.Output.Tables["WixBundlePayload"]; - command.RelatedPackageTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleRelatedPackage"]); - command.Execute(); - - if (null != variableCache) - { - variableCache.Add(String.Concat("packageLanguage.", facade.Package.WixChainItemId), facade.MsiPackage.ProductLanguage.ToString()); - - if (null != facade.MsiPackage.Manufacturer) - { - variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.MsiPackage.Manufacturer); - } - } + case WixBundlePackageType.Msi: + { + var command = new ProcessMsiPackageCommand(this.ServiceProvider, this.BackendExtensions, section, facade, payloadTuples); + command.Execute(); - } - break; + if (null != variableCache) + { + var msiPackage = (WixBundleMsiPackageTuple)facade.SpecificPackageTuple; + variableCache.Add(String.Concat("packageLanguage.", facade.PackageId), msiPackage.ProductLanguage.ToString()); - case WixBundlePackageType.Msp: + if (null != msiPackage.Manufacturer) { - ProcessMspPackageCommand command = new ProcessMspPackageCommand(); - command.AuthoredPayloads = payloads; - command.Facade = facade; - command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); - command.Execute(); + variableCache.Add(String.Concat("packageManufacturer.", facade.PackageId), msiPackage.Manufacturer); } - break; + } - case WixBundlePackageType.Msu: - { - ProcessMsuPackageCommand command = new ProcessMsuPackageCommand(); - command.Facade = facade; - command.Execute(); - } - break; + } + break; + + case WixBundlePackageType.Msp: + { + var command = new ProcessMspPackageCommand(this.Messaging, section, facade, payloadTuples); + command.Execute(); + } + break; + + case WixBundlePackageType.Msu: + { + var command = new ProcessMsuPackageCommand(facade, payloadTuples); + command.Execute(); + } + break; } if (null != variableCache) { - BindBundleCommand.PopulatePackageVariableCache(facade.Package, variableCache); + BindBundleCommand.PopulatePackageVariableCache(facade.PackageTuple, variableCache); } } + if (this.Messaging.EncounteredError) + { + return; + } + // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) // are present. - payloads = new RowDictionary(this.Output.Tables["WixBundlePayload"]); + payloadTuples = section.Tuples.OfType().ToDictionary(t => t.Id.Id); // Process the payloads that were added by processing the packages. { - ProcessPayloadsCommand command = new ProcessPayloadsCommand(); - command.Payloads = payloads.Values.Where(r => !processedPayloads.Contains(r.Id)).ToList(); - command.DefaultPackaging = bundleRow.DefaultPackagingType; - command.LayoutDirectory = layoutDirectory; + var toProcess = payloadTuples.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList(); + + var command = new ProcessPayloadsCommand(this.ServiceProvider, this.BackendHelper, toProcess, bundleTuple.DefaultPackagingType, layoutDirectory); command.Execute(); fileTransfers.AddRange(command.FileTransfers); @@ -303,45 +255,43 @@ namespace WixToolset.Core.Burn } // Set the package metadata from the payloads now that we have the complete payload information. - ILookup payloadsByPackage = payloads.Values.ToLookup(p => p.Package); - { - foreach (PackageFacade facade in facades.Values) + var payloadsByPackageId = payloadTuples.Values.ToLookup(p => p.PackageRef); + + foreach (var facade in facades.Values) { - facade.Package.Size = 0; + facade.PackageTuple.Size = 0; - IEnumerable packagePayloads = payloadsByPackage[facade.Package.WixChainItemId]; + var packagePayloads = payloadsByPackageId[facade.PackageId]; - foreach (WixBundlePayloadRow payload in packagePayloads) + foreach (var payload in packagePayloads) { - facade.Package.Size += payload.FileSize; + facade.PackageTuple.Size += payload.FileSize; } - if (!facade.Package.InstallSize.HasValue) + if (!facade.PackageTuple.InstallSize.HasValue) { - facade.Package.InstallSize = facade.Package.Size; - + facade.PackageTuple.InstallSize = facade.PackageTuple.Size; } - WixBundlePayloadRow packagePayload = payloads[facade.Package.PackagePayload]; + var packagePayload = payloadTuples[facade.PackageTuple.PayloadRef]; - if (String.IsNullOrEmpty(facade.Package.Description)) + if (String.IsNullOrEmpty(facade.PackageTuple.Description)) { - facade.Package.Description = packagePayload.Description; + facade.PackageTuple.Description = packagePayload.Description; } - if (String.IsNullOrEmpty(facade.Package.DisplayName)) + if (String.IsNullOrEmpty(facade.PackageTuple.DisplayName)) { - facade.Package.DisplayName = packagePayload.DisplayName; + facade.PackageTuple.DisplayName = packagePayload.DisplayName; } } } - // Give the UX payloads their embedded IDs... - int uxPayloadIndex = 0; + var uxPayloadIndex = 0; { - foreach (WixBundlePayloadRow payload in payloads.Values.Where(p => Compiler.BurnUXContainerId == p.Container)) + foreach (var payload in payloadTuples.Values.Where(p => BurnConstants.BurnUXContainerName == p.ContainerRef)) { // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even // downloaded. The current engine requires the UX to be fully present before any downloading starts, @@ -349,7 +299,7 @@ namespace WixToolset.Core.Burn // into the temporary UX directory correctly, so we don't allow external either. if (PackagingType.Embedded != payload.Packaging) { - Messaging.Instance.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.FullFileName)); + this.Messaging.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.SourceFile.Path)); payload.Packaging = PackagingType.Embedded; } @@ -360,12 +310,12 @@ namespace WixToolset.Core.Burn if (0 == uxPayloadIndex) { // If we didn't get any UX payloads, it's an error! - throw new WixException(WixErrors.MissingBundleInformation("BootstrapperApplication")); + throw new WixException(ErrorMessages.MissingBundleInformation("BootstrapperApplication")); } // Give the embedded payloads without an embedded id yet an embedded id. - int payloadIndex = 0; - foreach (WixBundlePayloadRow payload in payloads.Values) + var payloadIndex = 0; + foreach (var payload in payloadTuples.Values) { Debug.Assert(PackagingType.Unknown != payload.Packaging); @@ -377,38 +327,38 @@ namespace WixToolset.Core.Burn } } + if (this.Messaging.EncounteredError) + { + return; + } + // Determine patches to automatically slipstream. { - AutomaticallySlipstreamPatchesCommand command = new AutomaticallySlipstreamPatchesCommand(); - command.PackageFacades = facades.Values; - command.SlipstreamMspTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleSlipstreamMsp"]); - command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); + var command = new AutomaticallySlipstreamPatchesCommand(section, facades.Values); command.Execute(); } // If catalog files exist, non-embedded payloads should validate with the catalogs. - IEnumerable catalogs = this.Output.Tables["WixBundleCatalog"].RowsAs(); + var catalogs = section.Tuples.OfType().ToList(); - if (catalogs.Any()) + if (catalogs.Count > 0) { - VerifyPayloadsWithCatalogCommand command = new VerifyPayloadsWithCatalogCommand(); - command.Catalogs = catalogs; - command.Payloads = payloads.Values; + var command = new VerifyPayloadsWithCatalogCommand(this.Messaging, catalogs, payloadTuples.Values); command.Execute(); } - if (Messaging.Instance.EncounteredError) + if (this.Messaging.EncounteredError) { return; } IEnumerable orderedFacades; - IEnumerable boundaries; + IEnumerable boundaries; { - OrderPackagesAndRollbackBoundariesCommand command = new OrderPackagesAndRollbackBoundariesCommand(); - command.Boundaries = new RowDictionary(this.Output.Tables["WixBundleRollbackBoundary"]); - command.PackageFacades = facades; - command.WixGroupTable = wixGroupTable; + var groupTuples = section.Tuples.OfType(); + var boundaryTuplesById = section.Tuples.OfType().ToDictionary(b => b.Id.Id); + + var command = new OrderPackagesAndRollbackBoundariesCommand(this.Messaging, groupTuples, boundaryTuplesById, facades); command.Execute(); orderedFacades = command.OrderedPackageFacades; @@ -418,280 +368,122 @@ namespace WixToolset.Core.Burn // Resolve any delayed fields before generating the manifest. if (this.DelayedFields.Any()) { - var resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand(); - resolveDelayedFieldsCommand.OutputType = this.Output.Type; - resolveDelayedFieldsCommand.DelayedFields = this.DelayedFields; - resolveDelayedFieldsCommand.ModularizationGuid = null; - resolveDelayedFieldsCommand.VariableCache = variableCache; + var resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand(this.Messaging, this.DelayedFields, variableCache); resolveDelayedFieldsCommand.Execute(); } - // Set the overridable bundle provider key. - this.SetBundleProviderKey(this.Output, bundleRow); + Dictionary dependencyTuplesByKey; + { + var command = new ProcessDependencyProvidersCommand(this.Messaging, section, facades); + command.Execute(); - // Import or generate dependency providers for packages in the manifest. - this.ProcessDependencyProviders(this.Output, facades); + bundleTuple.ProviderKey = command.BundleProviderKey; // set the overridable bundle provider key. + dependencyTuplesByKey = command.DependencyTuplesByKey; + } // Update the bundle per-machine/per-user scope based on the chained packages. - this.ResolveBundleInstallScope(bundleRow, orderedFacades); + this.ResolveBundleInstallScope(section, bundleTuple, orderedFacades); // Generate the core-defined BA manifest tables... { - CreateBootstrapperApplicationManifestCommand command = new CreateBootstrapperApplicationManifestCommand(); - command.BundleRow = bundleRow; - command.ChainPackages = orderedFacades; - command.LastUXPayloadIndex = uxPayloadIndex; - command.MsiFeatures = this.Output.Tables["WixBundleMsiFeature"].RowsAs(); - command.Output = this.Output; - command.Payloads = payloads; - command.TableDefinitions = this.TableDefinitions; - command.TempFilesLocation = this.IntermediateFolder; + var command = new CreateBootstrapperApplicationManifestCommand(section, bundleTuple, orderedFacades, uxPayloadIndex, payloadTuples, this.IntermediateFolder); command.Execute(); - WixBundlePayloadRow baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; - payloads.Add(baManifestPayload); + var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; + payloadTuples.Add(baManifestPayload.Id.Id, baManifestPayload); } - //foreach (BinderExtension extension in this.Extensions) - //{ - // extension.PostBind(this.Context); - //} +#if TODO + foreach (BinderExtension extension in this.Extensions) + { + extension.PostBind(this.Context); + } +#endif // Create all the containers except the UX container first so the manifest (that goes in the UX container) // can contain all size and hash information about the non-UX containers. - RowDictionary containers = new RowDictionary(this.Output.Tables["WixBundleContainer"]); - - ILookup payloadsByContainer = payloads.Values.ToLookup(p => p.Container); - - int attachedContainerIndex = 1; // count starts at one because UX container is "0". - - IEnumerable uxContainerPayloads = Enumerable.Empty(); - - foreach (WixBundleContainerRow container in containers.Values) + WixBundleContainerTuple uxContainer; + IEnumerable uxPayloads; + IEnumerable containers; { - IEnumerable containerPayloads = payloadsByContainer[container.Id]; - - if (!containerPayloads.Any()) - { - if (container.Id != Compiler.BurnDefaultAttachedContainerId) - { - // TODO: display warning that we're ignoring container that ended up with no paylods in it. - } - } - else if (Compiler.BurnUXContainerId == container.Id) - { - container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); - container.AttachedContainerIndex = 0; - - // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first - // in the list since that is the Payload that Burn attempts to load. - List uxPayloads = new List(); - - string baPayloadId = baRow.FieldAsString(0); - - foreach (WixBundlePayloadRow uxPayload in containerPayloads) - { - if (uxPayload.Id == baPayloadId) - { - uxPayloads.Insert(0, uxPayload); - } - else - { - uxPayloads.Add(uxPayload); - } - } + var command = new CreateNonUXContainers(this.BackendHelper, section, bundleApplicationTuple, payloadTuples, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); + command.Execute(); - uxContainerPayloads = uxPayloads; - } - else - { - container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + fileTransfers.AddRange(command.FileTransfers); - // Add detached containers to the list of file transfers. - if (ContainerType.Detached == container.Type) - { - FileTransfer transfer; - if (FileTransfer.TryCreate(container.WorkingPath, Path.Combine(layoutDirectory, container.Name), true, "Container", container.SourceLineNumbers, out transfer)) - { - transfer.Built = true; - fileTransfers.Add(transfer); - } - } - else // update the attached container index. - { - Debug.Assert(ContainerType.Attached == container.Type); + uxContainer = command.UXContainer; + uxPayloads = command.UXContainerPayloads; + containers = command.Containers; + } + + // Create the bundle manifest. + string manifestPath; + { + var executableName = Path.GetFileName(this.OutputPath); - container.AttachedContainerIndex = attachedContainerIndex; - ++attachedContainerIndex; - } + var command = new CreateBurnManifestCommand(this.Messaging, this.BackendExtensions, executableName, section, bundleTuple, containers, chainTuple, orderedFacades, boundaries, uxPayloads, payloadTuples, orderedSearches, catalogs, this.IntermediateFolder); + command.Execute(); - this.CreateContainer(container, containerPayloads, null); - } + manifestPath = command.OutputPath; } - // Create the bundle manifest then UX container. - string manifestPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml"); + // Create the UX container. { - var command = new CreateBurnManifestCommand(); - command.BackendExtensions = this.BackendExtensions; - command.Output = this.Output; - - command.BundleInfo = bundleRow; - command.Chain = chainRow; - command.Containers = containers; - command.Catalogs = catalogs; - command.ExecutableName = Path.GetFileName(this.OutputPath); - command.OrderedPackages = orderedFacades; - command.OutputPath = manifestPath; - command.RollbackBoundaries = boundaries; - command.OrderedSearches = orderedSearches; - command.Payloads = payloads; - command.UXContainerPayloads = uxContainerPayloads; + var command = new CreateContainerCommand(manifestPath, uxPayloads, uxContainer.WorkingPath, this.DefaultCompressionLevel); command.Execute(); - } - - WixBundleContainerRow uxContainer = containers[Compiler.BurnUXContainerId]; - this.CreateContainer(uxContainer, uxContainerPayloads, manifestPath); - - // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note - // that today, the x64 Burn uses the x86 stub. - string stubPlatform = (Platform.X64 == bundleRow.Platform) ? "x86" : bundleRow.Platform.ToString(); - - string stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); - string bundleTempPath = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath)); - - Messaging.Instance.OnMessage(WixVerboses.GeneratingBundle(bundleTempPath, stubFile)); - string bundleFilename = Path.GetFileName(this.OutputPath); - if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) - { - Messaging.Instance.OnMessage(WixErrors.InsecureBundleFilename(bundleFilename)); + uxContainer.Hash = command.Hash; + uxContainer.Size = command.Size; } - FileTransfer bundleTransfer; - if (FileTransfer.TryCreate(bundleTempPath, this.OutputPath, true, "Bundle", bundleRow.SourceLineNumbers, out bundleTransfer)) { - bundleTransfer.Built = true; - fileTransfers.Add(bundleTransfer); - } + var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleTuple, uxContainer, containers, this.BurnStubPath); + command.Execute(); - File.Copy(stubFile, bundleTempPath, true); - File.SetAttributes(bundleTempPath, FileAttributes.Normal); + fileTransfers.Add(command.Transfer); + } - this.UpdateBurnResources(bundleTempPath, this.OutputPath, bundleRow); +#if TODO + this.Pdb = new Pdb { Output = output }; - // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers - // if they should be attached. - using (BurnWriter writer = BurnWriter.Open(bundleTempPath)) + if (!String.IsNullOrEmpty(this.OutputPdbPath)) { - FileInfo burnStubFile = new FileInfo(bundleTempPath); - writer.InitializeBundleSectionData(burnStubFile.Length, bundleRow.BundleId); - - // Always attach the UX container first - writer.AppendContainer(uxContainer.WorkingPath, BurnWriter.Container.UX); + var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); + trackedFiles.Add(trackPdb); - // Now append all other attached containers - foreach (WixBundleContainerRow container in containers.Values) - { - if (ContainerType.Attached == container.Type) - { - // The container was only created if it had payloads. - if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) - { - writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); - } - } - } - } - - if (null != this.PdbFile) - { - Pdb pdb = new Pdb(); - pdb.Output = Output; - pdb.Save(this.PdbFile); + this.Pdb.Save(trackPdb.Path); } +#endif +#if TODO // does this need to come back, or do they only need to be in TrackedFiles? + this.ContentFilePaths = payloadTuples.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); +#endif this.FileTransfers = fileTransfers; - this.ContentFilePaths = payloads.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); - } - - private Table GetRequiredTable(string tableName) - { - Table table = this.Output.Tables[tableName]; - if (null == table || 0 == table.Rows.Count) - { - throw new WixException(WixErrors.MissingBundleInformation(tableName)); - } - - return table; - } - - private Row GetSingleRowTable(string tableName) - { - Table table = this.Output.Tables[tableName]; - if (null == table || 1 != table.Rows.Count) - { - throw new WixException(WixErrors.MissingBundleInformation(tableName)); - } + this.TrackedFiles = trackedFiles; - return table.Rows[0]; + // TODO: Eventually this gets removed + var intermediate = new Intermediate(this.Output.Id, new[] { section }, this.Output.Localizations.ToDictionary(l => l.Culture, StringComparer.OrdinalIgnoreCase), this.Output.EmbedFilePaths); + var trackIntermediate = this.BackendHelper.TrackFile(Path.Combine(this.IntermediateFolder, Path.GetFileName(Path.ChangeExtension(this.OutputPath, "wir"))), TrackedFileType.Intermediate); + intermediate.Save(trackIntermediate.Path); + trackedFiles.Add(trackIntermediate); } - private List OrderSearches() + private IEnumerable OrderSearches(IntermediateSection section) { - Dictionary allSearches = new Dictionary(); - Table wixFileSearchTable = this.Output.Tables["WixFileSearch"]; - if (null != wixFileSearchTable && 0 < wixFileSearchTable.Rows.Count) - { - foreach (Row row in wixFileSearchTable.Rows) - { - WixFileSearchInfo fileSearchInfo = new WixFileSearchInfo(row); - allSearches.Add(fileSearchInfo.Id, fileSearchInfo); - } - } - - Table wixRegistrySearchTable = this.Output.Tables["WixRegistrySearch"]; - if (null != wixRegistrySearchTable && 0 < wixRegistrySearchTable.Rows.Count) - { - foreach (Row row in wixRegistrySearchTable.Rows) - { - WixRegistrySearchInfo registrySearchInfo = new WixRegistrySearchInfo(row); - allSearches.Add(registrySearchInfo.Id, registrySearchInfo); - } - } - - Table wixComponentSearchTable = this.Output.Tables["WixComponentSearch"]; - if (null != wixComponentSearchTable && 0 < wixComponentSearchTable.Rows.Count) - { - foreach (Row row in wixComponentSearchTable.Rows) - { - WixComponentSearchInfo componentSearchInfo = new WixComponentSearchInfo(row); - allSearches.Add(componentSearchInfo.Id, componentSearchInfo); - } - } + var searchesById = section.Tuples + .Where(t => t.Definition.Type == TupleDefinitionType.WixComponentSearch || + t.Definition.Type == TupleDefinitionType.WixFileSearch || + t.Definition.Type == TupleDefinitionType.WixProductSearch || + t.Definition.Type == TupleDefinitionType.WixRegistrySearch) + .ToDictionary(t => t.Id.Id); - Table wixProductSearchTable = this.Output.Tables["WixProductSearch"]; - if (null != wixProductSearchTable && 0 < wixProductSearchTable.Rows.Count) - { - foreach (Row row in wixProductSearchTable.Rows) - { - WixProductSearchInfo productSearchInfo = new WixProductSearchInfo(row); - allSearches.Add(productSearchInfo.Id, productSearchInfo); - } - } + var orderedSearches = new List(searchesById.Keys.Count); - // Merge in the variable/condition info and get the canonical ordering for - // the searches. - List orderedSearches = new List(); - Table wixSearchTable = this.Output.Tables["WixSearch"]; - if (null != wixSearchTable && 0 < wixSearchTable.Rows.Count) + foreach (var searchTuple in section.Tuples.OfType()) { - orderedSearches.Capacity = wixSearchTable.Rows.Count; - foreach (Row row in wixSearchTable.Rows) + if (searchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple)) { - WixSearchInfo searchInfo = allSearches[(string)row[0]]; - searchInfo.AddWixSearchRowInfo(row); - orderedSearches.Add(searchInfo); + orderedSearches.Add(new SearchFacade(searchTuple, specificSearchTuple)); } } @@ -703,9 +495,9 @@ namespace WixToolset.Core.Burn /// /// The package with properties to cache. /// The property cache. - private static void PopulatePackageVariableCache(WixBundlePackageRow package, IDictionary variableCache) + private static void PopulatePackageVariableCache(WixBundlePackageTuple package, IDictionary variableCache) { - string id = package.WixChainItemId; + var id = package.Id.Id; variableCache.Add(String.Concat("packageDescription.", id), package.Description); //variableCache.Add(String.Concat("packageLanguage.", id), package.Language); @@ -714,231 +506,63 @@ namespace WixToolset.Core.Burn variableCache.Add(String.Concat("packageVersion.", id), package.Version); } - private void CreateContainer(WixBundleContainerRow container, IEnumerable containerPayloads, string manifestFile) + private void ResolveBundleInstallScope(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable facades) { - CreateContainerCommand command = new CreateContainerCommand(); - command.DefaultCompressionLevel = this.DefaultCompressionLevel; - command.Payloads = containerPayloads; - command.ManifestFile = manifestFile; - command.OutputPath = container.WorkingPath; - command.Execute(); - - container.Hash = command.Hash; - container.Size = command.Size; - } + var dependencyTuplesById = section.Tuples.OfType().ToDictionary(t => t.Id.Id); - private void ResolveBundleInstallScope(WixBundleRow bundleInfo, IEnumerable facades) - { - foreach (PackageFacade facade in facades) + foreach (var facade in facades) { - if (bundleInfo.PerMachine && YesNoDefaultType.No == facade.Package.PerMachine) + if (bundleTuple.PerMachine && YesNoDefaultType.No == facade.PackageTuple.PerMachine) { - Messaging.Instance.OnMessage(WixVerboses.SwitchingToPerUserPackage(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); + this.Messaging.Write(VerboseMessages.SwitchingToPerUserPackage(facade.PackageTuple.SourceLineNumbers, facade.PackageId)); - bundleInfo.PerMachine = false; + bundleTuple.Attributes &= ~WixBundleAttributes.PerMachine; break; } } - foreach (PackageFacade facade in facades) + foreach (var facade in facades) { // Update package scope from bundle scope if default. - if (YesNoDefaultType.Default == facade.Package.PerMachine) + if (YesNoDefaultType.Default == facade.PackageTuple.PerMachine) { - facade.Package.PerMachine = bundleInfo.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; + facade.PackageTuple.PerMachine = bundleTuple.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; } // We will only register packages in the same scope as the bundle. Warn if any packages with providers // are in a different scope and not permanent (permanents typically don't need a ref-count). - if (!bundleInfo.PerMachine && YesNoDefaultType.Yes == facade.Package.PerMachine && !facade.Package.Permanent && 0 < facade.Provides.Count) + if (!bundleTuple.PerMachine && + YesNoDefaultType.Yes == facade.PackageTuple.PerMachine && + !facade.PackageTuple.Permanent && + dependencyTuplesById.ContainsKey(facade.PackageId)) { - Messaging.Instance.OnMessage(WixWarnings.NoPerMachineDependencies(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); + this.Messaging.Write(WarningMessages.NoPerMachineDependencies(facade.PackageTuple.SourceLineNumbers, facade.PackageId)); } } } - private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleRow bundleInfo) + private IEnumerable GetRequiredTuples() where T : IntermediateTuple { - WixToolset.Dtf.Resources.ResourceCollection resources = new WixToolset.Dtf.Resources.ResourceCollection(); - WixToolset.Dtf.Resources.VersionResource version = new WixToolset.Dtf.Resources.VersionResource("#1", 1033); + var tuples = this.Output.Sections.Single().Tuples.OfType().ToList(); - version.Load(bundleTempPath); - resources.Add(version); - - // Ensure the bundle info provides a full four part version. - Version fourPartVersion = new Version(bundleInfo.Version); - int major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; - int minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; - int build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; - int revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; - - if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) - { - throw new WixException(WixErrors.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version)); - } - - fourPartVersion = new Version(major, minor, build, revision); - version.FileVersion = fourPartVersion; - version.ProductVersion = fourPartVersion; - - WixToolset.Dtf.Resources.VersionStringTable strings = version[1033]; - strings["LegalCopyright"] = bundleInfo.Copyright; - strings["OriginalFilename"] = Path.GetFileName(outputPath); - strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. - strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts. - - if (!String.IsNullOrEmpty(bundleInfo.Name)) + if (0 == tuples.Count) { - strings["ProductName"] = bundleInfo.Name; - strings["FileDescription"] = bundleInfo.Name; + throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); } - if (!String.IsNullOrEmpty(bundleInfo.Publisher)) - { - strings["CompanyName"] = bundleInfo.Publisher; - } - else - { - strings["CompanyName"] = String.Empty; - } - - if (!String.IsNullOrEmpty(bundleInfo.IconPath)) - { - Dtf.Resources.GroupIconResource iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033); - iconGroup.ReadFromFile(bundleInfo.IconPath); - resources.Add(iconGroup); - - foreach (Dtf.Resources.Resource icon in iconGroup.Icons) - { - resources.Add(icon); - } - } - - if (!String.IsNullOrEmpty(bundleInfo.SplashScreenBitmapPath)) - { - Dtf.Resources.BitmapResource bitmap = new Dtf.Resources.BitmapResource("#1", 1033); - bitmap.ReadFromFile(bundleInfo.SplashScreenBitmapPath); - resources.Add(bitmap); - } - - resources.Save(bundleTempPath); + return tuples; } -//#region DependencyExtension - /// - /// Imports authored dependency providers for each package in the manifest, - /// and generates dependency providers for certain package types that do not - /// have a provider defined. - /// - /// The object for the bundle. - /// An indexed collection of chained packages. - private void ProcessDependencyProviders(Output bundle, IDictionary facades) + private T GetSingleTuple() where T : IntermediateTuple { - // First import any authored dependencies. These may merge with imported provides from MSI packages. - Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; - if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) - { - // Add package information for each dependency provider authored into the manifest. - foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) - { - string packageId = (string)wixDependencyProviderRow[1]; - - PackageFacade facade = null; - if (facades.TryGetValue(packageId, out facade)) - { - ProvidesDependency dependency = new ProvidesDependency(wixDependencyProviderRow); - - if (String.IsNullOrEmpty(dependency.Key)) - { - switch (facade.Package.Type) - { - // The WixDependencyExtension allows an empty Key for MSIs and MSPs. - case WixBundlePackageType.Msi: - dependency.Key = facade.MsiPackage.ProductCode; - break; - case WixBundlePackageType.Msp: - dependency.Key = facade.MspPackage.PatchCode; - break; - } - } - - if (String.IsNullOrEmpty(dependency.Version)) - { - dependency.Version = facade.Package.Version; - } - - // If the version is still missing, a version could not be harvested from the package and was not authored. - if (String.IsNullOrEmpty(dependency.Version)) - { - Messaging.Instance.OnMessage(WixErrors.MissingDependencyVersion(facade.Package.WixChainItemId)); - } - - if (String.IsNullOrEmpty(dependency.DisplayName)) - { - dependency.DisplayName = facade.Package.DisplayName; - } + var tuples = this.Output.Sections.Single().Tuples.OfType().ToList(); - if (!facade.Provides.Merge(dependency)) - { - Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); - } - } - } - } - - // Generate providers for MSI packages that still do not have providers. - foreach (PackageFacade facade in facades.Values) + if (1 != tuples.Count) { - string key = null; - - if (WixBundlePackageType.Msi == facade.Package.Type && 0 == facade.Provides.Count) - { - key = facade.MsiPackage.ProductCode; - } - else if (WixBundlePackageType.Msp == facade.Package.Type && 0 == facade.Provides.Count) - { - key = facade.MspPackage.PatchCode; - } - - if (!String.IsNullOrEmpty(key)) - { - ProvidesDependency dependency = new ProvidesDependency(key, facade.Package.Version, facade.Package.DisplayName, 0); - - if (!facade.Provides.Merge(dependency)) - { - Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); - } - } + throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); } - } - /// - /// Sets the provider key for the bundle. - /// - /// The object for the bundle. - /// The containing the provider key and other information for the bundle. - private void SetBundleProviderKey(Output bundle, WixBundleRow bundleInfo) - { - // From DependencyCommon.cs in the WixDependencyExtension. - const int ProvidesAttributesBundle = 0x10000; - - Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; - if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) - { - // Search the WixDependencyProvider table for the single bundle provider key. - foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) - { - object attributes = wixDependencyProviderRow[5]; - if (null != attributes && 0 != (ProvidesAttributesBundle & (int)attributes)) - { - bundleInfo.ProviderKey = (string)wixDependencyProviderRow[2]; - break; - } - } - } - - // Defaults to the bundle ID as the provider key. -#endif + return tuples[0]; } } } diff --git a/src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs b/src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs new file mode 100644 index 00000000..7f36dbcc --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs @@ -0,0 +1,166 @@ +// 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.Burn.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Extensibility.Services; + using WixToolset.Data.Tuples; + + internal class ProcessDependencyProvidersCommand + { + public ProcessDependencyProvidersCommand(IMessaging messaging, IntermediateSection section, IDictionary facades) + { + this.Messaging = messaging; + this.Section = section; + + this.Facades = facades; + } + + public string BundleProviderKey { get; private set; } + + public Dictionary DependencyTuplesByKey { get; private set; } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private IDictionary Facades { get; } + + /// + /// Sets the explicitly provided bundle provider key, if provided. And... + /// Imports authored dependency providers for each package in the manifest, + /// and generates dependency providers for certain package types that do not + /// have a provider defined. + /// + public void Execute() + { + var wixDependencyProviderTuples = this.Section.Tuples.OfType(); + + foreach (var wixDependencyProviderTuple in wixDependencyProviderTuples) + { + // Sets the provider key for the bundle, if it is not set already. + if (String.IsNullOrEmpty(this.BundleProviderKey)) + { + if (wixDependencyProviderTuple.Bundle) + { + this.BundleProviderKey = wixDependencyProviderTuple.ProviderKey; + } + } + + // Import any authored dependencies. These may merge with imported provides from MSI packages. + var packageId = wixDependencyProviderTuple.Id.Id; + + if (this.Facades.TryGetValue(packageId, out var facade)) + { + var dependency = new ProvidesDependencyTuple(wixDependencyProviderTuple.SourceLineNumbers, wixDependencyProviderTuple.Id) + { + PackageRef = packageId, + Key = wixDependencyProviderTuple.ProviderKey, + Version = wixDependencyProviderTuple.Version, + DisplayName = wixDependencyProviderTuple.DisplayName, + Attributes = (int)wixDependencyProviderTuple.Attributes + }; + + if (String.IsNullOrEmpty(dependency.Key)) + { + switch (facade.SpecificPackageTuple) + { + // The WixDependencyExtension allows an empty Key for MSIs and MSPs. + case WixBundleMsiPackageTuple msiPackage: + dependency.Key = msiPackage.ProductCode; + break; + case WixBundleMspPackageTuple mspPackage: + dependency.Key = mspPackage.PatchCode; + break; + } + } + + if (String.IsNullOrEmpty(dependency.Version)) + { + dependency.Version = facade.PackageTuple.Version; + } + + // If the version is still missing, a version could not be harvested from the package and was not authored. + if (String.IsNullOrEmpty(dependency.Version)) + { + this.Messaging.Write(ErrorMessages.MissingDependencyVersion(facade.PackageId)); + } + + if (String.IsNullOrEmpty(dependency.DisplayName)) + { + dependency.DisplayName = facade.PackageTuple.DisplayName; + } + + this.Section.Tuples.Add(dependency); + } + } + + this.DependencyTuplesByKey = this.GetDependencyTuplesByKey(); + + // Generate providers for MSI and MSP packages that still do not have providers. + foreach (var facade in this.Facades.Values) + { + string key = null; + + //if (WixBundlePackageType.Msi == facade.PackageTuple.Type) + if (facade.SpecificPackageTuple is WixBundleMsiPackageTuple msiPackage) + { + //var msiPackage = (WixBundleMsiPackageTuple)facade.SpecificPackageTuple; + key = msiPackage.ProductCode; + } + //else if (WixBundlePackageType.Msp == facade.PackageTuple.Type) + else if (facade.SpecificPackageTuple is WixBundleMspPackageTuple mspPackage) + { + //var mspPackage = (WixBundleMspPackageTuple)facade.SpecificPackageTuple; + key = mspPackage.PatchCode; + } + + if (!String.IsNullOrEmpty(key) && !this.DependencyTuplesByKey.ContainsKey(key)) + { + var dependency = new ProvidesDependencyTuple(facade.PackageTuple.SourceLineNumbers, facade.PackageTuple.Id) + { + PackageRef = facade.PackageId, + Key = key, + Version = facade.PackageTuple.Version, + DisplayName = facade.PackageTuple.DisplayName + }; + + this.Section.Tuples.Add(dependency); + + this.DependencyTuplesByKey.Add(dependency.Key, dependency); + } + } + } + + private Dictionary GetDependencyTuplesByKey() + { + var dependencyTuplesByKey = new Dictionary(); + + var dependencyTuples = this.Section.Tuples.OfType(); + + foreach (var dependency in dependencyTuples) + { + if (dependencyTuplesByKey.TryGetValue(dependency.Key, out var collision)) + { + // If not a perfect dependency collision, display an error. + if (dependency.Key != collision.Key || + dependency.Version != collision.Version || + dependency.DisplayName != collision.DisplayName) + { + this.Messaging.Write(ErrorMessages.DuplicateProviderDependencyKey(dependency.Key, dependency.PackageRef)); + } + } + else + { + dependencyTuplesByKey.Add(dependency.Key, dependency); + } + } + + return dependencyTuplesByKey; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs b/src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs deleted file mode 100644 index c7eba01c..00000000 --- a/src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs +++ /dev/null @@ -1,110 +0,0 @@ -// 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.Burn -{ - using System; - using System.Xml; - using WixToolset.Data; - - /// - /// Represents an authored or imported dependency provider. - /// - internal sealed class ProvidesDependency - { -#if TODO - /// - /// Creates a new instance of the class from a . - /// - /// The from which data is imported. - internal ProvidesDependency(Row row) - : this((string)row[2], (string)row[3], (string)row[4], (int?)row[5]) - { - } -#endif - - /// - /// Creates a new instance of the class. - /// - /// The unique key of the dependency. - /// Additional attributes for the dependency. - internal ProvidesDependency(string key, string version, string displayName, int? attributes) - { - this.Key = key; - this.Version = version; - this.DisplayName = displayName; - this.Attributes = attributes; - } - - /// - /// Gets or sets the unique key of the package provider. - /// - internal string Key { get; set; } - - /// - /// Gets or sets the version of the package provider. - /// - internal string Version { get; set; } - - /// - /// Gets or sets the display name of the package provider. - /// - internal string DisplayName { get; set; } - - /// - /// Gets or sets the attributes for the dependency. - /// - internal int? Attributes { get; set; } - - /// - /// Gets or sets whether the dependency was imported from the package. - /// - internal bool Imported { get; set; } - - /// - /// Gets whether certain properties are the same. - /// - /// Another to compare. - /// This is not the same as object equality, but only checks a subset of properties - /// to determine if the objects are similar and could be merged into a collection. - /// True if certain properties are the same. - internal bool Equals(ProvidesDependency other) - { - if (null != other) - { - return this.Key == other.Key && - this.Version == other.Version && - this.DisplayName == other.DisplayName; - } - - return false; - } - - /// - /// Writes the dependency to the bundle XML manifest. - /// - /// The for the bundle XML manifest. - internal void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("Provides"); - writer.WriteAttributeString("Key", this.Key); - - if (!String.IsNullOrEmpty(this.Version)) - { - writer.WriteAttributeString("Version", this.Version); - } - - if (!String.IsNullOrEmpty(this.DisplayName)) - { - writer.WriteAttributeString("DisplayName", this.DisplayName); - } - - if (this.Imported) - { - // The package dependency was explicitly authored into the manifest. - writer.WriteAttributeString("Imported", "yes"); - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs b/src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs deleted file mode 100644 index 668b81d3..00000000 --- a/src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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.Burn -{ - using System; - using System.Collections.ObjectModel; - - /// - /// A case-insensitive collection of unique objects. - /// - internal sealed class ProvidesDependencyCollection : KeyedCollection - { - /// - /// Creates a case-insensitive collection of unique objects. - /// - internal ProvidesDependencyCollection() - : base(StringComparer.InvariantCultureIgnoreCase) - { - } - - /// - /// Adds the to the collection if it doesn't already exist. - /// - /// The to add to the collection. - /// True if the was added to the collection; otherwise, false. - /// The parameter is null. - internal bool Merge(ProvidesDependency dependency) - { - if (null == dependency) - { - throw new ArgumentNullException("dependency"); - } - - // If the dependency key is already in the collection, verify equality for a subset of properties. - if (this.Contains(dependency.Key)) - { - ProvidesDependency current = this[dependency.Key]; - if (!current.Equals(dependency)) - { - return false; - } - } - - base.Add(dependency); - return true; - } - - /// - /// Gets the for the . - /// - /// The dependency to index. - /// The parameter is null. - /// The for the . - protected override string GetKeyForItem(ProvidesDependency dependency) - { - if (null == dependency) - { - throw new ArgumentNullException("dependency"); - } - - return dependency.Key; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/SearchFacade.cs b/src/WixToolset.Core.Burn/Bind/SearchFacade.cs new file mode 100644 index 00000000..65f3cb5b --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/SearchFacade.cs @@ -0,0 +1,197 @@ +// 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.Burn +{ + using System; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Tuples; + + internal class SearchFacade + { + public SearchFacade(WixSearchTuple searchTuple, IntermediateTuple searchSpecificTuple) + { + this.SearchTuple = searchTuple; + this.SearchSpecificTuple = searchSpecificTuple; + } + + public WixSearchTuple SearchTuple { get; } + + public IntermediateTuple SearchSpecificTuple { get; } + + /// + /// Generates Burn manifest and ParameterInfo-style markup a search. + /// + /// + public void WriteXml(XmlTextWriter writer) + { + switch (this.SearchSpecificTuple) + { + case WixComponentSearchTuple tuple: + this.WriteComponentSearchXml(writer, tuple); + break; + case WixFileSearchTuple tuple: + this.WriteFileSearchXml(writer, tuple); + break; + case WixProductSearchTuple tuple: + this.WriteProductSearchXml(writer, tuple); + break; + case WixRegistrySearchTuple tuple: + this.WriteRegistrySearchXml(writer, tuple); + break; + } + } + + private void WriteCommonAttributes(XmlTextWriter writer) + { + writer.WriteAttributeString("Id", this.SearchTuple.Id.Id); + writer.WriteAttributeString("Variable", this.SearchTuple.Variable); + if (!String.IsNullOrEmpty(this.SearchTuple.Condition)) + { + writer.WriteAttributeString("Condition", this.SearchTuple.Condition); + } + } + + private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchTuple searchTuple) + { + writer.WriteStartElement("MsiComponentSearch"); + + this.WriteCommonAttributes(writer); + + writer.WriteAttributeString("ComponentId", searchTuple.Guid); + + if (!String.IsNullOrEmpty(searchTuple.ProductCode)) + { + writer.WriteAttributeString("ProductCode", searchTuple.ProductCode); + } + + if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.KeyPath)) + { + writer.WriteAttributeString("Type", "keyPath"); + } + else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.WantDirectory)) + { + writer.WriteAttributeString("Type", "directory"); + } + + writer.WriteEndElement(); + } + + private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchTuple searchTuple) + { + writer.WriteStartElement((0 == (searchTuple.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); + + this.WriteCommonAttributes(writer); + + writer.WriteAttributeString("Path", searchTuple.Path); + if (WixFileSearchAttributes.WantExists == (searchTuple.Attributes & WixFileSearchAttributes.WantExists)) + { + writer.WriteAttributeString("Type", "exists"); + } + else if (WixFileSearchAttributes.WantVersion == (searchTuple.Attributes & WixFileSearchAttributes.WantVersion)) + { + // Can never get here for DirectorySearch. + writer.WriteAttributeString("Type", "version"); + } + else + { + writer.WriteAttributeString("Type", "path"); + } + writer.WriteEndElement(); + } + + private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchTuple tuple) + { + writer.WriteStartElement("MsiProductSearch"); + + this.WriteCommonAttributes(writer); + + if (0 != (tuple.Attributes & WixProductSearchAttributes.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", tuple.Guid); + } + else + { + writer.WriteAttributeString("ProductCode", tuple.Guid); + } + + if (0 != (tuple.Attributes & WixProductSearchAttributes.Version)) + { + writer.WriteAttributeString("Type", "version"); + } + else if (0 != (tuple.Attributes & WixProductSearchAttributes.Language)) + { + writer.WriteAttributeString("Type", "language"); + } + else if (0 != (tuple.Attributes & WixProductSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (tuple.Attributes & WixProductSearchAttributes.Assignment)) + { + writer.WriteAttributeString("Type", "assignment"); + } + + writer.WriteEndElement(); + } + + private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchTuple tuple) + { + writer.WriteStartElement("RegistrySearch"); + + this.WriteCommonAttributes(writer); + + switch (tuple.Root) + { + case RegistryRootType.ClassesRoot: + writer.WriteAttributeString("Root", "HKCR"); + break; + case RegistryRootType.CurrentUser: + writer.WriteAttributeString("Root", "HKCU"); + break; + case RegistryRootType.LocalMachine: + writer.WriteAttributeString("Root", "HKLM"); + break; + case RegistryRootType.Users: + writer.WriteAttributeString("Root", "HKU"); + break; + } + + writer.WriteAttributeString("Key", tuple.Key); + + if (!String.IsNullOrEmpty(tuple.Value)) + { + writer.WriteAttributeString("Value", tuple.Value); + } + + var existenceOnly = 0 != (tuple.Attributes & WixRegistrySearchAttributes.WantExists); + + writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); + + if (0 != (tuple.Attributes & WixRegistrySearchAttributes.Win64)) + { + writer.WriteAttributeString("Win64", "yes"); + } + + if (!existenceOnly) + { + if (0 != (tuple.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) + { + writer.WriteAttributeString("ExpandEnvironment", "yes"); + } + + // We *always* say this is VariableType="string". If we end up + // needing to be more specific, we will have to expand the "Format" + // attribute to allow "number" and "version". + + writer.WriteAttributeString("VariableType", "string"); + } + + writer.WriteEndElement(); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs deleted file mode 100644 index b9c29df0..00000000 --- a/src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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.Burn -{ - using System; - using System.Xml; - using WixToolset.Data; - - /// - /// Utility class for all WixComponentSearches. - /// - internal class WixComponentSearchInfo : WixSearchInfo - { -#if TODO - public WixComponentSearchInfo(Row row) - : this((string)row[0], (string)row[1], (string)row[2], (int)row[3]) - { - } -#endif - - public WixComponentSearchInfo(string id, string guid, string productCode, int attributes) - : base(id) - { - this.Guid = guid; - this.ProductCode = productCode; - this.Attributes = (WixComponentSearchAttributes)attributes; - } - - public string Guid { get; private set; } - public string ProductCode { get; private set; } - public WixComponentSearchAttributes Attributes { get; private set; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup for a component search. - /// - /// - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("MsiComponentSearch"); - this.WriteWixSearchAttributes(writer); - - writer.WriteAttributeString("ComponentId", this.Guid); - - if (!String.IsNullOrEmpty(this.ProductCode)) - { - writer.WriteAttributeString("ProductCode", this.ProductCode); - } - - if (0 != (this.Attributes & WixComponentSearchAttributes.KeyPath)) - { - writer.WriteAttributeString("Type", "keyPath"); - } - else if (0 != (this.Attributes & WixComponentSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (this.Attributes & WixComponentSearchAttributes.WantDirectory)) - { - writer.WriteAttributeString("Type", "directory"); - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs deleted file mode 100644 index 41393f6b..00000000 --- a/src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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.Burn -{ - using System; - using System.Xml; - using WixToolset.Data; - - /// - /// Utility class for all WixFileSearches (file and directory searches). - /// - internal class WixFileSearchInfo : WixSearchInfo - { -#if TODO - public WixFileSearchInfo(Row row) - : this((string)row[0], (string)row[1], (int)row[9]) - { - } -#endif - - public WixFileSearchInfo(string id, string path, int attributes) - : base(id) - { - this.Path = path; - this.Attributes = (WixFileSearchAttributes)attributes; - } - - public string Path { get; private set; } - public WixFileSearchAttributes Attributes { get; private set; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup for a file/directory search. - /// - /// - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement((0 == (this.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); - this.WriteWixSearchAttributes(writer); - writer.WriteAttributeString("Path", this.Path); - if (WixFileSearchAttributes.WantExists == (this.Attributes & WixFileSearchAttributes.WantExists)) - { - writer.WriteAttributeString("Type", "exists"); - } - else if (WixFileSearchAttributes.WantVersion == (this.Attributes & WixFileSearchAttributes.WantVersion)) - { - // Can never get here for DirectorySearch. - writer.WriteAttributeString("Type", "version"); - } - else - { - writer.WriteAttributeString("Type", "path"); - } - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs deleted file mode 100644 index cd4a70b3..00000000 --- a/src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs +++ /dev/null @@ -1,69 +0,0 @@ -// 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.Burn -{ - using System; - using System.Xml; - using WixToolset.Data; - - /// - /// Utility class for all WixProductSearches. - /// - internal class WixProductSearchInfo : WixSearchInfo - { -#if TODO - public WixProductSearchInfo(Row row) - : this((string)row[0], (string)row[1], (int)row[2]) - { - } -#endif - - public WixProductSearchInfo(string id, string guid, int attributes) - : base(id) - { - this.Guid = guid; - this.Attributes = (WixProductSearchAttributes)attributes; - } - - public string Guid { get; private set; } - public WixProductSearchAttributes Attributes { get; private set; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup for a product search. - /// - /// - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("MsiProductSearch"); - this.WriteWixSearchAttributes(writer); - - if (0 != (this.Attributes & WixProductSearchAttributes.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", this.Guid); - } - else - { - writer.WriteAttributeString("ProductCode", this.Guid); - } - - if (0 != (this.Attributes & WixProductSearchAttributes.Version)) - { - writer.WriteAttributeString("Type", "version"); - } - else if (0 != (this.Attributes & WixProductSearchAttributes.Language)) - { - writer.WriteAttributeString("Type", "language"); - } - else if (0 != (this.Attributes & WixProductSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (this.Attributes & WixProductSearchAttributes.Assignment)) - { - writer.WriteAttributeString("Type", "assignment"); - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs deleted file mode 100644 index 3f85b996..00000000 --- a/src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs +++ /dev/null @@ -1,93 +0,0 @@ -// 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.Burn -{ - using System; - using System.Xml; - using WixToolset.Data.WindowsInstaller; - - /// - /// Utility class for all WixRegistrySearches. - /// - internal class WixRegistrySearchInfo : WixSearchInfo - { -#if TODO - public WixRegistrySearchInfo(Row row) - : this((string)row[0], (int)row[1], (string)row[2], (string)row[3], (int)row[4]) - { - } -#endif - - public WixRegistrySearchInfo(string id, int root, string key, string value, int attributes) - : base(id) - { - this.Root = root; - this.Key = key; - this.Value = value; - this.Attributes = (WixRegistrySearchAttributes)attributes; - } - - public int Root { get; private set; } - public string Key { get; private set; } - public string Value { get; private set; } - public WixRegistrySearchAttributes Attributes { get; private set; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup for a registry search. - /// - /// - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("RegistrySearch"); - this.WriteWixSearchAttributes(writer); - - switch (this.Root) - { - case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: - writer.WriteAttributeString("Root", "HKCR"); - break; - case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: - writer.WriteAttributeString("Root", "HKCU"); - break; - case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: - writer.WriteAttributeString("Root", "HKLM"); - break; - case WindowsInstallerConstants.MsidbRegistryRootUsers: - writer.WriteAttributeString("Root", "HKU"); - break; - } - - writer.WriteAttributeString("Key", this.Key); - - if (!String.IsNullOrEmpty(this.Value)) - { - writer.WriteAttributeString("Value", this.Value); - } - - bool existenceOnly = 0 != (this.Attributes & WixRegistrySearchAttributes.WantExists); - - writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); - - if (0 != (this.Attributes & WixRegistrySearchAttributes.Win64)) - { - writer.WriteAttributeString("Win64", "yes"); - } - - if (!existenceOnly) - { - if (0 != (this.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) - { - writer.WriteAttributeString("ExpandEnvironment", "yes"); - } - - // We *always* say this is VariableType="string". If we end up - // needing to be more specific, we will have to expand the "Format" - // attribute to allow "number" and "version". - - writer.WriteAttributeString("VariableType", "string"); - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs deleted file mode 100644 index 04347583..00000000 --- a/src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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.Burn -{ - using System; - using System.Diagnostics; - using System.Xml; - using WixToolset.Data; - - /// - /// Utility base class for all WixSearches. - /// - internal abstract class WixSearchInfo - { - public WixSearchInfo(string id) - { - this.Id = id; - } - -#if TODO - public void AddWixSearchRowInfo(Row row) - { - Debug.Assert((string)row[0] == Id); - Variable = (string)row[1]; - Condition = (string)row[2]; - } -#endif - - public string Id { get; private set; } - public string Variable { get; private set; } - public string Condition { get; private set; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup a search. - /// - /// - public virtual void WriteXml(XmlTextWriter writer) - { - } - - /// - /// Writes attributes common to all WixSearch elements. - /// - /// - protected void WriteWixSearchAttributes(XmlTextWriter writer) - { - writer.WriteAttributeString("Id", this.Id); - writer.WriteAttributeString("Variable", this.Variable); - if (!String.IsNullOrEmpty(this.Condition)) - { - writer.WriteAttributeString("Condition", this.Condition); - } - } - } -} diff --git a/src/WixToolset.Core.Burn/BundleBackend.cs b/src/WixToolset.Core.Burn/BundleBackend.cs index f859cbec..99442403 100644 --- a/src/WixToolset.Core.Burn/BundleBackend.cs +++ b/src/WixToolset.Core.Burn/BundleBackend.cs @@ -15,20 +15,26 @@ namespace WixToolset.Core.Burn { public IBindResult Bind(IBindContext context) { - BindBundleCommand command = new BindBundleCommand(context); - //command.DefaultCompressionLevel = context.DefaultCompressionLevel; - //command.Extensions = context.Extensions; - //command.IntermediateFolder = context.IntermediateFolder; - //command.Output = context.IntermediateRepresentation; - //command.OutputPath = context.OutputPath; - //command.PdbFile = context.OutputPdbPath; - //command.WixVariableResolver = context.WixVariableResolver; + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + + var command = new BindBundleCommand(context, backendExtensions); command.Execute(); var result = context.ServiceProvider.GetService(); result.FileTransfers = command.FileTransfers; result.TrackedFiles = command.TrackedFiles; + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } return result; } @@ -53,10 +59,10 @@ namespace WixToolset.Core.Burn public Intermediate Unbind(IUnbindContext context) { - string uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); - string acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); + var uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); + var acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); - using (BurnReader reader = BurnReader.Open(context.InputFilePath)) + using (var reader = BurnReader.Open(context.InputFilePath)) { reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder); reader.ExtractAttachedContainer(acExtractPath, context.IntermediateFolder); diff --git a/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs index cf702b2e..b3a29e15 100644 --- a/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs @@ -7,107 +7,116 @@ namespace WixToolset.Core.Burn.Bundles using System.Diagnostics; using System.Linq; using WixToolset.Data; + using WixToolset.Data.Tuples; internal class AutomaticallySlipstreamPatchesCommand { -#if TODO - public IEnumerable PackageFacades { private get; set; } + public AutomaticallySlipstreamPatchesCommand(IntermediateSection section, ICollection packageFacades) + { + this.Section = section; + this.PackageFacades = packageFacades; + } - public Table WixBundlePatchTargetCodeTable { private get; set; } + private IntermediateSection Section { get; } - public Table SlipstreamMspTable { private get; set; } + private IEnumerable PackageFacades { get; } public void Execute() { - List msiPackages = new List(); - Dictionary> targetsProductCode = new Dictionary>(); - Dictionary> targetsUpgradeCode = new Dictionary>(); + var msiPackages = new List(); + var targetsProductCode = new Dictionary>(); + var targetsUpgradeCode = new Dictionary>(); - foreach (PackageFacade facade in this.PackageFacades) + foreach (var facade in this.PackageFacades) { - if (WixBundlePackageType.Msi == facade.Package.Type) + // Keep track of all MSI packages. + if (facade.SpecificPackageTuple is WixBundleMsiPackageTuple msiPackage) { - // Keep track of all MSI packages. - msiPackages.Add(facade.MsiPackage); + msiPackages.Add(msiPackage); } - else if (WixBundlePackageType.Msp == facade.Package.Type && facade.MspPackage.Slipstream) + else if (facade.SpecificPackageTuple is WixBundleMspPackageTuple mspPackage && mspPackage.Slipstream) { - IEnumerable patchTargetCodeRows = this.WixBundlePatchTargetCodeTable.RowsAs().Where(r => r.MspPackageId == facade.Package.WixChainItemId); + var patchTargetCodeTuples = this.Section.Tuples + .OfType() + .Where(r => r.PackageRef == facade.PackageId); // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. - foreach (WixBundlePatchTargetCodeRow row in patchTargetCodeRows) + foreach (var tuple in patchTargetCodeTuples) { - if (row.TargetsProductCode) + if (tuple.TargetsProductCode) { - List rows; - if (!targetsProductCode.TryGetValue(row.TargetCode, out rows)) + if (!targetsProductCode.TryGetValue(tuple.TargetCode, out var tuples)) { - rows = new List(); - targetsProductCode.Add(row.TargetCode, rows); + tuples = new List(); + targetsProductCode.Add(tuple.TargetCode, tuples); } - rows.Add(row); + tuples.Add(tuple); } - else if (row.TargetsUpgradeCode) + else if (tuple.TargetsUpgradeCode) { - List rows; - if (!targetsUpgradeCode.TryGetValue(row.TargetCode, out rows)) + if (!targetsUpgradeCode.TryGetValue(tuple.TargetCode, out var tuples)) { - rows = new List(); - targetsUpgradeCode.Add(row.TargetCode, rows); + tuples = new List(); + targetsUpgradeCode.Add(tuple.TargetCode, tuples); } } } } } - RowIndexedList slipstreamMspRows = new RowIndexedList(SlipstreamMspTable); + var slipstreamMspIds = new HashSet(); // Loop through the MSI and slipstream patches targeting it. - foreach (WixBundleMsiPackageRow msi in msiPackages) + foreach (var msi in msiPackages) { - List rows; - if (targetsProductCode.TryGetValue(msi.ProductCode, out rows)) + if (targetsProductCode.TryGetValue(msi.ProductCode, out var tuples)) { - foreach (WixBundlePatchTargetCodeRow row in rows) + foreach (var tuple in tuples) { - Debug.Assert(row.TargetsProductCode); - Debug.Assert(!row.TargetsUpgradeCode); - - Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false); - slipstreamMspRow[0] = msi.ChainPackageId; - slipstreamMspRow[1] = row.MspPackageId; + Debug.Assert(tuple.TargetsProductCode); + Debug.Assert(!tuple.TargetsUpgradeCode); - if (slipstreamMspRows.TryAdd(slipstreamMspRow)) - { - SlipstreamMspTable.Rows.Add(slipstreamMspRow); - } + this.TryAddSlipstreamTuple(slipstreamMspIds, msi, tuple); } - - rows = null; } - if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out rows)) + if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out tuples)) { - foreach (WixBundlePatchTargetCodeRow row in rows) + foreach (var tuple in tuples) { - Debug.Assert(!row.TargetsProductCode); - Debug.Assert(row.TargetsUpgradeCode); - - Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false); - slipstreamMspRow[0] = msi.ChainPackageId; - slipstreamMspRow[1] = row.MspPackageId; + Debug.Assert(!tuple.TargetsProductCode); + Debug.Assert(tuple.TargetsUpgradeCode); - if (slipstreamMspRows.TryAdd(slipstreamMspRow)) - { - SlipstreamMspTable.Rows.Add(slipstreamMspRow); - } + this.TryAddSlipstreamTuple(slipstreamMspIds, msi, tuple); } - rows = null; + tuples = null; } } } -#endif + + private bool TryAddSlipstreamTuple(HashSet slipstreamMspIds, WixBundleMsiPackageTuple msiPackage, WixBundlePatchTargetCodeTuple patchTargetCode) + { + var id = new Identifier(AccessModifier.Private, msiPackage.Id.Id, patchTargetCode.PackageRef); + + if (slipstreamMspIds.Add(id.Id)) + { + var slipstreamTuple = new WixBundleSlipstreamMspTuple(patchTargetCode.SourceLineNumbers) + { + TargetPackageRef = msiPackage.Id.Id, + MspPackageRef = patchTargetCode.PackageRef + }; + + //var slipstreamMspRow = SlipstreamMspTable.CreateRow(tuple.SourceLineNumbers, false); + //slipstreamMspRow[0] = msi.ChainPackageId; + //slipstreamMspRow[1] = tuple.MspPackageId; + + this.Section.Tuples.Add(slipstreamTuple); + return true; + } + + return false; + } } } diff --git a/src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs b/src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs new file mode 100644 index 00000000..3a71ed4c --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs @@ -0,0 +1,30 @@ +// 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.Burn.Bundles +{ + using System.IO; + using System.Security.Cryptography; + using System.Text; + + internal static class BundleHashAlgorithm + { + public static string Hash(FileInfo fileInfo) + { + byte[] hashBytes; + + using (var managed = new SHA1Managed()) + using (var stream = fileInfo.OpenRead()) + { + hashBytes = managed.ComputeHash(stream); + } + + var sb = new StringBuilder(hashBytes.Length * 2); + for (var i = 0; i < hashBytes.Length; i++) + { + sb.AppendFormat("{0:X2}", hashBytes[i]); + } + + return sb.ToString(); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs index df328eb6..78b95bf4 100644 --- a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs +++ b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs @@ -128,7 +128,7 @@ namespace WixToolset.Core.Burn.Bundles public void Dispose() { - Dispose(true); + this.Dispose(true); GC.SuppressFinalize(this); } @@ -238,7 +238,7 @@ namespace WixToolset.Core.Burn.Bundles { if (UInt32.MaxValue == this.wixburnDataOffset) { - if (!EnsureNTHeader(reader)) + if (!this.EnsureNTHeader(reader)) { return false; } @@ -286,7 +286,7 @@ namespace WixToolset.Core.Burn.Bundles { if (UInt32.MaxValue == this.firstSectionOffset) { - if (!EnsureDosHeader(reader)) + if (!this.EnsureDosHeader(reader)) { return false; } diff --git a/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs b/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs index 08eeaa15..83b73a61 100644 --- a/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs +++ b/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs @@ -67,19 +67,21 @@ namespace WixToolset.Core.Burn.Bundles /// Size of the stub engine "burn.exe". /// Unique identifier for this bundle. /// - public bool InitializeBundleSectionData(long stubSize, Guid bundleId) + public bool InitializeBundleSectionData(long stubSize, string bundleId) { if (this.invalidBundle) { return false; } + var bundleGuid = Guid.Parse(bundleId); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); - this.messaging.Write(VerboseMessages.BundleGuid(bundleId.ToString("B"))); + this.messaging.Write(VerboseMessages.BundleGuid(bundleId)); this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); - this.binaryWriter.Write(bundleId.ToByteArray()); + this.binaryWriter.Write(bundleGuid.ToByteArray()); this.StubSize = (uint)stubSize; @@ -146,7 +148,7 @@ namespace WixToolset.Core.Burn.Bundles return false; } - return AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount); + return this.AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount); } public void RememberThenResetSignature() diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs index 4fe8688c..5cd1f7e8 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -7,236 +7,268 @@ namespace WixToolset.Core.Burn.Bundles using System.Diagnostics; using System.Globalization; using System.IO; + using System.Linq; using System.Text; using System.Xml; using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Tuples; internal class CreateBootstrapperApplicationManifestCommand { -#if TODO - public WixBundleRow BundleRow { private get; set; } - - public IEnumerable ChainPackages { private get; set; } + public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable chainPackages, int lastUXPayloadIndex, Dictionary payloadTuples, string intermediateFolder) + { + this.Section = section; + this.BundleTuple = bundleTuple; + this.ChainPackages = chainPackages; + this.LastUXPayloadIndex = lastUXPayloadIndex; + this.Payloads = payloadTuples; + this.IntermediateFolder = intermediateFolder; + } - public int LastUXPayloadIndex { private get; set; } + private IntermediateSection Section { get; } - public IEnumerable MsiFeatures { private get; set; } + private WixBundleTuple BundleTuple { get; } - public Output Output { private get; set; } + private IEnumerable ChainPackages { get; } - public RowDictionary Payloads { private get; set; } + private int LastUXPayloadIndex { get; } - public TableDefinitionCollection TableDefinitions { private get; set; } + private Dictionary Payloads { get; } - public string TempFilesLocation { private get; set; } + private string IntermediateFolder { get; } - public WixBundlePayloadRow BootstrapperApplicationManifestPayloadRow { get; private set; } + public WixBundlePayloadTuple BootstrapperApplicationManifestPayloadRow { get; private set; } public void Execute() { - this.GenerateBAManifestBundleTables(); + var baManifestPath = this.CreateBootstrapperApplicationManifest(); - this.GenerateBAManifestMsiFeatureTables(); + this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(baManifestPath); + } - this.GenerateBAManifestPackageTables(); + private string CreateBootstrapperApplicationManifest() + { + var path = Path.Combine(this.IntermediateFolder, "wix-badata.xml"); - this.GenerateBAManifestPayloadTables(); + Directory.CreateDirectory(Path.GetDirectoryName(path)); - string baManifestPath = Path.Combine(this.TempFilesLocation, "wix-badata.xml"); + using (var writer = new XmlTextWriter(path, Encoding.Unicode)) + { + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument(); + writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"); - this.CreateBootstrapperApplicationManifest(baManifestPath); + this.WriteBundleInfo(writer); - this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(baManifestPath); + this.WritePackageInfo(writer); + + this.WriteFeatureInfo(writer); + + this.WritePayloadInfo(writer); + + this.WriteCustomBootstrapperApplicationData(writer); + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + + return path; } - private void GenerateBAManifestBundleTables() + private void WriteBundleInfo(XmlTextWriter writer) { - Table wixBundlePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleProperties"]); - - Row row = wixBundlePropertiesTable.CreateRow(this.BundleRow.SourceLineNumbers); - row[0] = this.BundleRow.Name; - row[1] = this.BundleRow.LogPathVariable; - row[2] = (YesNoDefaultType.Yes == this.BundleRow.Compressed) ? "yes" : "no"; - row[3] = this.BundleRow.BundleId.ToString("B"); - row[4] = this.BundleRow.UpgradeCode; - row[5] = this.BundleRow.PerMachine ? "yes" : "no"; + writer.WriteStartElement("WixBundleProperties"); + + writer.WriteAttributeString("DisplayName", this.BundleTuple.Name); + writer.WriteAttributeString("LogPathVariable", this.BundleTuple.LogPathVariable); + writer.WriteAttributeString("Compressed", this.BundleTuple.Compressed == true ? "yes" : "no"); + writer.WriteAttributeString("BundleId", this.BundleTuple.BundleId.ToUpperInvariant()); + writer.WriteAttributeString("UpgradeCode", this.BundleTuple.UpgradeCode); + writer.WriteAttributeString("PerMachine", this.BundleTuple.PerMachine ? "yes" : "no"); + + writer.WriteEndElement(); } - private void GenerateBAManifestPackageTables() + private void WritePackageInfo(XmlTextWriter writer) { - Table wixPackagePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageProperties"]); - - foreach (PackageFacade package in this.ChainPackages) + foreach (var package in this.ChainPackages) { - WixBundlePayloadRow packagePayload = this.Payloads[package.Package.PackagePayload]; - - Row row = wixPackagePropertiesTable.CreateRow(package.Package.SourceLineNumbers); - row[0] = package.Package.WixChainItemId; - row[1] = (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"; - row[2] = package.Package.DisplayName; - row[3] = package.Package.Description; - row[4] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // TODO: DownloadSize (compressed) (what does this mean when it's embedded?) - row[5] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // Package.Size (uncompressed) - row[6] = package.Package.InstallSize.Value.ToString(CultureInfo.InvariantCulture); // InstallSize (required disk space) - row[7] = package.Package.Type.ToString(); - row[8] = package.Package.Permanent ? "yes" : "no"; - row[9] = package.Package.LogPathVariable; - row[10] = package.Package.RollbackLogPathVariable; - row[11] = (PackagingType.Embedded == packagePayload.Packaging) ? "yes" : "no"; - - if (WixBundlePackageType.Msi == package.Package.Type) + var packagePayload = this.Payloads[package.PackageTuple.PayloadRef]; + + var size = package.PackageTuple.Size.ToString(CultureInfo.InvariantCulture); + + writer.WriteStartElement("WixBundleProperties"); + + writer.WriteAttributeString("Package", package.PackageId); + writer.WriteAttributeString("Vital", package.PackageTuple.Vital == true ? "yes" : "no"); + writer.WriteAttributeString("DisplayName", package.PackageTuple.DisplayName); + writer.WriteAttributeString("Description", package.PackageTuple.Description); + writer.WriteAttributeString("DownloadSize", size); + writer.WriteAttributeString("PackageSize", size); + writer.WriteAttributeString("InstalledSize", package.PackageTuple.InstallSize?.ToString(CultureInfo.InvariantCulture) ?? size); + writer.WriteAttributeString("PackageType", package.PackageTuple.Type.ToString()); + writer.WriteAttributeString("Permanent", package.PackageTuple.Permanent ? "yes" : "no"); + writer.WriteAttributeString("LogPathVariable", package.PackageTuple.LogPathVariable); + writer.WriteAttributeString("RollbackLogPathVariable", package.PackageTuple.RollbackLogPathVariable); + writer.WriteAttributeString("Compressed", packagePayload.Compressed == true ? "yes" : "no"); + + if (package.SpecificPackageTuple is WixBundleMsiPackageTuple msiPackage) { - row[12] = package.MsiPackage.DisplayInternalUI ? "yes" : "no"; + writer.WriteAttributeString("DisplayInternalUI", msiPackage.DisplayInternalUI ? "yes" : "no"); - if (!String.IsNullOrEmpty(package.MsiPackage.ProductCode)) + if (!String.IsNullOrEmpty(msiPackage.ProductCode)) { - row[13] = package.MsiPackage.ProductCode; + writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); } - if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) + if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) { - row[14] = package.MsiPackage.UpgradeCode; + writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); } } - else if (WixBundlePackageType.Msp == package.Package.Type) + else if (package.SpecificPackageTuple is WixBundleMspPackageTuple mspPackage) { - row[12] = package.MspPackage.DisplayInternalUI ? "yes" : "no"; + writer.WriteAttributeString("DisplayInternalUI", mspPackage.DisplayInternalUI ? "yes" : "no"); - if (!String.IsNullOrEmpty(package.MspPackage.PatchCode)) + if (!String.IsNullOrEmpty(mspPackage.PatchCode)) { - row[13] = package.MspPackage.PatchCode; + writer.WriteAttributeString("ProductCode", mspPackage.PatchCode); } } - if (!String.IsNullOrEmpty(package.Package.Version)) + if (!String.IsNullOrEmpty(package.PackageTuple.Version)) { - row[15] = package.Package.Version; + writer.WriteAttributeString("Version", package.PackageTuple.Version); } - if (!String.IsNullOrEmpty(package.Package.InstallCondition)) + if (!String.IsNullOrEmpty(package.PackageTuple.InstallCondition)) { - row[16] = package.Package.InstallCondition; + writer.WriteAttributeString("InstallCondition", package.PackageTuple.InstallCondition); } - switch (package.Package.Cache) + switch (package.PackageTuple.Cache) { case YesNoAlwaysType.No: - row[17] = "no"; + writer.WriteAttributeString("Cache", "no"); break; case YesNoAlwaysType.Yes: - row[17] = "yes"; + writer.WriteAttributeString("Cache", "yes"); break; case YesNoAlwaysType.Always: - row[17] = "always"; + writer.WriteAttributeString("Cache", "always"); break; } + + writer.WriteEndElement(); } } - private void GenerateBAManifestMsiFeatureTables() + private void WriteFeatureInfo(XmlTextWriter writer) { - Table wixPackageFeatureInfoTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageFeatureInfo"]); + var featureTuples = this.Section.Tuples.OfType(); - foreach (WixBundleMsiFeatureRow feature in this.MsiFeatures) + foreach (var featureTuple in featureTuples) { - Row row = wixPackageFeatureInfoTable.CreateRow(feature.SourceLineNumbers); - row[0] = feature.ChainPackageId; - row[1] = feature.Name; - row[2] = Convert.ToString(feature.Size, CultureInfo.InvariantCulture); - row[3] = feature.Parent; - row[4] = feature.Title; - row[5] = feature.Description; - row[6] = Convert.ToString(feature.Display, CultureInfo.InvariantCulture); - row[7] = Convert.ToString(feature.Level, CultureInfo.InvariantCulture); - row[8] = feature.Directory; - row[9] = Convert.ToString(feature.Attributes, CultureInfo.InvariantCulture); - } + writer.WriteStartElement("WixPackageFeatureInfo"); + + writer.WriteAttributeString("Package", featureTuple.PackageRef); + writer.WriteAttributeString("Feature", featureTuple.Name); + writer.WriteAttributeString("Size", featureTuple.Size.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Parent", featureTuple.Parent); + writer.WriteAttributeString("Title", featureTuple.Title); + writer.WriteAttributeString("Description", featureTuple.Description); + writer.WriteAttributeString("Display", featureTuple.Display.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Level", featureTuple.Level.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Directory", featureTuple.Directory); + writer.WriteAttributeString("Attributes", featureTuple.Attributes.ToString(CultureInfo.InvariantCulture)); + writer.WriteEndElement(); + } } - private void GenerateBAManifestPayloadTables() + private void WritePayloadInfo(XmlTextWriter writer) { - Table wixPayloadPropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPayloadProperties"]); + var payloadTuples = this.Section.Tuples.OfType(); - foreach (WixBundlePayloadRow payload in this.Payloads.Values) + foreach (var payloadTuple in payloadTuples) { - WixPayloadPropertiesRow row = (WixPayloadPropertiesRow)wixPayloadPropertiesTable.CreateRow(payload.SourceLineNumbers); - row.Id = payload.Id; - row.Package = payload.Package; - row.Container = payload.Container; - row.Name = payload.Name; - row.Size = payload.FileSize.ToString(); - row.DownloadUrl = payload.DownloadUrl; - row.LayoutOnly = payload.LayoutOnly ? "yes" : "no"; + writer.WriteStartElement("WixPackageFeatureInfo"); + + writer.WriteAttributeString("Id", payloadTuple.Id.Id); + writer.WriteAttributeString("Package", payloadTuple.PackageRef); + writer.WriteAttributeString("Container", payloadTuple.ContainerRef); + writer.WriteAttributeString("Name", payloadTuple.Name); + writer.WriteAttributeString("Size", payloadTuple.FileSize.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("DownloadUrl", payloadTuple.DownloadUrl); + writer.WriteAttributeString("LayoutOnly", payloadTuple.LayoutOnly ? "yes" : "no"); + + writer.WriteEndElement(); } } - private void CreateBootstrapperApplicationManifest(string path) + private void WriteCustomBootstrapperApplicationData(XmlTextWriter writer) { - using (XmlTextWriter writer = new XmlTextWriter(path, Encoding.Unicode)) + var dataTuplesGroupedByDefinitionName = this.Section.Tuples + .Where(t => t.Definition.HasTag(BurnConstants.BootstrapperApplicationDataTupleDefinitionTag)) + .GroupBy(t => t.Definition); + + foreach (var group in dataTuplesGroupedByDefinitionName) { - writer.Formatting = Formatting.Indented; - writer.WriteStartDocument(); - writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/2010/BootstrapperApplicationData"); + var definition = group.Key; - foreach (Table table in this.Output.Tables) - { - if (table.Definition.BootstrapperApplicationData) - { - // We simply assert that the table (and field) name is valid, because - // this is up to the extension developer to get right. An author will - // only affect the attribute value, and that will get properly escaped. + // We simply assert that the table (and field) name is valid, because + // this is up to the extension developer to get right. An author will + // only affect the attribute value, and that will get properly escaped. #if DEBUG - Debug.Assert(Common.IsIdentifier(table.Name)); - foreach (ColumnDefinition column in table.Definition.Columns) - { - Debug.Assert(Common.IsIdentifier(column.Name)); - } + Debug.Assert(Common.IsIdentifier(definition.Name)); + foreach (var fieldDef in definition.FieldDefinitions) + { + Debug.Assert(Common.IsIdentifier(fieldDef.Name)); + } #endif // DEBUG - foreach (Row row in table.Rows) - { - writer.WriteStartElement(table.Name); - - foreach (Field field in row.Fields) - { - if (null != field.Data) - { - writer.WriteAttributeString(field.Column.Name, field.Data.ToString()); - } - } + foreach (var row in group) + { + writer.WriteStartElement(definition.Name); - writer.WriteEndElement(); + foreach (var field in row.Fields) + { + if (!field.IsNull()) + { + writer.WriteAttributeString(field.Definition.Name, field.AsString()); } } - } - writer.WriteEndElement(); - writer.WriteEndDocument(); + writer.WriteEndElement(); + } } } - private WixBundlePayloadRow CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) + private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) { - Table payloadTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePayload"]); - WixBundlePayloadRow row = (WixBundlePayloadRow)payloadTable.CreateRow(this.BundleRow.SourceLineNumbers); - row.Id = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); - row.Name = "BootstrapperApplicationData.xml"; - row.SourceFile = baManifestPath; - row.Compressed = YesNoDefaultType.Yes; - row.UnresolvedSourceFile = baManifestPath; - row.Container = Compiler.BurnUXContainerId; - row.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex); - row.Packaging = PackagingType.Embedded; + var generatedId = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); + + var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) + { + Name = "BootstrapperApplicationData.xml", + SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, + Compressed = true, + UnresolvedSourceFile = baManifestPath, + ContainerRef = BurnConstants.BurnUXContainerName, + EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), + Packaging = PackagingType.Embedded, + }; + + var fileInfo = new FileInfo(baManifestPath); - FileInfo fileInfo = new FileInfo(row.SourceFile); + tuple.FileSize = (int)fileInfo.Length; - row.FileSize = (int)fileInfo.Length; + tuple.Hash = BundleHashAlgorithm.Hash(fileInfo); - row.Hash = Common.GetFileHash(fileInfo.FullName); + this.Section.Tuples.Add(tuple); - return row; + return tuple; } -#endif } } diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs new file mode 100644 index 00000000..bf0473d2 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs @@ -0,0 +1,171 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Tuples; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CreateBundleExeCommand + { + public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBundleTuple bundleTuple, WixBundleContainerTuple uxContainer, IEnumerable containers, string burnStubPath) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.IntermediateFolder = intermediateFolder; + this.OutputPath = outputPath; + this.BundleTuple = bundleTuple; + this.UXContainer = uxContainer; + this.Containers = containers; + this.BurnStubPath = burnStubPath; + } + + public IFileTransfer Transfer { get; private set; } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private string IntermediateFolder { get; } + + private string OutputPath { get; } + + private WixBundleTuple BundleTuple { get; } + + private WixBundleContainerTuple UXContainer { get; } + + private IEnumerable Containers { get; } + + private string BurnStubPath { get; } + + public void Execute() + { + var bundleFilename = Path.GetFileName(this.OutputPath); + + // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note + // that today, the x64 Burn uses the x86 stub. + + var stubFile = this.BurnStubPath; + + if (String.IsNullOrEmpty(stubFile)) + { + var stubPlatform = (Platform.X64 == this.BundleTuple.Platform) ? "x86" : this.BundleTuple.Platform.ToString(); + + stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); + } + + var bundleTempPath = Path.Combine(this.IntermediateFolder, bundleFilename); + + this.Messaging.Write(VerboseMessages.GeneratingBundle(bundleTempPath, stubFile)); + + if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.InsecureBundleFilename(bundleFilename)); + } + + this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleTuple.SourceLineNumbers); + + File.Copy(stubFile, bundleTempPath, true); + File.SetAttributes(bundleTempPath, FileAttributes.Normal); + + this.UpdateBurnResources(bundleTempPath, this.OutputPath, this.BundleTuple); + + // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers + // if they should be attached. + using (var writer = BurnWriter.Open(this.Messaging, bundleTempPath)) + { + var burnStubFile = new FileInfo(bundleTempPath); + writer.InitializeBundleSectionData(burnStubFile.Length, this.BundleTuple.BundleId); + + // Always attach the UX container first + writer.AppendContainer(this.UXContainer.WorkingPath, BurnWriter.Container.UX); + + // Now append all other attached containers + foreach (var container in this.Containers) + { + if (ContainerType.Attached == container.Type) + { + // The container was only created if it had payloads. + if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) + { + writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); + } + } + } + } + } + + private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleTuple bundleInfo) + { + var resources = new Dtf.Resources.ResourceCollection(); + var version = new Dtf.Resources.VersionResource("#1", 1033); + + version.Load(bundleTempPath); + resources.Add(version); + + // Ensure the bundle info provides a full four part version. + var fourPartVersion = new Version(bundleInfo.Version); + var major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; + var minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; + var build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; + var revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; + + if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) + { + throw new WixException(ErrorMessages.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version)); + } + + fourPartVersion = new Version(major, minor, build, revision); + version.FileVersion = fourPartVersion; + version.ProductVersion = fourPartVersion; + + var strings = version[1033]; + strings["LegalCopyright"] = bundleInfo.Copyright; + strings["OriginalFilename"] = Path.GetFileName(outputPath); + strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. + strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts. + + if (!String.IsNullOrEmpty(bundleInfo.Name)) + { + strings["ProductName"] = bundleInfo.Name; + strings["FileDescription"] = bundleInfo.Name; + } + + if (!String.IsNullOrEmpty(bundleInfo.Manufacturer)) + { + strings["CompanyName"] = bundleInfo.Manufacturer; + } + else + { + strings["CompanyName"] = String.Empty; + } + + if (!String.IsNullOrEmpty(bundleInfo.IconSourceFile)) + { + var iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033); + iconGroup.ReadFromFile(bundleInfo.IconSourceFile); + resources.Add(iconGroup); + + foreach (var icon in iconGroup.Icons) + { + resources.Add(icon); + } + } + + if (!String.IsNullOrEmpty(bundleInfo.SplashScreenSourceFile)) + { + var bitmap = new Dtf.Resources.BitmapResource("#1", 1033); + bitmap.ReadFromFile(bundleInfo.SplashScreenSourceFile); + resources.Add(bitmap); + } + + resources.Save(bundleTempPath); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 0ec8e46a..b7ea4116 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -6,76 +6,103 @@ namespace WixToolset.Core.Burn.Bundles using System.Collections.Generic; using System.Diagnostics; using System.Globalization; + using System.IO; using System.Linq; using System.Text; using System.Xml; using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Tuples; using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; internal class CreateBurnManifestCommand { -#if TODO - public IEnumerable BackendExtensions { private get; set; } + public CreateBurnManifestCommand(IMessaging messaging, IEnumerable backendExtensions, string executableName, IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable containers, WixChainTuple chainTuple, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, IEnumerable orderedSearches, IEnumerable catalogs, string intermediateFolder) + { + this.Messaging = messaging; + this.BackendExtensions = backendExtensions; + this.ExecutableName = executableName; + this.Section = section; + this.BundleTuple = bundleTuple; + this.Chain = chainTuple; + this.Containers = containers; + this.OrderedPackages = orderedPackages; + this.RollbackBoundaries = boundaries; + this.UXContainerPayloads = uxPayloads; + this.Payloads = allPayloadsById; + this.OrderedSearches = orderedSearches; + this.Catalogs = catalogs; + this.IntermediateFolder = intermediateFolder; + } + + public string OutputPath { get; private set; } + + private IMessaging Messaging { get; } - public Output Output { private get; set; } + private IEnumerable BackendExtensions { get; } - public string ExecutableName { private get; set; } + private string ExecutableName { get; } - public WixBundleRow BundleInfo { private get; set; } + private IntermediateSection Section { get; } - public WixChainRow Chain { private get; set; } + private WixBundleTuple BundleTuple { get; } - public string OutputPath { private get; set; } + private WixChainTuple Chain { get; } - public IEnumerable RollbackBoundaries { private get; set; } + private IEnumerable RollbackBoundaries { get; } - public IEnumerable OrderedPackages { private get; set; } + private IEnumerable OrderedPackages { get; } - public IEnumerable OrderedSearches { private get; set; } + private IEnumerable OrderedSearches { get; } - public Dictionary Payloads { private get; set; } + private Dictionary Payloads { get; } - public Dictionary Containers { private get; set; } + private IEnumerable Containers { get; } - public IEnumerable UXContainerPayloads { private get; set; } + private IEnumerable UXContainerPayloads { get; } - public IEnumerable Catalogs { private get; set; } + private IEnumerable Catalogs { get; } + + private string IntermediateFolder { get; } public void Execute() { - using (XmlTextWriter writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) + this.OutputPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml"); + + using (var writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) { writer.WriteStartDocument(); writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); // Write the condition, if there is one - if (null != this.BundleInfo.Condition) + if (null != this.BundleTuple.Condition) { - writer.WriteElementString("Condition", this.BundleInfo.Condition); + writer.WriteElementString("Condition", this.BundleTuple.Condition); } // Write the log element if default logging wasn't disabled. - if (!String.IsNullOrEmpty(this.BundleInfo.LogPrefix)) + if (!String.IsNullOrEmpty(this.BundleTuple.LogPrefix)) { writer.WriteStartElement("Log"); - if (!String.IsNullOrEmpty(this.BundleInfo.LogPathVariable)) + if (!String.IsNullOrEmpty(this.BundleTuple.LogPathVariable)) { - writer.WriteAttributeString("PathVariable", this.BundleInfo.LogPathVariable); + writer.WriteAttributeString("PathVariable", this.BundleTuple.LogPathVariable); } - writer.WriteAttributeString("Prefix", this.BundleInfo.LogPrefix); - writer.WriteAttributeString("Extension", this.BundleInfo.LogExtension); + writer.WriteAttributeString("Prefix", this.BundleTuple.LogPrefix); + writer.WriteAttributeString("Extension", this.BundleTuple.LogExtension); writer.WriteEndElement(); } // Get update if specified. - WixBundleUpdateRow updateRow = this.Output.Tables["WixBundleUpdate"].RowsAs().FirstOrDefault(); + var updateTuple = this.Section.Tuples.OfType().FirstOrDefault(); - if (null != updateRow) + if (null != updateTuple) { writer.WriteStartElement("Update"); - writer.WriteAttributeString("Location", updateRow.Location); + writer.WriteAttributeString("Location", updateTuple.Location); writer.WriteEndElement(); // } @@ -83,23 +110,27 @@ namespace WixToolset.Core.Burn.Bundles // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates // enumeration in the index row list is not used). - RowIndexedList relatedBundles = new RowIndexedList(this.Output.Tables["WixRelatedBundle"]); + var relatedBundles = this.Section.Tuples.OfType(); + var distinctRelatedBundles = new HashSet(); - foreach (WixRelatedBundleRow relatedBundle in relatedBundles) + foreach (var relatedBundle in relatedBundles) { - writer.WriteStartElement("RelatedBundle"); - writer.WriteAttributeString("Id", relatedBundle.Id); - writer.WriteAttributeString("Action", Convert.ToString(relatedBundle.Action, CultureInfo.InvariantCulture)); - writer.WriteEndElement(); + if (distinctRelatedBundles.Add(relatedBundle.BundleId)) + { + writer.WriteStartElement("RelatedBundle"); + writer.WriteAttributeString("Id", relatedBundle.BundleId); + writer.WriteAttributeString("Action", relatedBundle.Action.ToString()); + writer.WriteEndElement(); + } } // Write the variables - IEnumerable variables = this.Output.Tables["WixBundleVariable"].RowsAs(); + var variables = this.Section.Tuples.OfType(); - foreach (WixBundleVariableRow variable in variables) + foreach (var variable in variables) { writer.WriteStartElement("Variable"); - writer.WriteAttributeString("Id", variable.Id); + writer.WriteAttributeString("Id", variable.Id.Id); if (null != variable.Type) { writer.WriteAttributeString("Value", variable.Value); @@ -111,20 +142,20 @@ namespace WixToolset.Core.Burn.Bundles } // Write the searches - foreach (WixSearchInfo searchinfo in this.OrderedSearches) + foreach (var searchinfo in this.OrderedSearches) { searchinfo.WriteXml(writer); } // write the UX element writer.WriteStartElement("UX"); - if (!String.IsNullOrEmpty(this.BundleInfo.SplashScreenBitmapPath)) + if (!String.IsNullOrEmpty(this.BundleTuple.SplashScreenSourceFile)) { writer.WriteAttributeString("SplashScreen", "yes"); } // write the UX allPayloads... - foreach (WixBundlePayloadRow payload in this.UXContainerPayloads) + foreach (var payload in this.UXContainerPayloads) { writer.WriteStartElement("Payload"); this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); @@ -136,18 +167,18 @@ namespace WixToolset.Core.Burn.Bundles // write the catalog elements if (this.Catalogs.Any()) { - foreach (WixBundleCatalogRow catalog in this.Catalogs) + foreach (var catalog in this.Catalogs) { writer.WriteStartElement("Catalog"); - writer.WriteAttributeString("Id", catalog.Id); - writer.WriteAttributeString("Payload", catalog.Payload); + writer.WriteAttributeString("Id", catalog.Id.Id); + writer.WriteAttributeString("Payload", catalog.PayloadRef); writer.WriteEndElement(); } } - foreach (WixBundleContainerRow container in this.Containers.Values) + foreach (var container in this.Containers) { - if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) + if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) { writer.WriteStartElement("Container"); this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); @@ -155,9 +186,9 @@ namespace WixToolset.Core.Burn.Bundles } } - foreach (WixBundlePayloadRow payload in this.Payloads.Values) + foreach (var payload in this.Payloads.Values) { - if (PackagingType.Embedded == payload.Packaging && Compiler.BurnUXContainerId != payload.Container) + if (PackagingType.Embedded == payload.Packaging && BurnConstants.BurnUXContainerName != payload.ContainerRef) { writer.WriteStartElement("Payload"); this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); @@ -171,77 +202,78 @@ namespace WixToolset.Core.Burn.Bundles } } - foreach (WixBundleRollbackBoundaryRow rollbackBoundary in this.RollbackBoundaries) + foreach (var rollbackBoundary in this.RollbackBoundaries) { writer.WriteStartElement("RollbackBoundary"); - writer.WriteAttributeString("Id", rollbackBoundary.ChainPackageId); - writer.WriteAttributeString("Vital", YesNoType.Yes == rollbackBoundary.Vital ? "yes" : "no"); - writer.WriteAttributeString("Transaction", YesNoType.Yes == rollbackBoundary.Transaction ? "yes" : "no"); + writer.WriteAttributeString("Id", rollbackBoundary.Id.Id); + writer.WriteAttributeString("Vital", rollbackBoundary.Vital == false ? "no" : "yes"); + writer.WriteAttributeString("Transaction", rollbackBoundary.Transaction == true ? "yes" : "no"); writer.WriteEndElement(); } // Write the registration information... writer.WriteStartElement("Registration"); - writer.WriteAttributeString("Id", this.BundleInfo.BundleId.ToString("B")); + writer.WriteAttributeString("Id", this.BundleTuple.BundleId); writer.WriteAttributeString("ExecutableName", this.ExecutableName); - writer.WriteAttributeString("PerMachine", this.BundleInfo.PerMachine ? "yes" : "no"); - writer.WriteAttributeString("Tag", this.BundleInfo.Tag); - writer.WriteAttributeString("Version", this.BundleInfo.Version); - writer.WriteAttributeString("ProviderKey", this.BundleInfo.ProviderKey); + writer.WriteAttributeString("PerMachine", this.BundleTuple.PerMachine ? "yes" : "no"); + writer.WriteAttributeString("Tag", this.BundleTuple.Tag); + writer.WriteAttributeString("Version", this.BundleTuple.Version); + writer.WriteAttributeString("ProviderKey", this.BundleTuple.ProviderKey); writer.WriteStartElement("Arp"); - writer.WriteAttributeString("Register", (0 < this.BundleInfo.DisableModify && this.BundleInfo.DisableRemove) ? "no" : "yes"); // do not register if disabled modify and remove. - writer.WriteAttributeString("DisplayName", this.BundleInfo.Name); - writer.WriteAttributeString("DisplayVersion", this.BundleInfo.Version); + writer.WriteAttributeString("Register", (this.BundleTuple.DisableModify || this.BundleTuple.SingleChangeUninstallButton) && this.BundleTuple.DisableRemove ? "no" : "yes"); // do not register if disabled modify and remove. + writer.WriteAttributeString("DisplayName", this.BundleTuple.Name); + writer.WriteAttributeString("DisplayVersion", this.BundleTuple.Version); - if (!String.IsNullOrEmpty(this.BundleInfo.Publisher)) + if (!String.IsNullOrEmpty(this.BundleTuple.Manufacturer)) { - writer.WriteAttributeString("Publisher", this.BundleInfo.Publisher); + writer.WriteAttributeString("Publisher", this.BundleTuple.Manufacturer); } - if (!String.IsNullOrEmpty(this.BundleInfo.HelpLink)) + if (!String.IsNullOrEmpty(this.BundleTuple.HelpUrl)) { - writer.WriteAttributeString("HelpLink", this.BundleInfo.HelpLink); + writer.WriteAttributeString("HelpLink", this.BundleTuple.HelpUrl); } - if (!String.IsNullOrEmpty(this.BundleInfo.HelpTelephone)) + if (!String.IsNullOrEmpty(this.BundleTuple.HelpTelephone)) { - writer.WriteAttributeString("HelpTelephone", this.BundleInfo.HelpTelephone); + writer.WriteAttributeString("HelpTelephone", this.BundleTuple.HelpTelephone); } - if (!String.IsNullOrEmpty(this.BundleInfo.AboutUrl)) + if (!String.IsNullOrEmpty(this.BundleTuple.AboutUrl)) { - writer.WriteAttributeString("AboutUrl", this.BundleInfo.AboutUrl); + writer.WriteAttributeString("AboutUrl", this.BundleTuple.AboutUrl); } - if (!String.IsNullOrEmpty(this.BundleInfo.UpdateUrl)) + if (!String.IsNullOrEmpty(this.BundleTuple.UpdateUrl)) { - writer.WriteAttributeString("UpdateUrl", this.BundleInfo.UpdateUrl); + writer.WriteAttributeString("UpdateUrl", this.BundleTuple.UpdateUrl); } - if (!String.IsNullOrEmpty(this.BundleInfo.ParentName)) + if (!String.IsNullOrEmpty(this.BundleTuple.ParentName)) { - writer.WriteAttributeString("ParentDisplayName", this.BundleInfo.ParentName); + writer.WriteAttributeString("ParentDisplayName", this.BundleTuple.ParentName); } - if (1 == this.BundleInfo.DisableModify) + if (this.BundleTuple.DisableModify) { writer.WriteAttributeString("DisableModify", "yes"); } - else if (2 == this.BundleInfo.DisableModify) + + if (this.BundleTuple.DisableRemove) { - writer.WriteAttributeString("DisableModify", "button"); + writer.WriteAttributeString("DisableRemove", "yes"); } - if (this.BundleInfo.DisableRemove) + if (this.BundleTuple.SingleChangeUninstallButton) { - writer.WriteAttributeString("DisableRemove", "yes"); + writer.WriteAttributeString("DisableModify", "button"); } writer.WriteEndElement(); // // Get update registration if specified. - WixUpdateRegistrationRow updateRegistrationInfo = this.Output.Tables["WixUpdateRegistration"].RowsAs().FirstOrDefault(); + var updateRegistrationInfo = this.Section.Tuples.OfType().FirstOrDefault(); if (null != updateRegistrationInfo) { @@ -263,9 +295,9 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); // } - IEnumerable bundleTags = this.Output.Tables["WixBundleTag"].RowsAs(); - - foreach (Row row in bundleTags) +#if TODO // Handle SWID Tags + var bundleTags = this.Output.Tables["WixBundleTag"].RowsAs(); + foreach (var row in bundleTags) { writer.WriteStartElement("SoftwareTag"); writer.WriteAttributeString("Filename", (string)row[0]); @@ -273,6 +305,7 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteCData((string)row[4]); writer.WriteEndElement(); } +#endif writer.WriteEndElement(); // @@ -294,25 +327,28 @@ namespace WixToolset.Core.Burn.Bundles } // Index a few tables by package. - ILookup targetCodesByPatch = this.Output.Tables["WixBundlePatchTargetCode"].RowsAs().ToLookup(r => r.MspPackageId); - ILookup msiFeaturesByPackage = this.Output.Tables["WixBundleMsiFeature"].RowsAs().ToLookup(r => r.ChainPackageId); - ILookup msiPropertiesByPackage = this.Output.Tables["WixBundleMsiProperty"].RowsAs().ToLookup(r => r.ChainPackageId); - ILookup payloadsByPackage = this.Payloads.Values.ToLookup(p => p.Package); - ILookup relatedPackagesByPackage = this.Output.Tables["WixBundleRelatedPackage"].RowsAs().ToLookup(r => r.ChainPackageId); - ILookup slipstreamMspsByPackage = this.Output.Tables["WixBundleSlipstreamMsp"].RowsAs().ToLookup(r => r.ChainPackageId); - ILookup exitCodesByPackage = this.Output.Tables["WixBundlePackageExitCode"].RowsAs().ToLookup(r => r.ChainPackageId); - ILookup commandLinesByPackage = this.Output.Tables["WixBundlePackageCommandLine"].RowsAs().ToLookup(r => r.ChainPackageId); + var targetCodesByPatch = this.Section.Tuples.OfType().ToLookup(r => r.PackageRef); + var msiFeaturesByPackage = this.Section.Tuples.OfType().ToLookup(r => r.PackageRef); + var msiPropertiesByPackage = this.Section.Tuples.OfType().ToLookup(r => r.PackageRef); + var payloadsByPackage = this.Payloads.Values.ToLookup(p => p.PackageRef); + var relatedPackagesByPackage = this.Section.Tuples.OfType().ToLookup(r => r.PackageRef); + var slipstreamMspsByPackage = this.Section.Tuples.OfType().ToLookup(r => r.MspPackageRef); + var exitCodesByPackage = this.Section.Tuples.OfType().ToLookup(r => r.ChainPackageId); + var commandLinesByPackage = this.Section.Tuples.OfType().ToLookup(r => r.WixBundlePackageRef); + + var dependenciesByPackage = this.Section.Tuples.OfType().ToLookup(p => p.PackageRef); + // Build up the list of target codes from all the MSPs in the chain. - List targetCodes = new List(); + var targetCodes = new List(); - foreach (PackageFacade package in this.OrderedPackages) + foreach (var package in this.OrderedPackages) { - writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.Package.Type)); + writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.PackageTuple.Type)); - writer.WriteAttributeString("Id", package.Package.WixChainItemId); + writer.WriteAttributeString("Id", package.PackageId); - switch (package.Package.Cache) + switch (package.PackageTuple.Cache) { case YesNoAlwaysType.No: writer.WriteAttributeString("Cache", "no"); @@ -325,74 +361,74 @@ namespace WixToolset.Core.Burn.Bundles break; } - writer.WriteAttributeString("CacheId", package.Package.CacheId); - writer.WriteAttributeString("InstallSize", Convert.ToString(package.Package.InstallSize)); - writer.WriteAttributeString("Size", Convert.ToString(package.Package.Size)); - writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.Package.PerMachine ? "yes" : "no"); - writer.WriteAttributeString("Permanent", package.Package.Permanent ? "yes" : "no"); - writer.WriteAttributeString("Vital", (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"); + writer.WriteAttributeString("CacheId", package.PackageTuple.CacheId); + writer.WriteAttributeString("InstallSize", Convert.ToString(package.PackageTuple.InstallSize)); + writer.WriteAttributeString("Size", Convert.ToString(package.PackageTuple.Size)); + writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.PackageTuple.PerMachine ? "yes" : "no"); + writer.WriteAttributeString("Permanent", package.PackageTuple.Permanent ? "yes" : "no"); + writer.WriteAttributeString("Vital", package.PackageTuple.Vital == false ? "no" : "yes"); - if (null != package.Package.RollbackBoundary) + if (null != package.PackageTuple.RollbackBoundaryRef) { - writer.WriteAttributeString("RollbackBoundaryForward", package.Package.RollbackBoundary); + writer.WriteAttributeString("RollbackBoundaryForward", package.PackageTuple.RollbackBoundaryRef); } - if (!String.IsNullOrEmpty(package.Package.RollbackBoundaryBackward)) + if (!String.IsNullOrEmpty(package.PackageTuple.RollbackBoundaryBackwardRef)) { - writer.WriteAttributeString("RollbackBoundaryBackward", package.Package.RollbackBoundaryBackward); + writer.WriteAttributeString("RollbackBoundaryBackward", package.PackageTuple.RollbackBoundaryBackwardRef); } - if (!String.IsNullOrEmpty(package.Package.LogPathVariable)) + if (!String.IsNullOrEmpty(package.PackageTuple.LogPathVariable)) { - writer.WriteAttributeString("LogPathVariable", package.Package.LogPathVariable); + writer.WriteAttributeString("LogPathVariable", package.PackageTuple.LogPathVariable); } - if (!String.IsNullOrEmpty(package.Package.RollbackLogPathVariable)) + if (!String.IsNullOrEmpty(package.PackageTuple.RollbackLogPathVariable)) { - writer.WriteAttributeString("RollbackLogPathVariable", package.Package.RollbackLogPathVariable); + writer.WriteAttributeString("RollbackLogPathVariable", package.PackageTuple.RollbackLogPathVariable); } - if (!String.IsNullOrEmpty(package.Package.InstallCondition)) + if (!String.IsNullOrEmpty(package.PackageTuple.InstallCondition)) { - writer.WriteAttributeString("InstallCondition", package.Package.InstallCondition); + writer.WriteAttributeString("InstallCondition", package.PackageTuple.InstallCondition); } - if (WixBundlePackageType.Exe == package.Package.Type) + if (package.SpecificPackageTuple is WixBundleExePackageTuple exePackage) // EXE { - writer.WriteAttributeString("DetectCondition", package.ExePackage.DetectCondition); - writer.WriteAttributeString("InstallArguments", package.ExePackage.InstallCommand); - writer.WriteAttributeString("UninstallArguments", package.ExePackage.UninstallCommand); - writer.WriteAttributeString("RepairArguments", package.ExePackage.RepairCommand); - writer.WriteAttributeString("Repairable", package.ExePackage.Repairable ? "yes" : "no"); - if (!String.IsNullOrEmpty(package.ExePackage.ExeProtocol)) + writer.WriteAttributeString("DetectCondition", exePackage.DetectCondition); + writer.WriteAttributeString("InstallArguments", exePackage.InstallCommand); + writer.WriteAttributeString("UninstallArguments", exePackage.UninstallCommand); + writer.WriteAttributeString("RepairArguments", exePackage.RepairCommand); + writer.WriteAttributeString("Repairable", exePackage.Repairable ? "yes" : "no"); + if (!String.IsNullOrEmpty(exePackage.ExeProtocol)) { - writer.WriteAttributeString("Protocol", package.ExePackage.ExeProtocol); + writer.WriteAttributeString("Protocol", exePackage.ExeProtocol); } } - else if (WixBundlePackageType.Msi == package.Package.Type) + else if (package.SpecificPackageTuple is WixBundleMsiPackageTuple msiPackage) // MSI { - writer.WriteAttributeString("ProductCode", package.MsiPackage.ProductCode); - writer.WriteAttributeString("Language", package.MsiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Version", package.MsiPackage.ProductVersion); - writer.WriteAttributeString("DisplayInternalUI", package.MsiPackage.DisplayInternalUI ? "yes" : "no"); - if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) + writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); + writer.WriteAttributeString("Language", msiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Version", msiPackage.ProductVersion); + writer.WriteAttributeString("DisplayInternalUI", msiPackage.DisplayInternalUI ? "yes" : "no"); + if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) { - writer.WriteAttributeString("UpgradeCode", package.MsiPackage.UpgradeCode); + writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); } } - else if (WixBundlePackageType.Msp == package.Package.Type) + else if (package.SpecificPackageTuple is WixBundleMspPackageTuple mspPackage) // MSP { - writer.WriteAttributeString("PatchCode", package.MspPackage.PatchCode); - writer.WriteAttributeString("PatchXml", package.MspPackage.PatchXml); - writer.WriteAttributeString("DisplayInternalUI", package.MspPackage.DisplayInternalUI ? "yes" : "no"); + writer.WriteAttributeString("PatchCode", mspPackage.PatchCode); + writer.WriteAttributeString("PatchXml", mspPackage.PatchXml); + writer.WriteAttributeString("DisplayInternalUI", mspPackage.DisplayInternalUI ? "yes" : "no"); // If there is still a chance that all of our patches will target a narrow set of // product codes, add the patch list to the overall list. if (null != targetCodes) { - if (!package.MspPackage.TargetUnspecified) + if (!mspPackage.TargetUnspecified) { - IEnumerable patchTargetCodes = targetCodesByPatch[package.MspPackage.ChainPackageId]; + var patchTargetCodes = targetCodesByPatch[mspPackage.Id.Id]; targetCodes.AddRange(patchTargetCodes); } @@ -402,24 +438,24 @@ namespace WixToolset.Core.Burn.Bundles } } } - else if (WixBundlePackageType.Msu == package.Package.Type) + else if (package.SpecificPackageTuple is WixBundleMsuPackageTuple msuPackage) // MSU { - writer.WriteAttributeString("DetectCondition", package.MsuPackage.DetectCondition); - writer.WriteAttributeString("KB", package.MsuPackage.MsuKB); + writer.WriteAttributeString("DetectCondition", msuPackage.DetectCondition); + writer.WriteAttributeString("KB", msuPackage.MsuKB); } - IEnumerable packageMsiFeatures = msiFeaturesByPackage[package.Package.WixChainItemId]; + var packageMsiFeatures = msiFeaturesByPackage[package.PackageId]; - foreach (WixBundleMsiFeatureRow feature in packageMsiFeatures) + foreach (var feature in packageMsiFeatures) { writer.WriteStartElement("MsiFeature"); writer.WriteAttributeString("Id", feature.Name); writer.WriteEndElement(); } - IEnumerable packageMsiProperties = msiPropertiesByPackage[package.Package.WixChainItemId]; + var packageMsiProperties = msiPropertiesByPackage[package.PackageId]; - foreach (WixBundleMsiPropertyRow msiProperty in packageMsiProperties) + foreach (var msiProperty in packageMsiProperties) { writer.WriteStartElement("MsiProperty"); writer.WriteAttributeString("Id", msiProperty.Name); @@ -431,18 +467,18 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); } - IEnumerable packageSlipstreamMsps = slipstreamMspsByPackage[package.Package.WixChainItemId]; + var packageSlipstreamMsps = slipstreamMspsByPackage[package.PackageId]; - foreach (WixBundleSlipstreamMspRow slipstreamMsp in packageSlipstreamMsps) + foreach (var slipstreamMsp in packageSlipstreamMsps) { writer.WriteStartElement("SlipstreamMsp"); - writer.WriteAttributeString("Id", slipstreamMsp.MspPackageId); + writer.WriteAttributeString("Id", slipstreamMsp.MspPackageRef); writer.WriteEndElement(); } - IEnumerable packageExitCodes = exitCodesByPackage[package.Package.WixChainItemId]; + var packageExitCodes = exitCodesByPackage[package.PackageId]; - foreach (WixBundlePackageExitCodeRow exitCode in packageExitCodes) + foreach (var exitCode in packageExitCodes) { writer.WriteStartElement("ExitCode"); @@ -459,9 +495,9 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); } - IEnumerable packageCommandLines = commandLinesByPackage[package.Package.WixChainItemId]; + var packageCommandLines = commandLinesByPackage[package.PackageId]; - foreach (WixBundlePackageCommandLineRow commandLine in packageCommandLines) + foreach (var commandLine in packageCommandLines) { writer.WriteStartElement("CommandLine"); writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument); @@ -472,18 +508,38 @@ namespace WixToolset.Core.Burn.Bundles } // Output the dependency information. - foreach (ProvidesDependency dependency in package.Provides) + var dependencies = dependenciesByPackage[package.PackageId]; + + foreach (var dependency in dependencies) { - // TODO: Add to wixpdb as an imported table, or link package wixpdbs to bundle wixpdbs. - dependency.WriteXml(writer); + writer.WriteStartElement("Provides"); + writer.WriteAttributeString("Key", dependency.Key); + + if (!String.IsNullOrEmpty(dependency.Version)) + { + writer.WriteAttributeString("Version", dependency.Version); + } + + if (!String.IsNullOrEmpty(dependency.DisplayName)) + { + writer.WriteAttributeString("DisplayName", dependency.DisplayName); + } + + if (dependency.Imported) + { + // The package dependency was explicitly authored into the manifest. + writer.WriteAttributeString("Imported", "yes"); + } + + writer.WriteEndElement(); } - IEnumerable packageRelatedPackages = relatedPackagesByPackage[package.Package.WixChainItemId]; + var packageRelatedPackages = relatedPackagesByPackage[package.PackageId]; - foreach (WixBundleRelatedPackageRow related in packageRelatedPackages) + foreach (var related in packageRelatedPackages) { writer.WriteStartElement("RelatedPackage"); - writer.WriteAttributeString("Id", related.Id); + writer.WriteAttributeString("Id", related.RelatedId); if (!String.IsNullOrEmpty(related.MinVersion)) { writer.WriteAttributeString("MinVersion", related.MinVersion); @@ -496,7 +552,7 @@ namespace WixToolset.Core.Burn.Bundles } writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no"); - string[] relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (0 < relatedLanguages.Length) { @@ -513,17 +569,17 @@ namespace WixToolset.Core.Burn.Bundles // Write any contained Payloads with the PackagePayload being first writer.WriteStartElement("PayloadRef"); - writer.WriteAttributeString("Id", package.Package.PackagePayload); + writer.WriteAttributeString("Id", package.PackageTuple.PayloadRef); writer.WriteEndElement(); - IEnumerable packagePayloads = payloadsByPackage[package.Package.WixChainItemId]; + var packagePayloads = payloadsByPackage[package.PackageId]; - foreach (WixBundlePayloadRow payload in packagePayloads) + foreach (var payload in packagePayloads) { - if (payload.Id != package.Package.PackagePayload) + if (payload.Id.Id != package.PackageTuple.PayloadRef) { writer.WriteStartElement("PayloadRef"); - writer.WriteAttributeString("Id", payload.Id); + writer.WriteAttributeString("Id", payload.Id.Id); writer.WriteEndElement(); } } @@ -534,7 +590,7 @@ namespace WixToolset.Core.Burn.Bundles if (null != targetCodes) { - foreach (WixBundlePatchTargetCodeRow targetCode in targetCodes) + foreach (var targetCode in targetCodes) { writer.WriteStartElement("PatchTargetCode"); writer.WriteAttributeString("TargetCode", targetCode.TargetCode); @@ -544,12 +600,12 @@ namespace WixToolset.Core.Burn.Bundles } // Write the ApprovedExeForElevation elements. - IEnumerable approvedExesForElevation = this.Output.Tables["WixApprovedExeForElevation"].RowsAs(); + var approvedExesForElevation = this.Section.Tuples.OfType(); - foreach (WixApprovedExeForElevationRow approvedExeForElevation in approvedExesForElevation) + foreach (var approvedExeForElevation in approvedExesForElevation) { writer.WriteStartElement("ApprovedExeForElevation"); - writer.WriteAttributeString("Id", approvedExeForElevation.Id); + writer.WriteAttributeString("Id", approvedExeForElevation.Id.Id); writer.WriteAttributeString("Key", approvedExeForElevation.Key); if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName)) @@ -569,15 +625,15 @@ namespace WixToolset.Core.Burn.Bundles } } - private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerRow container) + private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerTuple container) { - writer.WriteAttributeString("Id", container.Id); + writer.WriteAttributeString("Id", container.Id.Id); writer.WriteAttributeString("FileSize", container.Size.ToString(CultureInfo.InvariantCulture)); writer.WriteAttributeString("Hash", container.Hash); if (ContainerType.Detached == container.Type) { - string resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id, container.Name); + string resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id.Id, container.Name); if (!String.IsNullOrEmpty(resolvedUrl)) { writer.WriteAttributeString("DownloadUrl", resolvedUrl); @@ -593,7 +649,7 @@ namespace WixToolset.Core.Burn.Bundles { if (!String.IsNullOrEmpty(container.DownloadUrl)) { - Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id)); + this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id.Id)); } writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. @@ -603,11 +659,11 @@ namespace WixToolset.Core.Burn.Bundles } } - private void WriteBurnManifestPayloadAttributes(XmlTextWriter writer, WixBundlePayloadRow payload, bool embeddedOnly, Dictionary allPayloads) + private void WriteBurnManifestPayloadAttributes(XmlTextWriter writer, WixBundlePayloadTuple payload, bool embeddedOnly, Dictionary allPayloads) { Debug.Assert(!embeddedOnly || PackagingType.Embedded == payload.Packaging); - writer.WriteAttributeString("Id", payload.Id); + writer.WriteAttributeString("Id", payload.Id.Id); writer.WriteAttributeString("FilePath", payload.Name); writer.WriteAttributeString("FileSize", payload.FileSize.ToString(CultureInfo.InvariantCulture)); writer.WriteAttributeString("Hash", payload.Hash); @@ -632,22 +688,22 @@ namespace WixToolset.Core.Burn.Bundles case PackagingType.Embedded: // this means it's in a container. if (!String.IsNullOrEmpty(payload.DownloadUrl)) { - Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForEmbeddedPayloads(payload.SourceLineNumbers, payload.Id)); + this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForEmbeddedPayloads(payload.SourceLineNumbers, payload.Id.Id)); } writer.WriteAttributeString("Packaging", "embedded"); writer.WriteAttributeString("SourcePath", payload.EmbeddedId); - if (Compiler.BurnUXContainerId != payload.Container) + if (BurnConstants.BurnUXContainerName != payload.ContainerRef) { - writer.WriteAttributeString("Container", payload.Container); + writer.WriteAttributeString("Container", payload.ContainerRef); } break; case PackagingType.External: - string packageId = payload.ParentPackagePayload; - string parentUrl = payload.ParentPackagePayload == null ? null : allPayloads[payload.ParentPackagePayload].DownloadUrl; - string resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id, payload.Name); + var packageId = payload.ParentPackagePayloadRef; + var parentUrl = payload.ParentPackagePayloadRef == null ? null : allPayloads[payload.ParentPackagePayloadRef].DownloadUrl; + var resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id.Id, payload.Name); if (!String.IsNullOrEmpty(resolvedUrl)) { writer.WriteAttributeString("DownloadUrl", resolvedUrl); @@ -662,9 +718,9 @@ namespace WixToolset.Core.Burn.Bundles break; } - if (!String.IsNullOrEmpty(payload.Catalog)) + if (!String.IsNullOrEmpty(payload.CatalogRef)) { - writer.WriteAttributeString("Catalog", payload.Catalog); + writer.WriteAttributeString("Catalog", payload.CatalogRef); } } @@ -682,6 +738,5 @@ namespace WixToolset.Core.Burn.Bundles return resolved; } -#endif } } diff --git a/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs index c9dd2671..937721a6 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs @@ -4,24 +4,39 @@ namespace WixToolset.Core.Burn.Bundles { using System; using System.Collections.Generic; - using System.Diagnostics; using System.IO; using System.Linq; + using WixToolset.Core.Native; using WixToolset.Data; + using WixToolset.Data.Tuples; /// /// Creates cabinet files. /// internal class CreateContainerCommand { -#if TODO - public CompressionLevel DefaultCompressionLevel { private get; set; } + public CreateContainerCommand(IEnumerable payloads, string outputPath, CompressionLevel? compressionLevel) + { + this.Payloads = payloads; + this.OutputPath = outputPath; + this.CompressionLevel = compressionLevel; + } + + public CreateContainerCommand(string manifestPath, IEnumerable payloads, string outputPath, CompressionLevel? compressionLevel) + { + this.ManifestFile = manifestPath; + this.Payloads = payloads; + this.OutputPath = outputPath; + this.CompressionLevel = compressionLevel; + } + + private CompressionLevel? CompressionLevel { get; } - public IEnumerable Payloads { private get; set; } + private string ManifestFile { get; } - public string ManifestFile { private get; set; } + private string OutputPath { get; } - public string OutputPath { private get; set; } + private IEnumerable Payloads { get; } public string Hash { get; private set; } @@ -29,40 +44,34 @@ namespace WixToolset.Core.Burn.Bundles public void Execute() { - int payloadCount = this.Payloads.Count(); // The number of embedded payloads + var payloadCount = this.Payloads.Count(); // The number of embedded payloads if (!String.IsNullOrEmpty(this.ManifestFile)) { ++payloadCount; } - using (var cab = new WixCreateCab(Path.GetFileName(this.OutputPath), Path.GetDirectoryName(this.OutputPath), payloadCount, 0, 0, this.DefaultCompressionLevel)) - { - // If a manifest was provided always add it as "payload 0" to the container. - if (!String.IsNullOrEmpty(this.ManifestFile)) - { - cab.AddFile(this.ManifestFile, "0"); - } + var cabinetPath = Path.GetFullPath(this.OutputPath); - foreach (WixBundlePayloadRow payload in this.Payloads) - { - Debug.Assert(PackagingType.Embedded == payload.Packaging); + var files = new List(); - Messaging.Instance.OnMessage(WixVerboses.LoadingPayload(payload.FullFileName)); + // If a manifest was provided always add it as "payload 0" to the container. + if (!String.IsNullOrEmpty(this.ManifestFile)) + { + files.Add(new CabinetCompressFile(this.ManifestFile, "0")); + } - cab.AddFile(payload.FullFileName, payload.EmbeddedId); - } + files.AddRange(this.Payloads.Select(p => new CabinetCompressFile(p.SourceFile.Path, p.EmbeddedId))); - cab.Complete(); - } + var cab = new Cabinet(cabinetPath); + cab.Compress(files, this.CompressionLevel ?? Data.CompressionLevel.Mszip); // Now that the container is created, set the outputs of the command. - FileInfo fileInfo = new FileInfo(this.OutputPath); + var fileInfo = new FileInfo(cabinetPath); - this.Hash = Common.GetFileHash(fileInfo.FullName); + this.Hash = BundleHashAlgorithm.Hash(fileInfo); this.Size = fileInfo.Length; } -#endif } } diff --git a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs new file mode 100644 index 00000000..612e0e11 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs @@ -0,0 +1,134 @@ +// 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.Burn.Bundles +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Tuples; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CreateNonUXContainers + { + public CreateNonUXContainers(IBackendHelper backendHelper, IntermediateSection section, WixBootstrapperApplicationTuple bootstrapperApplicationTuple, Dictionary payloadTuples, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) + { + this.BackendHelper = backendHelper; + this.Section = section; + this.BootstrapperApplicationTuple = bootstrapperApplicationTuple; + this.PayloadTuples = payloadTuples; + this.IntermediateFolder = intermediateFolder; + this.LayoutFolder = layoutFolder; + this.DefaultCompressionLevel = defaultCompressionLevel; + } + + public IEnumerable FileTransfers { get; private set; } + + public WixBundleContainerTuple UXContainer { get; set; } + + public IEnumerable UXContainerPayloads { get; private set; } + + public IEnumerable Containers { get; private set; } + + private IBackendHelper BackendHelper { get; } + + private IntermediateSection Section { get; } + + private WixBootstrapperApplicationTuple BootstrapperApplicationTuple { get; } + + private Dictionary PayloadTuples { get; } + + private string IntermediateFolder { get; } + + private string LayoutFolder { get; } + + private CompressionLevel? DefaultCompressionLevel { get; } + + public void Execute() + { + var fileTransfers = new List(); + + var uxPayloadTuples = new List(); + + var attachedContainerIndex = 1; // count starts at one because UX container is "0". + + var containerTuples = this.Section.Tuples.OfType().ToList(); + + var payloadsByContainer = this.PayloadTuples.Values.ToLookup(p => p.ContainerRef); + + foreach (var container in containerTuples) + { + var containerId = container.Id.Id; + + var containerPayloads = payloadsByContainer[containerId]; + + if (!containerPayloads.Any()) + { + if (containerId != BurnConstants.BurnDefaultAttachedContainerName) + { + // TODO: display warning that we're ignoring container that ended up with no paylods in it. + } + } + else if (BurnConstants.BurnUXContainerName == containerId) + { + this.UXContainer = container; + + container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + container.AttachedContainerIndex = 0; + + // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first + // in the list since that is the Payload that Burn attempts to load. + var baPayloadId = this.BootstrapperApplicationTuple.Id.Id; + + foreach (var uxPayload in containerPayloads) + { + if (uxPayload.Id.Id == baPayloadId) + { + uxPayloadTuples.Insert(0, uxPayload); + } + else + { + uxPayloadTuples.Add(uxPayload); + } + } + } + else + { + container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + + // Add detached containers to the list of file transfers. + if (ContainerType.Detached == container.Type) + { + var transfer = this.BackendHelper.CreateFileTransfer(container.WorkingPath, Path.Combine(this.LayoutFolder, container.Name), true, container.SourceLineNumbers); + fileTransfers.Add(transfer); + } + else // update the attached container index. + { + Debug.Assert(ContainerType.Attached == container.Type); + + container.AttachedContainerIndex = attachedContainerIndex; + ++attachedContainerIndex; + } + + this.CreateContainer(container, containerPayloads, null); + } + } + + this.Containers = containerTuples; + this.UXContainerPayloads = uxPayloadTuples; + this.FileTransfers = fileTransfers; + } + + private void CreateContainer(WixBundleContainerTuple container, IEnumerable containerPayloads, string manifestFile) + { + var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); + command.Execute(); + + container.Hash = command.Hash; + container.Size = command.Size; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs index 1ed37046..71e4cfea 100644 --- a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs @@ -3,61 +3,69 @@ namespace WixToolset.Core.Burn.Bundles { using System.Collections.Generic; + using System.Linq; using WixToolset.Data; + using WixToolset.Data.Tuples; internal class GetPackageFacadesCommand { -#if TODO - public Table PackageTable { private get; set; } - - public Table ExePackageTable { private get; set; } - - public Table MsiPackageTable { private get; set; } + public GetPackageFacadesCommand(IEnumerable chainPackageTuples, IntermediateSection section) + { + this.ChainPackageTuples = chainPackageTuples; + this.Section = section; + } - public Table MspPackageTable { private get; set; } + private IEnumerable ChainPackageTuples { get; } - public Table MsuPackageTable { private get; set; } + private IntermediateSection Section { get; } public IDictionary PackageFacades { get; private set; } public void Execute() { - RowDictionary exePackages = new RowDictionary(this.ExePackageTable); - RowDictionary msiPackages = new RowDictionary(this.MsiPackageTable); - RowDictionary mspPackages = new RowDictionary(this.MspPackageTable); - RowDictionary msuPackages = new RowDictionary(this.MsuPackageTable); + var exePackages = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); + var msiPackages = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); + var mspPackages = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); + var msuPackages = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); - Dictionary facades = new Dictionary(this.PackageTable.Rows.Count); + var facades = new Dictionary(); - foreach (WixBundlePackageRow package in this.PackageTable.Rows) + foreach (var package in this.ChainPackageTuples) { - string id = package.WixChainItemId; - PackageFacade facade = null; - + var id = package.Id.Id; switch (package.Type) { - case WixBundlePackageType.Exe: - facade = new PackageFacade(package, exePackages.Get(id)); - break; - - case WixBundlePackageType.Msi: - facade = new PackageFacade(package, msiPackages.Get(id)); - break; - - case WixBundlePackageType.Msp: - facade = new PackageFacade(package, mspPackages.Get(id)); - break; - - case WixBundlePackageType.Msu: - facade = new PackageFacade(package, msuPackages.Get(id)); - break; + case WixBundlePackageType.Exe: + if (exePackages.TryGetValue(id, out var exePackage)) + { + facades.Add(id, new PackageFacade(package, exePackage)); + } + break; + + case WixBundlePackageType.Msi: + if (msiPackages.TryGetValue(id, out var msiPackage)) + { + facades.Add(id, new PackageFacade(package, msiPackage)); + } + break; + + case WixBundlePackageType.Msp: + if (mspPackages.TryGetValue(id, out var mspPackage)) + { + facades.Add(id, new PackageFacade(package, mspPackage)); + } + break; + + case WixBundlePackageType.Msu: + if (msuPackages.TryGetValue(id, out var msuPackage)) + { + facades.Add(id, new PackageFacade(package, msuPackage)); + } + break; } - - facades.Add(id, facade); } this.PackageFacades = facades; } -#endif } } diff --git a/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs index 48923ba1..8ead0952 100644 --- a/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs @@ -5,24 +5,35 @@ namespace WixToolset.Core.Burn.Bundles using System; using System.Collections.Generic; using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Extensibility.Services; internal class OrderPackagesAndRollbackBoundariesCommand { -#if TODO - public Table WixGroupTable { private get; set; } + public OrderPackagesAndRollbackBoundariesCommand(IMessaging messaging, IEnumerable groupTuples, Dictionary boundaryTuples, IDictionary packageFacades) + { + this.Messaging = messaging; + this.GroupTuples = groupTuples; + this.Boundaries = boundaryTuples; + this.PackageFacades = packageFacades; + } + + private IMessaging Messaging { get; } + + public IEnumerable GroupTuples { get; } - public RowDictionary Boundaries { private get; set; } + public Dictionary Boundaries { get; } - public IDictionary PackageFacades { private get; set; } + public IDictionary PackageFacades { get; } public IEnumerable OrderedPackageFacades { get; private set; } - public IEnumerable UsedRollbackBoundaries { get; private set; } + public IEnumerable UsedRollbackBoundaries { get; private set; } public void Execute() { - List orderedFacades = new List(); - List usedBoundaries = new List(); + var orderedFacades = new List(); + var usedBoundaries = new List(); // Process the chain of packages to add them in the correct order // and assign the forward rollback boundaries as appropriate. Remember @@ -33,44 +44,44 @@ namespace WixToolset.Core.Burn.Bundles // We handle uninstall (aka: backwards) rollback boundaries after // we get these install/repair (aka: forward) rollback boundaries // defined. - WixBundleRollbackBoundaryRow previousRollbackBoundary = null; - WixBundleRollbackBoundaryRow lastRollbackBoundary = null; - bool boundaryHadX86Package = false; + WixBundleRollbackBoundaryTuple previousRollbackBoundary = null; + WixBundleRollbackBoundaryTuple lastRollbackBoundary = null; + var boundaryHadX86Package = false; - foreach (WixGroupRow row in this.WixGroupTable.Rows) + foreach (var groupTuple in this.GroupTuples) { - if (ComplexReferenceChildType.Package == row.ChildType && ComplexReferenceParentType.PackageGroup == row.ParentType && "WixChain" == row.ParentId) + if (ComplexReferenceChildType.Package == groupTuple.ChildType && ComplexReferenceParentType.PackageGroup == groupTuple.ParentType && "WixChain" == groupTuple.ParentId) { - PackageFacade facade = null; - if (PackageFacades.TryGetValue(row.ChildId, out facade)) + if (this.PackageFacades.TryGetValue(groupTuple.ChildId, out var facade)) { if (null != previousRollbackBoundary) { usedBoundaries.Add(previousRollbackBoundary); - facade.Package.RollbackBoundary = previousRollbackBoundary.ChainPackageId; + facade.PackageTuple.RollbackBoundaryRef = previousRollbackBoundary.Id.Id; previousRollbackBoundary = null; - boundaryHadX86Package = (facade.Package.x64 == YesNoType.Yes); + boundaryHadX86Package = facade.PackageTuple.Win64; } // Error if MSI transaction has x86 package preceding x64 packages - if ((lastRollbackBoundary != null) && (lastRollbackBoundary.Transaction == YesNoType.Yes) + if ((lastRollbackBoundary != null) + && lastRollbackBoundary.Transaction == true && boundaryHadX86Package - && (facade.Package.x64 == YesNoType.Yes)) + && facade.PackageTuple.Win64) { - Messaging.Instance.OnMessage(WixErrors.MsiTransactionX86BeforeX64(lastRollbackBoundary.SourceLineNumbers)); + this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64(lastRollbackBoundary.SourceLineNumbers)); } - boundaryHadX86Package = boundaryHadX86Package || (facade.Package.x64 == YesNoType.No); + boundaryHadX86Package |= facade.PackageTuple.Win64; orderedFacades.Add(facade); } else // must be a rollback boundary. { // Discard the next rollback boundary if we have a previously defined boundary. - WixBundleRollbackBoundaryRow nextRollbackBoundary = Boundaries.Get(row.ChildId); + var nextRollbackBoundary = this.Boundaries[groupTuple.ChildId]; if (null != previousRollbackBoundary) { - Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.ChainPackageId)); + this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id.Id)); } else { @@ -83,7 +94,7 @@ namespace WixToolset.Core.Burn.Bundles if (null != previousRollbackBoundary) { - Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.ChainPackageId)); + this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.Id.Id)); } // With the forward rollback boundaries assigned, we can now go @@ -120,14 +131,14 @@ namespace WixToolset.Core.Burn.Bundles foreach (PackageFacade package in orderedFacades) { - if (null != package.Package.RollbackBoundary) + if (null != package.PackageTuple.RollbackBoundaryRef) { if (null != previousFacade) { - previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; + previousFacade.PackageTuple.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; } - previousRollbackBoundaryId = package.Package.RollbackBoundary; + previousRollbackBoundaryId = package.PackageTuple.RollbackBoundaryRef; } previousFacade = package; @@ -135,12 +146,11 @@ namespace WixToolset.Core.Burn.Bundles if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) { - previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; + previousFacade.PackageTuple.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; } this.OrderedPackageFacades = orderedFacades; this.UsedRollbackBoundaries = usedBoundaries; } -#endif } } diff --git a/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs b/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs index c68a8311..8b1711a1 100644 --- a/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs +++ b/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs @@ -2,57 +2,24 @@ namespace WixToolset.Core.Burn.Bundles { + using System.Diagnostics; + using WixToolset.Data; + using WixToolset.Data.Tuples; + internal class PackageFacade { -#if TODO - private PackageFacade(WixBundlePackageRow package) - { - this.Package = package; - this.Provides = new ProvidesDependencyCollection(); - } - - public PackageFacade(WixBundlePackageRow package, WixBundleExePackageRow exePackage) - : this(package) + public PackageFacade(WixBundlePackageTuple packageTuple, IntermediateTuple specificPackageTuple) { - this.ExePackage = exePackage; - } + Debug.Assert(packageTuple.Id.Id == specificPackageTuple.Id.Id); - public PackageFacade(WixBundlePackageRow package, WixBundleMsiPackageRow msiPackage) - : this(package) - { - this.MsiPackage = msiPackage; + this.PackageTuple = packageTuple; + this.SpecificPackageTuple = specificPackageTuple; } - public PackageFacade(WixBundlePackageRow package, WixBundleMspPackageRow mspPackage) - : this(package) - { - this.MspPackage = mspPackage; - } - - public PackageFacade(WixBundlePackageRow package, WixBundleMsuPackageRow msuPackage) - : this(package) - { - this.MsuPackage = msuPackage; - } - - public WixBundlePackageRow Package { get; private set; } - - public WixBundleExePackageRow ExePackage { get; private set; } - - public WixBundleMsiPackageRow MsiPackage { get; private set; } - - public WixBundleMspPackageRow MspPackage { get; private set; } + public string PackageId => this.PackageTuple.Id.Id; - public WixBundleMsuPackageRow MsuPackage { get; private set; } + public WixBundlePackageTuple PackageTuple { get; } - /// - /// The provides dependencies authored and imported for this package. - /// - /// - /// TODO: Eventually this collection should turn into Rows so they are tracked in the PDB but - /// the relationship with the extension makes it much trickier to pull off. - /// - public ProvidesDependencyCollection Provides { get; private set; } -#endif + public IntermediateTuple SpecificPackageTuple { get; } } } diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs index 77102c0f..56254a06 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs @@ -3,32 +3,37 @@ namespace WixToolset.Core.Burn.Bundles { using System; - using WixToolset.Data; + using System.Collections.Generic; + using WixToolset.Data.Tuples; /// /// Initializes package state from the Exe contents. /// internal class ProcessExePackageCommand { -#if TODO - public RowDictionary AuthoredPayloads { private get; set; } + public ProcessExePackageCommand(PackageFacade facade, Dictionary payloadTuples) + { + this.AuthoredPayloads = payloadTuples; + this.Facade = facade; + } + + public Dictionary AuthoredPayloads { get; } - public PackageFacade Facade { private get; set; } + public PackageFacade Facade { get; } /// /// Processes the Exe packages to add properties and payloads from the Exe packages. /// public void Execute() { - WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); + var packagePayload = this.AuthoredPayloads[this.Facade.PackageTuple.PayloadRef]; - if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) + if (String.IsNullOrEmpty(this.Facade.PackageTuple.CacheId)) { - this.Facade.Package.CacheId = packagePayload.Hash; + this.Facade.PackageTuple.CacheId = packagePayload.Hash; } - this.Facade.Package.Version = packagePayload.Version; + this.Facade.PackageTuple.Version = packagePayload.Version; } -#endif } } diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs index 39a71be7..5fcf172f 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs @@ -3,7 +3,6 @@ namespace WixToolset.Core.Burn.Bundles { using System; - using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -11,47 +10,62 @@ namespace WixToolset.Core.Burn.Bundles using System.Linq; using WixToolset.Data; using WixToolset.Extensibility; - using WixToolset.Core.Native; using Dtf = WixToolset.Dtf.WindowsInstaller; - using WixToolset.Data.Bind; + using WixToolset.Extensibility.Services; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; /// /// Initializes package state from the MSI contents. /// internal class ProcessMsiPackageCommand { -#if TODO private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'"; - public RowDictionary AuthoredPayloads { private get; set; } + public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary payloadTuples) + { + this.Messaging = serviceProvider.GetService(); + this.BackendHelper = serviceProvider.GetService(); + this.PathResolver = serviceProvider.GetService(); + + this.BackendExtensions = backendExtensions; + + this.AuthoredPayloads = payloadTuples; + this.Section = section; + this.Facade = facade; + } + + private IMessaging Messaging { get; } - public PackageFacade Facade { private get; set; } + private IBackendHelper BackendHelper { get; } - public IEnumerable BackendExtensions { private get; set; } + private IPathResolver PathResolver { get; } - public Table MsiFeatureTable { private get; set; } + private IEnumerable BackendExtensions { get; } - public Table MsiPropertyTable { private get; set; } + private Dictionary AuthoredPayloads { get; } - public Table PayloadTable { private get; set; } + private PackageFacade Facade { get; } - public Table RelatedPackageTable { private get; set; } + private IntermediateSection Section { get; } /// /// Processes the MSI packages to add properties and payloads from the MSI packages. /// public void Execute() { - WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); + var packagePayload = this.AuthoredPayloads[this.Facade.PackageTuple.PayloadRef]; - string sourcePath = packagePayload.FullFileName; - bool longNamesInImage = false; - bool compressed = false; - bool x64 = false; + var msiPackage = (WixBundleMsiPackageTuple)this.Facade.SpecificPackageTuple; + + var sourcePath = packagePayload.SourceFile.Path; + var longNamesInImage = false; + var compressed = false; try { // Read data out of the msi database... - using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) + using (var sumInfo = new Dtf.SummaryInfo(sourcePath, false)) { // 1 is the Word Count summary information stream bit that means // the MSI uses short file names when set. We care about long file @@ -62,83 +76,84 @@ namespace WixToolset.Core.Burn.Bundles // files are compressed in the MSI by default when the bit is set. compressed = 2 == (sumInfo.WordCount & 2); - x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64")); - // 8 is the Word Count summary information stream bit that means // "Elevated privileges are not required to install this package." // in MSI 4.5 and below, if this bit is 0, elevation is required. - this.Facade.Package.PerMachine = (0 == (sumInfo.WordCount & 8)) ? YesNoDefaultType.Yes : YesNoDefaultType.No; - this.Facade.Package.x64 = x64 ? YesNoType.Yes : YesNoType.No; + var perMachine = (0 == (sumInfo.WordCount & 8)); + var x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64")); + + this.Facade.PackageTuple.PerMachine = perMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; + this.Facade.PackageTuple.Win64 = x64; } - using (Dtf.Database db = new Dtf.Database(sourcePath)) + using (var db = new Dtf.Database(sourcePath)) { - this.Facade.MsiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode"); - this.Facade.MsiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode"); - this.Facade.MsiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer"); - this.Facade.MsiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture); - this.Facade.MsiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion"); + msiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode"); + msiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode"); + msiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer"); + msiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture); + msiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion"); - if (!Common.IsValidModuleOrBundleVersion(this.Facade.MsiPackage.ProductVersion)) + if (!Common.IsValidModuleOrBundleVersion(msiPackage.ProductVersion)) { // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? string version = null; - string[] versionParts = this.Facade.MsiPackage.ProductVersion.Split('.'); - int count = versionParts.Length; + string[] versionParts = msiPackage.ProductVersion.Split('.'); + var count = versionParts.Length; if (0 < count) { version = versionParts[0]; - for (int i = 1; i < 4 && i < count; ++i) + for (var i = 1; i < 4 && i < count; ++i) { version = String.Concat(version, ".", versionParts[i]); } } - + if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version)) { - Messaging.Instance.OnMessage(WixWarnings.VersionTruncated(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath, version)); - this.Facade.MsiPackage.ProductVersion = version; + this.Messaging.Write(WarningMessages.VersionTruncated(this.Facade.PackageTuple.SourceLineNumbers, msiPackage.ProductVersion, sourcePath, version)); + msiPackage.ProductVersion = version; } else { - Messaging.Instance.OnMessage(WixErrors.InvalidProductVersion(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath)); + this.Messaging.Write(ErrorMessages.InvalidProductVersion(this.Facade.PackageTuple.SourceLineNumbers, msiPackage.ProductVersion, sourcePath)); } } - if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) + if (String.IsNullOrEmpty(this.Facade.PackageTuple.CacheId)) { - this.Facade.Package.CacheId = String.Format("{0}v{1}", this.Facade.MsiPackage.ProductCode, this.Facade.MsiPackage.ProductVersion); + this.Facade.PackageTuple.CacheId = String.Format("{0}v{1}", msiPackage.ProductCode, msiPackage.ProductVersion); } - if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) + if (String.IsNullOrEmpty(this.Facade.PackageTuple.DisplayName)) { - this.Facade.Package.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName"); + this.Facade.PackageTuple.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName"); } - if (String.IsNullOrEmpty(this.Facade.Package.Description)) + if (String.IsNullOrEmpty(this.Facade.PackageTuple.Description)) { - this.Facade.Package.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS"); + this.Facade.PackageTuple.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS"); } - ISet payloadNames = this.GetPayloadTargetNames(); + var payloadNames = this.GetPayloadTargetNames(packagePayload.Id.Id); - ISet msiPropertyNames = this.GetMsiPropertyNames(); + var msiPropertyNames = this.GetMsiPropertyNames(packagePayload.Id.Id); - this.SetPerMachineAppropriately(db, sourcePath); + this.SetPerMachineAppropriately(db, msiPackage, sourcePath); // Ensure the MSI package is appropriately marked visible or not. - this.SetPackageVisibility(db, msiPropertyNames); + this.SetPackageVisibility(db, msiPackage, msiPropertyNames); // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. if (!msiPropertyNames.Contains("MSIFASTINSTALL") && !ProcessMsiPackageCommand.HasProperty(db, "MSIFASTINSTALL")) { - this.AddMsiProperty("MSIFASTINSTALL", "7"); + this.AddMsiProperty(msiPackage, "MSIFASTINSTALL", "7"); } this.CreateRelatedPackages(db); // If feature selection is enabled, represent the Feature table in the manifest. - if (this.Facade.MsiPackage.EnableFeatureSelection) + if ((msiPackage.Attributes & WixBundleMsiPackageAttributes.EnableFeatureSelection) == WixBundleMsiPackageAttributes.EnableFeatureSelection) { this.CreateMsiFeatures(db); } @@ -148,90 +163,92 @@ namespace WixToolset.Core.Burn.Bundles // Add all external files as package payloads and calculate the total install size as the rollup of // File table's sizes. - this.Facade.Package.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); + this.Facade.PackageTuple.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); // Add all dependency providers from the MSI. - this.ImportDependencyProviders(db); + this.ImportDependencyProviders(msiPackage, db); } } catch (Dtf.InstallerException e) { - Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(this.Facade.Package.SourceLineNumbers, sourcePath, e.Message)); + this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(this.Facade.PackageTuple.SourceLineNumbers, sourcePath, e.Message)); } } - private ISet GetPayloadTargetNames() + private ISet GetPayloadTargetNames(string packageId) { - IEnumerable payloadNames = this.PayloadTable.RowsAs() - .Where(r => r.Package == this.Facade.Package.WixChainItemId) - .Select(r => r.Name); + var payloadNames = this.Section.Tuples.OfType() + .Where(p => p.PackageRef == packageId) + .Select(p => p.Name); return new HashSet(payloadNames, StringComparer.OrdinalIgnoreCase); } - private ISet GetMsiPropertyNames() + private ISet GetMsiPropertyNames(string packageId) { - IEnumerable properties = this.MsiPropertyTable.RowsAs() - .Where(r => r.ChainPackageId == this.Facade.Package.WixChainItemId) - .Select(r => r.Name); + var properties = this.Section.Tuples.OfType() + .Where(p => p.Id.Id == packageId) + .Select(p => p.Name); return new HashSet(properties, StringComparer.Ordinal); } - private void SetPerMachineAppropriately(Dtf.Database db, string sourcePath) + private void SetPerMachineAppropriately(Dtf.Database db, WixBundleMsiPackageTuple msiPackage, string sourcePath) { - if (this.Facade.MsiPackage.ForcePerMachine) + if (msiPackage.ForcePerMachine) { - if (YesNoDefaultType.No == this.Facade.Package.PerMachine) + if (YesNoDefaultType.No == this.Facade.PackageTuple.PerMachine) { - Messaging.Instance.OnMessage(WixWarnings.PerUserButForcingPerMachine(this.Facade.Package.SourceLineNumbers, sourcePath)); - this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. + this.Messaging.Write(WarningMessages.PerUserButForcingPerMachine(this.Facade.PackageTuple.SourceLineNumbers, sourcePath)); + this.Facade.PackageTuple.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. } // Force ALLUSERS=1 via the MSI command-line. - this.AddMsiProperty("ALLUSERS", "1"); + this.AddMsiProperty(msiPackage, "ALLUSERS", "1"); } else { - string allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS"); + var allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS"); if (String.IsNullOrEmpty(allusers)) { // Not forced per-machine and no ALLUSERS property, flip back to per-user. - if (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) + if (YesNoDefaultType.Yes == this.Facade.PackageTuple.PerMachine) { - Messaging.Instance.OnMessage(WixWarnings.ImplicitlyPerUser(this.Facade.Package.SourceLineNumbers, sourcePath)); - this.Facade.Package.PerMachine = YesNoDefaultType.No; + this.Messaging.Write(WarningMessages.ImplicitlyPerUser(this.Facade.PackageTuple.SourceLineNumbers, sourcePath)); + this.Facade.PackageTuple.PerMachine = YesNoDefaultType.No; } } else if (allusers.Equals("1", StringComparison.Ordinal)) { - if (YesNoDefaultType.No == this.Facade.Package.PerMachine) + if (YesNoDefaultType.No == this.Facade.PackageTuple.PerMachine) { - Messaging.Instance.OnMessage(WixErrors.PerUserButAllUsersEquals1(this.Facade.Package.SourceLineNumbers, sourcePath)); + this.Messaging.Write(ErrorMessages.PerUserButAllUsersEquals1(this.Facade.PackageTuple.SourceLineNumbers, sourcePath)); } } else if (allusers.Equals("2", StringComparison.Ordinal)) { - Messaging.Instance.OnMessage(WixWarnings.DiscouragedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) ? "machine" : "user")); + this.Messaging.Write(WarningMessages.DiscouragedAllUsersValue(this.Facade.PackageTuple.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.PackageTuple.PerMachine) ? "machine" : "user")); } else { - Messaging.Instance.OnMessage(WixErrors.UnsupportedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, allusers)); + this.Messaging.Write(ErrorMessages.UnsupportedAllUsersValue(this.Facade.PackageTuple.SourceLineNumbers, sourcePath, allusers)); } } } - private void SetPackageVisibility(Dtf.Database db, ISet msiPropertyNames) + private void SetPackageVisibility(Dtf.Database db, WixBundleMsiPackageTuple msiPackage, ISet msiPropertyNames) { - bool alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT"); + var alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT"); + var visible = (this.Facade.PackageTuple.Attributes & WixBundlePackageAttributes.Visible) == WixBundlePackageAttributes.Visible; - if (alreadyVisible != this.Facade.Package.Visible) // if not already set to the correct visibility. + // If not already set to the correct visibility. + if (alreadyVisible != visible) { // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) { - this.AddMsiProperty("ARPSYSTEMCOMPONENT", this.Facade.Package.Visible ? String.Empty : "1"); + this.AddMsiProperty(msiPackage, "ARPSYSTEMCOMPONENT", visible ? String.Empty : "1"); } } } @@ -241,30 +258,35 @@ namespace WixToolset.Core.Burn.Bundles // Represent the Upgrade table as related packages. if (db.Tables.Contains("Upgrade")) { - using (Dtf.View view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) + using (var view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) { view.Execute(); while (true) { - using (Dtf.Record record = view.Fetch()) + using (var record = view.Fetch()) { if (null == record) { break; } - WixBundleRelatedPackageRow related = (WixBundleRelatedPackageRow)this.RelatedPackageTable.CreateRow(this.Facade.Package.SourceLineNumbers); - related.ChainPackageId = this.Facade.Package.WixChainItemId; - related.Id = record.GetString(1); - related.MinVersion = record.GetString(2); - related.MaxVersion = record.GetString(3); - related.Languages = record.GetString(4); - - int attributes = record.GetInteger(5); - related.OnlyDetect = (attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect) == MsiInterop.MsidbUpgradeAttributesOnlyDetect; - related.MinInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMinInclusive; - related.MaxInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive; - related.LangInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive) == 0; + var recordAttributes = record.GetInteger(5); + + var attributes = WixBundleRelatedPackageAttributes.None; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect) == WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect ? WixBundleRelatedPackageAttributes.OnlyDetect : 0; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive ? WixBundleRelatedPackageAttributes.MinInclusive : 0; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive ? WixBundleRelatedPackageAttributes.MaxInclusive : 0; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive ? WixBundleRelatedPackageAttributes.LangInclusive : 0; + + var related = new WixBundleRelatedPackageTuple(this.Facade.PackageTuple.SourceLineNumbers) + { + PackageRef = this.Facade.PackageId, + RelatedId = record.GetString(1), + MinVersion = record.GetString(2), + MaxVersion = record.GetString(3), + Languages = record.GetString(4), + Attributes = attributes, + }; } } } @@ -275,26 +297,26 @@ namespace WixToolset.Core.Burn.Bundles { if (db.Tables.Contains("Feature")) { - using (Dtf.View featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) - using (Dtf.View componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) + using (var featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) + using (var componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) { - using (Dtf.Record featureRecord = new Dtf.Record(1)) - using (Dtf.Record componentRecord = new Dtf.Record(1)) + using (var featureRecord = new Dtf.Record(1)) + using (var componentRecord = new Dtf.Record(1)) { - using (Dtf.View allFeaturesView = db.OpenView("SELECT * FROM `Feature`")) + using (var allFeaturesView = db.OpenView("SELECT * FROM `Feature`")) { allFeaturesView.Execute(); while (true) { - using (Dtf.Record allFeaturesResultRecord = allFeaturesView.Fetch()) + using (var allFeaturesResultRecord = allFeaturesView.Fetch()) { if (null == allFeaturesResultRecord) { break; } - string featureName = allFeaturesResultRecord.GetString(1); + var featureName = allFeaturesResultRecord.GetString(1); // Calculate the Feature size. featureRecord.SetString(1, featureName); @@ -304,43 +326,46 @@ namespace WixToolset.Core.Burn.Bundles long size = 0; while (true) { - using (Dtf.Record componentResultRecord = featureView.Fetch()) + using (var componentResultRecord = featureView.Fetch()) { if (null == componentResultRecord) { break; } - string component = componentResultRecord.GetString(1); + + var component = componentResultRecord.GetString(1); componentRecord.SetString(1, component); componentView.Execute(componentRecord); while (true) { - using (Dtf.Record fileResultRecord = componentView.Fetch()) + using (var fileResultRecord = componentView.Fetch()) { if (null == fileResultRecord) { break; } - string fileSize = fileResultRecord.GetString(1); + var fileSize = fileResultRecord.GetString(1); size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); } } } } - WixBundleMsiFeatureRow feature = (WixBundleMsiFeatureRow)this.MsiFeatureTable.CreateRow(this.Facade.Package.SourceLineNumbers); - feature.ChainPackageId = this.Facade.Package.WixChainItemId; - feature.Name = featureName; - feature.Parent = allFeaturesResultRecord.GetString(2); - feature.Title = allFeaturesResultRecord.GetString(3); - feature.Description = allFeaturesResultRecord.GetString(4); - feature.Display = allFeaturesResultRecord.GetInteger(5); - feature.Level = allFeaturesResultRecord.GetInteger(6); - feature.Directory = allFeaturesResultRecord.GetString(7); - feature.Attributes = allFeaturesResultRecord.GetInteger(8); - feature.Size = size; + var feature = new WixBundleMsiFeatureTuple(this.Facade.PackageTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, this.Facade.PackageId, featureName)) + { + PackageRef = this.Facade.PackageId, + Name = featureName, + Parent = allFeaturesResultRecord.GetString(2), + Title = allFeaturesResultRecord.GetString(3), + Description = allFeaturesResultRecord.GetString(4), + Display = allFeaturesResultRecord.GetInteger(5), + Level = allFeaturesResultRecord.GetInteger(6), + Directory = allFeaturesResultRecord.GetString(7), + Attributes = allFeaturesResultRecord.GetInteger(8), + Size = size + }; } } } @@ -349,113 +374,119 @@ namespace WixToolset.Core.Burn.Bundles } } - private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadRow packagePayload, ISet payloadNames) + private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadTuple packagePayload, ISet payloadNames) { if (db.Tables.Contains("Media")) { - foreach (string cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`")) + foreach (var cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`")) { if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) { // If we didn't find the Payload as an existing child of the package, we need to // add it. We expect the file to exist on-disk in the same relative location as // the MSI expects to find it... - string cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); + var cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); if (!payloadNames.Contains(cabinetName)) { - string generatedId = Common.GenerateIdentifier("cab", packagePayload.Id, cabinet); - string payloadSourceFile = this.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.Package.SourceLineNumbers, BindStage.Normal); - - WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); - payload.Id = generatedId; - payload.Name = cabinetName; - payload.SourceFile = payloadSourceFile; - payload.Compressed = packagePayload.Compressed; - payload.UnresolvedSourceFile = cabinetName; - payload.Package = packagePayload.Package; - payload.Container = packagePayload.Container; - payload.ContentFile = true; - payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; - payload.Packaging = packagePayload.Packaging; - payload.ParentPackagePayload = packagePayload.Id; + var generatedId = Common.GenerateIdentifier("cab", packagePayload.Id.Id, cabinet); + var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.PackageTuple.SourceLineNumbers, BindStage.Normal); + + var tuple = new WixBundlePayloadTuple(this.Facade.PackageTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) + { + Name = cabinetName, + SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, + Compressed = packagePayload.Compressed, + UnresolvedSourceFile = cabinetName, + PackageRef = packagePayload.PackageRef, + ContainerRef = packagePayload.ContainerRef, + ContentFile = true, + EnableSignatureValidation = packagePayload.EnableSignatureValidation, + Packaging = packagePayload.Packaging, + ParentPackagePayloadRef = packagePayload.Id.Id, + }; + + this.Section.Tuples.Add(tuple); } } } } } - private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadRow packagePayload, bool longNamesInImage, bool compressed, ISet payloadNames) + private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadTuple packagePayload, bool longNamesInImage, bool compressed, ISet payloadNames) { long size = 0; if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File")) { - Hashtable directories = new Hashtable(); + var directories = new Dictionary(); // Load up the directory hash table so we will be able to resolve source paths // for files in the MSI database. - using (Dtf.View view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) + using (var view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) { view.Execute(); while (true) { - using (Dtf.Record record = view.Fetch()) + using (var record = view.Fetch()) { if (null == record) { break; } - string sourceName = Common.GetName(record.GetString(3), true, longNamesInImage); - directories.Add(record.GetString(1), new ResolvedDirectory(record.GetString(2), sourceName)); + var sourceName = Common.GetName(record.GetString(3), true, longNamesInImage); + + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(record.GetString(2), sourceName); + + directories.Add(record.GetString(1), resolvedDirectory); } } } // Resolve the source paths to external files and add each file size to the total // install size of the package. - using (Dtf.View view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) + using (var view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) { view.Execute(); while (true) { - using (Dtf.Record record = view.Fetch()) + using (var record = view.Fetch()) { if (null == record) { break; } - // Skip adding the loose files as payloads if it was suppressed. - if (!this.Facade.MsiPackage.SuppressLooseFilePayloadGeneration) + // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not + // explicitly marked compressed then this is an external file. + var compressionBit = record.GetInteger(4); + if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) || + (!compressed && 0 == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesCompressed))) { - // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not - // explicitly marked compressed then this is an external file. - if (MsiInterop.MsidbFileAttributesNoncompressed == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesNoncompressed) || - (!compressed && 0 == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesCompressed))) + string fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); + var name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); + + if (!payloadNames.Contains(name)) { - string fileSourcePath = Binder.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); - string name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); + var generatedId = Common.GenerateIdentifier("f", packagePayload.Id.Id, record.GetString(2)); + var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.PackageTuple.SourceLineNumbers, BindStage.Normal); - if (!payloadNames.Contains(name)) + var tuple = new WixBundlePayloadTuple(this.Facade.PackageTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) { - string generatedId = Common.GenerateIdentifier("f", packagePayload.Id, record.GetString(2)); - string payloadSourceFile = this.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.Package.SourceLineNumbers, BindStage.Normal); - - WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); - payload.Id = generatedId; - payload.Name = name; - payload.SourceFile = payloadSourceFile; - payload.Compressed = packagePayload.Compressed; - payload.UnresolvedSourceFile = name; - payload.Package = packagePayload.Package; - payload.Container = packagePayload.Container; - payload.ContentFile = true; - payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; - payload.Packaging = packagePayload.Packaging; - payload.ParentPackagePayload = packagePayload.Id; - } + Name = name, + SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, + Compressed = packagePayload.Compressed, + UnresolvedSourceFile = name, + PackageRef = packagePayload.PackageRef, + ContainerRef = packagePayload.ContainerRef, + ContentFile = true, + EnableSignatureValidation = packagePayload.EnableSignatureValidation, + Packaging = packagePayload.Packaging, + ParentPackagePayloadRef = packagePayload.Id.Id, + }; + + this.Section.Tuples.Add(tuple); } } @@ -468,26 +499,30 @@ namespace WixToolset.Core.Burn.Bundles return size; } - private void AddMsiProperty(string name, string value) + private void AddMsiProperty(WixBundleMsiPackageTuple msiPackage, string name, string value) { - WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.MsiPropertyTable.CreateRow(this.Facade.MsiPackage.SourceLineNumbers); - row.ChainPackageId = this.Facade.Package.WixChainItemId; - row.Name = name; - row.Value = value; + var tuple = new WixBundleMsiPropertyTuple(msiPackage.SourceLineNumbers, new Identifier(AccessModifier.Private, msiPackage.Id.Id, name)) + { + PackageRef = msiPackage.Id.Id, + Name = name, + Value = value + }; + + this.Section.Tuples.Add(tuple); } - private void ImportDependencyProviders(Dtf.Database db) + private void ImportDependencyProviders(WixBundleMsiPackageTuple msiPackage, Dtf.Database db) { if (db.Tables.Contains("WixDependencyProvider")) { - string query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"; + var query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"; - using (Dtf.View view = db.OpenView(query)) + using (var view = db.OpenView(query)) { view.Execute(); while (true) { - using (Dtf.Record record = view.Fetch()) + using (var record = view.Fetch()) { if (null == record) { @@ -495,34 +530,51 @@ namespace WixToolset.Core.Burn.Bundles } // Import the provider key and attributes. - string providerKey = record.GetString(1); - string version = record.GetString(2) ?? this.Facade.MsiPackage.ProductVersion; - string displayName = record.GetString(3) ?? this.Facade.Package.DisplayName; - int attributes = record.GetInteger(4); - - ProvidesDependency dependency = new ProvidesDependency(providerKey, version, displayName, attributes); - dependency.Imported = true; - - this.Facade.Provides.Add(dependency); + var tuple = new ProvidesDependencyTuple(msiPackage.SourceLineNumbers) + { + PackageRef = msiPackage.Id.Id, + Key = record.GetString(1), + Version = record.GetString(2) ?? msiPackage.ProductVersion, + DisplayName = record.GetString(3) ?? this.Facade.PackageTuple.DisplayName, + Attributes = record.GetInteger(4), + Imported = true + }; + + this.Section.Tuples.Add(tuple); } } } } } - private string ResolveRelatedFile(string sourceFile, string relatedSource, string type, SourceLineNumber sourceLineNumbers, BindStage stage) + private string ResolveRelatedFile(string resolvedSource, string unresolvedSource, string relatedSource, string type, SourceLineNumber sourceLineNumbers, BindStage stage) { + var checkedPaths = new List(); + foreach (var extension in this.BackendExtensions) { - var relatedFile = extension.ResolveRelatedFile(sourceFile, relatedSource, type, sourceLineNumbers, stage); + var resolved = extension.ResolveRelatedFile(unresolvedSource, relatedSource, type, sourceLineNumbers, stage); - if (!String.IsNullOrEmpty(relatedFile)) + if (resolved?.CheckedPaths != null) { - return relatedFile; + checkedPaths.AddRange(resolved.CheckedPaths); + } + + if (!String.IsNullOrEmpty(resolved?.Path)) + { + return resolved?.Path; } } - return null; + var resolvedPath = Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource); + + if (!File.Exists(resolvedPath)) + { + checkedPaths.Add(resolvedPath); + this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, resolvedPath, type, checkedPaths)); + } + + return resolvedPath; } /// @@ -571,6 +623,5 @@ namespace WixToolset.Core.Burn.Bundles return String.Format(CultureInfo.InvariantCulture, ProcessMsiPackageCommand.PropertySqlFormat, property); } -#endif } } diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs index e0390360..dc832d40 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs @@ -10,6 +10,8 @@ namespace WixToolset.Core.Burn.Bundles using System.Text; using System.Xml; using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Extensibility.Services; using Dtf = WixToolset.Dtf.WindowsInstaller; /// @@ -17,80 +19,92 @@ namespace WixToolset.Core.Burn.Bundles /// internal class ProcessMspPackageCommand { -#if TODO private const string PatchMetadataFormat = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = '{0}'"; private static readonly Encoding XmlOutputEncoding = new UTF8Encoding(false); - public RowDictionary AuthoredPayloads { private get; set; } + public ProcessMspPackageCommand(IMessaging messaging, IntermediateSection section, PackageFacade facade, Dictionary payloadTuples) + { + this.Messaging = messaging; + + this.AuthoredPayloads = payloadTuples; + this.Section = section; + this.Facade = facade; + } + + public IMessaging Messaging { get; } + + public Dictionary AuthoredPayloads { private get; set; } public PackageFacade Facade { private get; set; } - public Table WixBundlePatchTargetCodeTable { private get; set; } + public IntermediateSection Section { get; } /// /// Processes the Msp packages to add properties and payloads from the Msp packages. /// public void Execute() { - WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); + var packagePayload = this.AuthoredPayloads[this.Facade.PackageTuple.PayloadRef]; + + var mspPackage = (WixBundleMspPackageTuple)this.Facade.SpecificPackageTuple; - string sourcePath = packagePayload.FullFileName; + var sourcePath = packagePayload.SourceFile.Path; try { // Read data out of the msp database... - using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) + using (var sumInfo = new Dtf.SummaryInfo(sourcePath, false)) { - this.Facade.MspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38); + mspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38); } - using (Dtf.Database db = new Dtf.Database(sourcePath)) + using (var db = new Dtf.Database(sourcePath)) { - if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) + if (String.IsNullOrEmpty(this.Facade.PackageTuple.DisplayName)) { - this.Facade.Package.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName"); + this.Facade.PackageTuple.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName"); } - if (String.IsNullOrEmpty(this.Facade.Package.Description)) + if (String.IsNullOrEmpty(this.Facade.PackageTuple.Description)) { - this.Facade.Package.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description"); + this.Facade.PackageTuple.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description"); } - this.Facade.MspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName"); + mspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName"); } - this.ProcessPatchXml(packagePayload, sourcePath); + this.ProcessPatchXml(packagePayload, mspPackage, sourcePath); } catch (Dtf.InstallerException e) { - Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); + this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); return; } - if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) + if (String.IsNullOrEmpty(this.Facade.PackageTuple.CacheId)) { - this.Facade.Package.CacheId = this.Facade.MspPackage.PatchCode; + this.Facade.PackageTuple.CacheId = mspPackage.PatchCode; } } - private void ProcessPatchXml(WixBundlePayloadRow packagePayload, string sourcePath) + private void ProcessPatchXml(WixBundlePayloadTuple packagePayload, WixBundleMspPackageTuple mspPackage, string sourcePath) { - HashSet uniqueTargetCodes = new HashSet(); + var uniqueTargetCodes = new HashSet(); - string patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath); + var patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath); - XmlDocument doc = new XmlDocument(); + var doc = new XmlDocument(); doc.LoadXml(patchXml); - XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); + var nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); // Determine target ProductCodes and/or UpgradeCodes. foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) { // If this patch targets a product code, this is the best case. - XmlNode targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); - WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None; + var targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); + var attributes = WixBundlePatchTargetCodeAttributes.None; if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) { @@ -105,45 +119,49 @@ namespace WixToolset.Core.Burn.Bundles } else // this patch targets an unknown number of products { - this.Facade.MspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; + mspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; } } - string targetCode = targetCodeElement.InnerText; + var targetCode = targetCodeElement.InnerText; if (uniqueTargetCodes.Add(targetCode)) { - WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)this.WixBundlePatchTargetCodeTable.CreateRow(packagePayload.SourceLineNumbers); - row.MspPackageId = packagePayload.Id; - row.TargetCode = targetCode; - row.Attributes = attributes; + var tuple = new WixBundlePatchTargetCodeTuple(packagePayload.SourceLineNumbers) + { + PackageRef = packagePayload.Id.Id, + TargetCode = targetCode, + Attributes = attributes + }; + + this.Section.Tuples.Add(tuple); } } // Suppress patch sequence data for improved performance. - XmlNode root = doc.DocumentElement; + var root = doc.DocumentElement; foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) { root.RemoveChild(node); } // Save the XML as compact as possible. - using (StringWriter writer = new StringWriter()) + using (var writer = new StringWriter()) { - XmlWriterSettings settings = new XmlWriterSettings() + var settings = new XmlWriterSettings() { Encoding = ProcessMspPackageCommand.XmlOutputEncoding, Indent = false, - NewLineChars = string.Empty, + NewLineChars = String.Empty, NewLineHandling = NewLineHandling.Replace, }; - using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings)) + using (var xmlWriter = XmlWriter.Create(writer, settings)) { doc.WriteTo(xmlWriter); } - this.Facade.MspPackage.PatchXml = writer.ToString(); + mspPackage.PatchXml = writer.ToString(); } } @@ -175,16 +193,6 @@ namespace WixToolset.Core.Burn.Bundles return String.Format(CultureInfo.InvariantCulture, ProcessMspPackageCommand.PatchMetadataFormat, property); } - private static bool TargetsCode(XmlNode node) - { - if (null != node) - { - XmlAttribute attr = node.Attributes["Validate"]; - return null != attr && "true".Equals(attr.Value); - } - - return false; - } -#endif + private static bool TargetsCode(XmlNode node) => "true" == node?.Attributes["Validate"]?.Value; } } diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs index ef720bc1..6a39f42f 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs @@ -3,29 +3,35 @@ namespace WixToolset.Core.Burn.Bundles { using System; + using System.Collections.Generic; using WixToolset.Data; + using WixToolset.Data.Tuples; /// /// Processes the Msu packages to add properties and payloads from the Msu packages. /// internal class ProcessMsuPackageCommand { -#if TODO - public RowDictionary AuthoredPayloads { private get; set; } + public ProcessMsuPackageCommand(PackageFacade facade, Dictionary payloadTuples) + { + this.AuthoredPayloads = payloadTuples; + this.Facade = facade; + } + + public Dictionary AuthoredPayloads { private get; set; } public PackageFacade Facade { private get; set; } public void Execute() { - WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); + var packagePayload = this.AuthoredPayloads[this.Facade.PackageTuple.PayloadRef]; - if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) + if (String.IsNullOrEmpty(this.Facade.PackageTuple.CacheId)) { - this.Facade.Package.CacheId = packagePayload.Hash; + this.Facade.PackageTuple.CacheId = packagePayload.Hash; } - this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. + this.Facade.PackageTuple.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. } -#endif } } diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs index 80f3add3..0560e336 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs @@ -9,58 +9,73 @@ namespace WixToolset.Core.Burn.Bundles using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Tuples; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; internal class ProcessPayloadsCommand { -#if TODO private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); - public IEnumerable Payloads { private get; set; } + public ProcessPayloadsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, IEnumerable payloads, PackagingType defaultPackaging, string layoutDirectory) + { + this.Messaging = serviceProvider.GetService(); + + this.BackendHelper = backendHelper; + this.Payloads = payloads; + this.DefaultPackaging = defaultPackaging; + this.LayoutDirectory = layoutDirectory; + } + + public IEnumerable FileTransfers { get; private set; } - public PackagingType DefaultPackaging { private get; set; } + private IMessaging Messaging { get; } - public string LayoutDirectory { private get; set; } + private IBackendHelper BackendHelper { get; } - public IEnumerable FileTransfers { get; private set; } + private IEnumerable Payloads { get; } + + private PackagingType DefaultPackaging { get; } + + private string LayoutDirectory { get; } public void Execute() { - List fileTransfers = new List(); + var fileTransfers = new List(); - foreach (WixBundlePayloadRow payload in this.Payloads) + foreach (var payload in this.Payloads) { - string normalizedPath = payload.Name.Replace('\\', '/'); + var normalizedPath = payload.Name.Replace('\\', '/'); if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../")) { - Messaging.Instance.OnMessage(WixErrors.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name)); + this.Messaging.Write(ErrorMessages.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name)); } // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden // in the .wixlib). - ObjectField field = (ObjectField)payload.Fields[2]; - payload.ContentFile = !field.EmbeddedFileIndex.HasValue; + var sourceFile = payload.SourceFile; + payload.ContentFile = !sourceFile.EmbeddedFileIndex.HasValue; this.UpdatePayloadPackagingType(payload); - if (String.IsNullOrEmpty(payload.SourceFile)) + if (String.IsNullOrEmpty(sourceFile?.Path)) { // Remote payloads obviously cannot be embedded. Debug.Assert(PackagingType.Embedded != payload.Packaging); } else // not a remote payload so we have a lot more to update. { - this.UpdatePayloadFileInformation(payload); + this.UpdatePayloadFileInformation(payload, sourceFile); - this.UpdatePayloadVersionInformation(payload); + this.UpdatePayloadVersionInformation(payload, sourceFile); // External payloads need to be transfered. if (PackagingType.External == payload.Packaging) { - FileTransfer transfer; - if (FileTransfer.TryCreate(payload.FullFileName, Path.Combine(this.LayoutDirectory, payload.Name), false, "Payload", payload.SourceLineNumbers, out transfer)) - { - fileTransfers.Add(transfer); - } + var transfer = this.BackendHelper.CreateFileTransfer(sourceFile.Path, Path.Combine(this.LayoutDirectory, payload.Name), false, payload.SourceLineNumbers); + fileTransfers.Add(transfer); } } } @@ -68,41 +83,41 @@ namespace WixToolset.Core.Burn.Bundles this.FileTransfers = fileTransfers; } - private void UpdatePayloadPackagingType(WixBundlePayloadRow payload) + private void UpdatePayloadPackagingType(WixBundlePayloadTuple payload) { if (PackagingType.Unknown == payload.Packaging) { - if (YesNoDefaultType.Yes == payload.Compressed) + if (!payload.Compressed.HasValue) { - payload.Packaging = PackagingType.Embedded; + payload.Packaging = this.DefaultPackaging; } - else if (YesNoDefaultType.No == payload.Compressed) + else if (payload.Compressed.Value) { - payload.Packaging = PackagingType.External; + payload.Packaging = PackagingType.Embedded; } else { - payload.Packaging = this.DefaultPackaging; + payload.Packaging = PackagingType.External; } } // Embedded payloads that are not assigned a container already are placed in the default attached // container. - if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.Container)) + if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.ContainerRef)) { - payload.Container = Compiler.BurnDefaultAttachedContainerId; + payload.ContainerRef = BurnConstants.BurnDefaultAttachedContainerName; } } - private void UpdatePayloadFileInformation(WixBundlePayloadRow payload) + private void UpdatePayloadFileInformation(WixBundlePayloadTuple payload, IntermediateFieldPathValue sourceFile) { - FileInfo fileInfo = new FileInfo(payload.SourceFile); + var fileInfo = new FileInfo(sourceFile.Path); if (null != fileInfo) { payload.FileSize = (int)fileInfo.Length; - payload.Hash = Common.GetFileHash(fileInfo.FullName); + payload.Hash = BundleHashAlgorithm.Hash(fileInfo); // Try to get the certificate if the payload is a signed file and we're not suppressing signature validation. if (payload.EnableSignatureValidation) @@ -122,9 +137,10 @@ namespace WixToolset.Core.Burn.Bundles byte[] publicKeyIdentifierHash = new byte[128]; uint publicKeyIdentifierHashSize = (uint)publicKeyIdentifierHash.Length; - WixToolset.Core.Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize); - StringBuilder sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2); - for (int i = 0; i < publicKeyIdentifierHashSize; ++i) + Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize); + + var sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2); + for (var i = 0; i < publicKeyIdentifierHashSize; ++i) { sb.AppendFormat("{0:X2}", publicKeyIdentifierHash[i]); } @@ -136,14 +152,14 @@ namespace WixToolset.Core.Burn.Bundles } } - private void UpdatePayloadVersionInformation(WixBundlePayloadRow payload) + private void UpdatePayloadVersionInformation(WixBundlePayloadTuple payload, IntermediateFieldPathValue sourceFile) { - FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(payload.SourceFile); + var versionInfo = FileVersionInfo.GetVersionInfo(sourceFile.Path); if (null != versionInfo) { // Use the fixed version info block for the file since the resource text may not be a dotted quad. - Version version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); + var version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); if (ProcessPayloadsCommand.EmptyVersion != version) { @@ -154,6 +170,5 @@ namespace WixToolset.Core.Burn.Bundles payload.DisplayName = versionInfo.ProductName; } } -#endif } } diff --git a/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs b/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs index 82682a47..2b17f985 100644 --- a/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs @@ -9,31 +9,42 @@ namespace WixToolset.Core.Burn.Bundles using System.Runtime.InteropServices; using System.Text; using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Extensibility.Services; internal class VerifyPayloadsWithCatalogCommand { -#if TODO - public IEnumerable Catalogs { private get; set; } + public VerifyPayloadsWithCatalogCommand(IMessaging messaging, IEnumerable catalogs, IEnumerable payloads) + { + this.Messaging = messaging; + this.Catalogs = catalogs; + this.Payloads = payloads; + } + + private IMessaging Messaging { get; } + + private IEnumerable Catalogs { get; } - public IEnumerable Payloads { private get; set; } + private IEnumerable Payloads { get; } public void Execute() { - List catalogIdsWithPaths = this.Catalogs + var catalogIdsWithPaths = this.Catalogs .Join(this.Payloads, - catalog => catalog.Payload, - payload => payload.Id, - (catalog, payload) => new CatalogIdWithPath() { Id = catalog.Id, FullPath = Path.GetFullPath(payload.SourceFile) }) + catalog => catalog.PayloadRef, + payload => payload.Id.Id, + (catalog, payload) => new CatalogIdWithPath() { Id = catalog.Id.Id, FullPath = Path.GetFullPath(payload.SourceFile.Path) }) .ToList(); - foreach (WixBundlePayloadRow payloadInfo in this.Payloads) + foreach (var payloadInfo in this.Payloads) { // Payloads that are not embedded should be verfied. if (String.IsNullOrEmpty(payloadInfo.EmbeddedId)) { - bool validated = false; + var sourceFile = payloadInfo.SourceFile.Path; + var validated = false; - foreach (CatalogIdWithPath catalog in catalogIdsWithPaths) + foreach (var catalog in catalogIdsWithPaths) { if (!validated) { @@ -41,11 +52,10 @@ namespace WixToolset.Core.Burn.Bundles uint cryptHashSize = 20; byte[] cryptHashBytes = new byte[cryptHashSize]; int error; - IntPtr fileHandle = IntPtr.Zero; - using (FileStream payloadStream = File.OpenRead(payloadInfo.FullFileName)) + using (var payloadStream = File.OpenRead(sourceFile)) { // Get the file handle - fileHandle = payloadStream.SafeFileHandle.DangerousGetHandle(); + var fileHandle = payloadStream.SafeFileHandle.DangerousGetHandle(); // 20 bytes is usually the hash size. Future hashes may be bigger if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) @@ -64,7 +74,7 @@ namespace WixToolset.Core.Burn.Bundles if (0 != error) { - Messaging.Instance.OnMessage(WixErrors.CatalogFileHashFailed(payloadInfo.FullFileName, error)); + this.Messaging.Write(ErrorMessages.CatalogFileHashFailed(sourceFile, error)); } } } @@ -79,15 +89,15 @@ namespace WixToolset.Core.Burn.Bundles catalogData.pbCalculatedFileHash = Marshal.AllocCoTaskMem((int)cryptHashSize); Marshal.Copy(cryptHashBytes, 0, catalogData.pbCalculatedFileHash, (int)cryptHashSize); - StringBuilder hashString = new StringBuilder(); - foreach (byte hashByte in cryptHashBytes) + var hashString = new StringBuilder(); + foreach (var hashByte in cryptHashBytes) { hashString.Append(hashByte.ToString("X2")); } catalogData.pcwszMemberTag = hashString.ToString(); // The file names need to be lower case for older OSes - catalogData.pcwszMemberFilePath = payloadInfo.FullFileName.ToLowerInvariant(); + catalogData.pcwszMemberFilePath = sourceFile.ToLowerInvariant(); catalogData.pcwszCatalogFilePath = catalog.FullPath.ToLowerInvariant(); // Create WINTRUST_DATA structure @@ -108,7 +118,7 @@ namespace WixToolset.Core.Burn.Bundles long verifyResult = VerifyInterop.WinVerifyTrust(noWindow, ref verifyGuid, ref trustData); if (0 == verifyResult) { - payloadInfo.Catalog = catalog.Id; + payloadInfo.CatalogRef = catalog.Id; validated = true; break; } @@ -132,7 +142,7 @@ namespace WixToolset.Core.Burn.Bundles // Error message if the file was not validated by one of the catalogs if (!validated) { - Messaging.Instance.OnMessage(WixErrors.CatalogVerificationFailed(payloadInfo.FullFileName)); + this.Messaging.Write(ErrorMessages.CatalogVerificationFailed(sourceFile)); } } } @@ -144,6 +154,5 @@ namespace WixToolset.Core.Burn.Bundles public string FullPath { get; set; } } -#endif } } diff --git a/src/WixToolset.Core.Burn/BurnBackendFactory.cs b/src/WixToolset.Core.Burn/BurnBackendFactory.cs index 4b2e833f..03013a08 100644 --- a/src/WixToolset.Core.Burn/BurnBackendFactory.cs +++ b/src/WixToolset.Core.Burn/BurnBackendFactory.cs @@ -1,11 +1,10 @@ -// 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. +// 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.Burn { using System; using System.IO; using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; internal class BurnBackendFactory : IBackendFactory { diff --git a/src/WixToolset.Core.Burn/VerifyInterop.cs b/src/WixToolset.Core.Burn/VerifyInterop.cs index 81fbec65..f021f1d0 100644 --- a/src/WixToolset.Core.Burn/VerifyInterop.cs +++ b/src/WixToolset.Core.Burn/VerifyInterop.cs @@ -3,8 +3,6 @@ namespace WixToolset { using System; - using System.Collections; - using System.Runtime.CompilerServices; using System.Runtime.InteropServices; internal class VerifyInterop diff --git a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj index db413cbb..d9493b8a 100644 --- a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj +++ b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj @@ -15,6 +15,7 @@ + diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 830880ee..53451752 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -30,6 +30,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.BackendHelper = context.ServiceProvider.GetService(); + this.PathResolver = this.ServiceProvider.GetService(); + this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions(); this.CabbingThreadCount = context.CabbingThreadCount; @@ -54,6 +56,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IBackendHelper BackendHelper { get; } + private IPathResolver PathResolver { get; } + private int Codepage { get; } private int CabbingThreadCount { get; } @@ -241,7 +245,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Set generated component guids. { - var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, section); + var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, this.PathResolver, section); command.Execute(); } @@ -501,7 +505,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Process uncompressed files. if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) { - var command = new ProcessUncompressedFilesCommand(section, this.BackendHelper); + var command = new ProcessUncompressedFilesCommand(section, this.BackendHelper, this.PathResolver); command.Compressed = compressed; command.FileFacades = uncompressedFiles; command.LayoutDirectory = layoutDirectory; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs index 835d9b8d..8135ae2e 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs @@ -6,9 +6,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System.Collections.Generic; using System.IO; using System.Linq; - using WixToolset.Core.Native; using WixToolset.Data; using WixToolset.Data.Tuples; + using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; /// @@ -16,10 +16,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class CalculateComponentGuids { - internal CalculateComponentGuids(IMessaging messaging, IBackendHelper helper, IntermediateSection section) + internal CalculateComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section) { this.Messaging = messaging; this.BackendHelper = helper; + this.PathResolver = pathResolver; this.Section = section; } @@ -27,12 +28,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IBackendHelper BackendHelper { get; } + private IPathResolver PathResolver { get; } + private IntermediateSection Section { get; } public void Execute() { Dictionary registryKeyRows = null; - Dictionary targetPathsByDirectoryId = null; + Dictionary targetPathsByDirectoryId = null; Dictionary componentIdGenSeeds = null; Dictionary> filesByComponentId = null; @@ -73,7 +76,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var directories = this.Section.Tuples.OfType().ToList(); - targetPathsByDirectoryId = new Dictionary(directories.Count); + targetPathsByDirectoryId = new Dictionary(directories.Count); // Get the target paths for all directories. foreach (var directory in directories) @@ -86,7 +89,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind continue; } - targetPathsByDirectoryId.Add(directory.Id.Id, new ResolvedDirectory(directory.ParentDirectoryRef, directory.Name)); + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); + targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); } } @@ -131,7 +135,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (fileRow.Id.Id == componentTuple.KeyPath) { // calculate the key file's canonical target path - string directoryPath = PathResolver.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentTuple.DirectoryRef, true); + string directoryPath = this.PathResolver.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentTuple.DirectoryRef, true); string fileName = Common.GetName(fileRow.Name, false, true).ToLowerInvariant(); path = Path.Combine(directoryPath, fileName); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs index 95438f96..a9b0f5f5 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs @@ -8,7 +8,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System.IO; using System.Linq; using System.Runtime.InteropServices; - using System.Threading; using WixToolset.Core.Bind; using WixToolset.Data; using WixToolset.Data.Tuples; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index f76cd227..cd3a67fa 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -105,6 +105,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple, output); break; + case TupleDefinitionType.MsiFileHash: + this.AddMsiFileHashTuple((MsiFileHashTuple)tuple, output); + break; + case TupleDefinitionType.MsiServiceConfig: this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple, output); break; @@ -500,6 +504,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[4] = tuple.Source; } + private void AddMsiFileHashTuple(MsiFileHashTuple tuple, Output output) + { + var table = output.EnsureTable(this.TableDefinitions["MsiFileHash"]); + var row = table.CreateRow(tuple.SourceLineNumbers); + row[0] = tuple.Id.Id; + row[1] = tuple.Options; + row[2] = tuple.HashPart1; + row[3] = tuple.HashPart2; + row[4] = tuple.HashPart3; + row[5] = tuple.HashPart4; + } + private void AddMsiServiceConfigTuple(MsiServiceConfigTuple tuple, Output output) { var events = tuple.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs b/src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs deleted file mode 100644 index 6dc18271..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs +++ /dev/null @@ -1,106 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - - internal static class PathResolver - { - /// - /// Get the source path of a directory. - /// - /// All cached directories. - /// Hash table of Component GUID generation seeds indexed by directory id. - /// Directory identifier. - /// Canonicalize the path for standard directories. - /// Source path of a directory. - public static string GetDirectoryPath(Dictionary directories, Dictionary componentIdGenSeeds, string directory, bool canonicalize) - { - if (!directories.TryGetValue(directory, out var resolvedDirectory)) - { - throw new WixException(ErrorMessages.ExpectedDirectory(directory)); - } - - if (null == resolvedDirectory.Path) - { - if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(directory)) - { - resolvedDirectory.Path = componentIdGenSeeds[directory]; - } - else if (canonicalize && WindowsInstallerStandard.IsStandardDirectory(directory)) - { - // when canonicalization is on, standard directories are treated equally - resolvedDirectory.Path = directory; - } - else - { - string name = resolvedDirectory.Name; - - if (canonicalize) - { - name = name?.ToLowerInvariant(); - } - - if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) - { - resolvedDirectory.Path = name; - } - else - { - string parentPath = GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize); - - if (null != resolvedDirectory.Name) - { - resolvedDirectory.Path = Path.Combine(parentPath, name); - } - else - { - resolvedDirectory.Path = parentPath; - } - } - } - } - - return resolvedDirectory.Path; - } - - /// - /// Gets the source path of a file. - /// - /// All cached directories in . - /// Parent directory identifier. - /// File name (in long|source format). - /// Specifies the package is compressed. - /// Specifies the package uses long file names. - /// Source path of file relative to package directory. - public static string GetFileSourcePath(Dictionary directories, string directoryId, string fileName, bool compressed, bool useLongName) - { - string fileSourcePath = Common.GetName(fileName, true, useLongName); - - if (compressed) - { - // Use just the file name of the file since all uncompressed files must appear - // in the root of the image in a compressed package. - } - else - { - // Get the relative path of where we want the file to be layed out as specified - // in the Directory table. - string directoryPath = PathResolver.GetDirectoryPath(directories, null, directoryId, false); - fileSourcePath = Path.Combine(directoryPath, fileSourcePath); - } - - // Strip off "SourceDir" if it's still on there. - if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal)) - { - fileSourcePath = fileSourcePath.Substring(10); - } - - return fileSourcePath; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs index 61e82f68..64fb3e4d 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs @@ -18,16 +18,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class ProcessUncompressedFilesCommand { - public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper) + public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver) { this.Section = section; this.BackendHelper = backendHelper; + this.PathResolver = pathResolver; } private IntermediateSection Section { get; } public IBackendHelper BackendHelper { get; } + public IPathResolver PathResolver { get; } + public string DatabasePath { private get; set; } public IEnumerable FileFacades { private get; set; } @@ -50,7 +53,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var trackedFiles = new List(); - var directories = new Dictionary(); + var directories = new Dictionary(); var mediaRows = this.Section.Tuples.OfType().ToDictionary(t => t.DiskId); @@ -69,7 +72,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind string sourceName = Common.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage); - directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName); + + directories.Add(directoryRecord.GetString(1), resolvedDirectory); } } } @@ -99,7 +104,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.Id.Id)); } - relativeFileLayoutPath = PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); + relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); } // finally put together the base media layout path and the relative file layout path diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ResolvedDirectory.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ResolvedDirectory.cs deleted file mode 100644 index e06321cf..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ResolvedDirectory.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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.Bind -{ - /// - /// Structure used for resolved directory information. - /// - internal struct ResolvedDirectory - { - /// - /// Constructor for ResolvedDirectory. - /// - /// Parent directory. - /// The directory name. - public ResolvedDirectory(string directoryParent, string name) - { - this.DirectoryParent = directoryParent; - this.Name = name; - this.Path = null; - } - - /// The directory parent. - public string DirectoryParent { get; set; } - - /// The name of this directory. - public string Name { get; set; } - - /// The path of this directory. - public string Path { get; set; } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs index 397092c4..1f2a22d9 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -162,7 +162,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.Section.Tuples.Add(facade.Hash); } - facade.Hash.FileRef = facade.File.Id.Id; facade.Hash.Options = 0; facade.Hash.HashPart1 = hash[0]; facade.Hash.HashPart2 = hash[1]; diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs index c6e21973..644e5c63 100644 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs +++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs @@ -30,10 +30,10 @@ namespace WixToolset.Core.Bind { // If the uri to the file that contains the embedded file does not already have embedded files // being extracted, create the dictionary to track that. - if (!filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) + if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) { extracts = new SortedList(); - filesWithEmbeddedFiles.Add(uri, extracts); + this.filesWithEmbeddedFiles.Add(uri, extracts); } // If the embedded file is not already tracked in the dictionary of extracts, add it. @@ -52,7 +52,7 @@ namespace WixToolset.Core.Bind public IEnumerable GetExpectedEmbeddedFiles() { - foreach (var uriWithExtracts in filesWithEmbeddedFiles) + foreach (var uriWithExtracts in this.filesWithEmbeddedFiles) { foreach (var extracts in uriWithExtracts.Value) { @@ -68,7 +68,7 @@ namespace WixToolset.Core.Bind public IEnumerable GetExtractFilesForUri(Uri uri) { - if (!filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) + if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) { extracts = new SortedList(); } diff --git a/src/WixToolset.Core/Bind/FileResolver.cs b/src/WixToolset.Core/Bind/FileResolver.cs index a67d784d..b1676fad 100644 --- a/src/WixToolset.Core/Bind/FileResolver.cs +++ b/src/WixToolset.Core/Bind/FileResolver.cs @@ -70,11 +70,17 @@ namespace WixToolset.Core.Bind /// Optional type of source file being resolved. /// Optional source line of source file being resolved. /// The binding stage used to determine what collection of bind paths will be used + /// Optional collection of paths already checked. /// Should return a valid path for the stream to be imported. - public string ResolveFile(string source, IntermediateTupleDefinition tupleDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage) + public string ResolveFile(string source, IntermediateTupleDefinition tupleDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, IEnumerable alreadyCheckedPaths = null) { var checkedPaths = new List(); + if (alreadyCheckedPaths != null) + { + checkedPaths.AddRange(alreadyCheckedPaths); + } + foreach (var extension in this.ResolverExtensions) { var resolved = extension.ResolveFile(source, tupleDefinition, sourceLineNumbers, bindStage); diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs index bec03907..22710aca 100644 --- a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs @@ -113,7 +113,7 @@ namespace WixToolset.Core.Bind } } - public static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary resolutionData) + private static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary resolutionData) { var matches = Common.WixVariableRegex.Matches(value); diff --git a/src/WixToolset.Core/BindContext.cs b/src/WixToolset.Core/BindContext.cs index 413be301..7882b22d 100644 --- a/src/WixToolset.Core/BindContext.cs +++ b/src/WixToolset.Core/BindContext.cs @@ -1,4 +1,4 @@ -// 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. +// 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 { @@ -19,6 +19,8 @@ namespace WixToolset.Core public IEnumerable BindPaths { get; set; } + public string BurnStubPath { get; set; } + public int CabbingThreadCount { get; set; } public string CabCachePath { get; set; } diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index bfee2478..972258fe 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -122,7 +122,7 @@ namespace WixToolset.Core.CommandLine } else { - this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths); + this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths, this.commandLine.BurnStubPath); } } } @@ -257,7 +257,7 @@ namespace WixToolset.Core.CommandLine return linker.Link(context); } - private void BindPhase(Intermediate output, IEnumerable localizations, IEnumerable filterCultures, string cabCachePath, IEnumerable bindPaths) + private void BindPhase(Intermediate output, IEnumerable localizations, IEnumerable filterCultures, string cabCachePath, IEnumerable bindPaths, string burnStubPath) { var intermediateFolder = this.IntermediateFolder; if (String.IsNullOrEmpty(intermediateFolder)) @@ -290,6 +290,7 @@ namespace WixToolset.Core.CommandLine { var context = this.ServiceProvider.GetService(); //context.CabbingThreadCount = this.CabbingThreadCount; + context.BurnStubPath = burnStubPath; context.CabCachePath = cabCachePath; context.Codepage = resolveResult.Codepage; //context.DefaultCompressionLevel = this.DefaultCompressionLevel; @@ -399,6 +400,8 @@ namespace WixToolset.Core.CommandLine public List BindPaths { get; } = new List(); + public string BurnStubPath { get; private set; } + public string CabCachePath { get; private set; } public List Cultures { get; } = new List(); @@ -480,6 +483,10 @@ namespace WixToolset.Core.CommandLine } break; } + case "burnstub": + this.BurnStubPath = parser.GetNextArgumentOrError(arg); + return true; + case "cc": this.CabCachePath = parser.GetNextArgumentOrError(arg); return true; diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 0dade46d..3ee87872 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -818,7 +818,7 @@ namespace WixToolset.Core { this.Core.AddTuple(new IconTuple(sourceLineNumbers, id) { - Data = sourceFile + Data = new IntermediateFieldPathValue { Path = sourceFile } }); } diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 0ed49fbc..3be7d0c5 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -9,6 +9,7 @@ namespace WixToolset.Core using System.IO; using System.Xml.Linq; using WixToolset.Data; + using WixToolset.Data.Burn; using WixToolset.Data.Tuples; using WixToolset.Extensibility; @@ -17,15 +18,9 @@ namespace WixToolset.Core ///
internal partial class Compiler : ICompiler { - public static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Private, "WixUXContainer"); - public static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Private, "WixAttachedContainer"); - public static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Private, "BundleLayoutOnlyPayloads"); - - // The following constants must stay in sync with src\burn\engine\core.h - private const string BURN_BUNDLE_NAME = "WixBundleName"; - private const string BURN_BUNDLE_ORIGINAL_SOURCE = "WixBundleOriginalSource"; - private const string BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER = "WixBundleOriginalSourceFolder"; - private const string BURN_BUNDLE_LAST_USED_SOURCE = "WixBundleLastUsedSource"; + private static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Private, BurnConstants.BurnUXContainerName); + private static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Private, BurnConstants.BurnDefaultAttachedContainerName); + private static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Private, BurnConstants.BundleLayoutOnlyPayloadsName); /// /// Parses an ApprovedExeForElevation element. @@ -78,11 +73,11 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); } - var attributes = BundleApprovedExeForElevationAttributes.None; + var attributes = WixApprovedExeForElevationAttributes.None; if (win64 == YesNoType.Yes) { - attributes |= BundleApprovedExeForElevationAttributes.Win64; + attributes |= WixApprovedExeForElevationAttributes.Win64; } this.Core.ParseForExtensionElements(node); @@ -92,7 +87,7 @@ namespace WixToolset.Core var tuple = new WixApprovedExeForElevationTuple(sourceLineNumbers, id) { Key = key, - Value = valueName, + ValueName = valueName, Attributes = attributes }; @@ -110,8 +105,7 @@ namespace WixToolset.Core string copyright = null; string aboutUrl = null; var compressed = YesNoDefaultType.Default; - var disableModify = -1; - var disableRemove = YesNoType.NotSet; + WixBundleAttributes attributes = 0; string helpTelephone = null; string helpUrl = null; string manufacturer = null; @@ -152,13 +146,12 @@ namespace WixToolset.Core switch (value) { case "button": - disableModify = 2; + attributes |= WixBundleAttributes.SingleChangeUninstallButton; break; case "yes": - disableModify = 1; + attributes |= WixBundleAttributes.DisableModify; break; case "no": - disableModify = 0; break; default: this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no")); @@ -166,10 +159,10 @@ namespace WixToolset.Core } break; case "DisableRemove": - disableRemove = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "DisableRepair": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixBundleAttributes.DisableRemove; + } break; case "HelpTelephone": helpTelephone = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -239,13 +232,13 @@ namespace WixToolset.Core if (String.IsNullOrEmpty(name)) { - logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup.log"); + logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup:.log"); } else { // Ensure only allowable path characters are in "name" (and change spaces to underscores). fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), "_"); - logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ".log"); + logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":.log"); } this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; @@ -351,6 +344,37 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { + var tuple = new WixBundleTuple(sourceLineNumbers) + { + UpgradeCode = upgradeCode, + Version = version, + Copyright = copyright, + Name = name, + Manufacturer = manufacturer, + Attributes = attributes, + AboutUrl = aboutUrl, + HelpUrl = helpUrl, + HelpTelephone = helpTelephone, + UpdateUrl = updateUrl, + Compressed = YesNoDefaultType.Yes == compressed ? true : YesNoDefaultType.No == compressed ? (bool?)false : null, + IconSourceFile = iconSourceFile, + SplashScreenSourceFile = splashScreenSourceFile, + Condition = condition, + Tag = tag, + Platform = this.CurrentPlatform, + ParentName = parentName, + }; + + if (!String.IsNullOrEmpty(logVariablePrefixAndExtension)) + { + var split = logVariablePrefixAndExtension.Split(':'); + tuple.LogPathVariable = split[0]; + tuple.LogPrefix = split[1]; + tuple.LogExtension = split[2]; + } + + this.Core.AddTuple(tuple);; + if (null != upgradeCode) { this.Core.AddTuple(new WixRelatedBundleTuple(sourceLineNumbers) @@ -360,64 +384,32 @@ namespace WixToolset.Core }); } - this.Core.AddTuple(new WixBundleContainerTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, Compiler.BurnDefaultAttachedContainerId)) + this.Core.AddTuple(new WixBundleContainerTuple(sourceLineNumbers, Compiler.BurnDefaultAttachedContainerId) { Name = "bundle-attached.cab", Type = ContainerType.Attached }); - var bundleTuple = this.Core.CreateTuple(sourceLineNumbers, TupleDefinitionType.WixBundle); - bundleTuple.Set(0, version); - bundleTuple.Set(1, copyright); - bundleTuple.Set(2, name); - bundleTuple.Set(3, aboutUrl); - if (-1 != disableModify) - { - bundleTuple.Set(4, disableModify); - } - if (YesNoType.NotSet != disableRemove) - { - bundleTuple.Set(5, (YesNoType.Yes == disableRemove) ? 1 : 0); - } - // row.Set(6] - (deprecated) "disable repair" - bundleTuple.Set(7, helpTelephone); - bundleTuple.Set(8, helpUrl); - bundleTuple.Set(9, manufacturer); - bundleTuple.Set(10, updateUrl); - if (YesNoDefaultType.Default != compressed) - { - bundleTuple.Set(11, (YesNoDefaultType.Yes == compressed) ? 1 : 0); - } - - bundleTuple.Set(12, logVariablePrefixAndExtension); - bundleTuple.Set(13, iconSourceFile); - bundleTuple.Set(14, splashScreenSourceFile); - bundleTuple.Set(15, condition); - bundleTuple.Set(16, tag); - bundleTuple.Set(17, this.CurrentPlatform.ToString()); - bundleTuple.Set(18, parentName); - bundleTuple.Set(19, upgradeCode); - // Ensure that the bundle stores the well-known persisted values. - this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, Compiler.BURN_BUNDLE_NAME)) + this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, BurnConstants.BURN_BUNDLE_NAME)) { Hidden = false, Persisted = true }); - this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, Compiler.BURN_BUNDLE_ORIGINAL_SOURCE)) + this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE)) { Hidden = false, Persisted = true }); - this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, Compiler.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER)) + this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER)) { Hidden = false, Persisted = true }); - this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, Compiler.BURN_BUNDLE_LAST_USED_SOURCE)) + this.Core.AddTuple(new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, BurnConstants.BURN_BUNDLE_LAST_USED_SOURCE)) { Hidden = false, Persisted = true @@ -473,7 +465,7 @@ namespace WixToolset.Core this.Core.ParseForExtensionElements(node); - return YesNoType.Yes == disableLog ? null : String.Concat(variable, ":", logPrefix, logExtension); + return YesNoType.Yes == disableLog ? null : String.Join(":", variable, logPrefix, logExtension); } /// @@ -1126,9 +1118,9 @@ namespace WixToolset.Core tuple = new WixBundlePayloadTuple(sourceLineNumbers, id) { Name = String.IsNullOrEmpty(name) ? Path.GetFileName(sourceFile) : name, - SourceFile = sourceFile, + SourceFile = new IntermediateFieldPathValue { Path = sourceFile }, DownloadUrl = downloadUrl, - Compressed = compressed, + Compressed = (compressed == YesNoDefaultType.Yes) ? true : (compressed == YesNoDefaultType.No) ? (bool?)false : null, UnresolvedSourceFile = sourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. DisplayName = displayName, Description = description, @@ -1665,14 +1657,12 @@ namespace WixToolset.Core var vital = YesNoType.Yes; string installCommand = null; string repairCommand = null; - var repairable = YesNoType.NotSet; string uninstallCommand = null; var perMachine = YesNoDefaultType.NotSet; string detectCondition = null; string protocol = null; var installSize = CompilerConstants.IntegerNotSet; string msuKB = null; - var suppressLooseFilePayloadGeneration = YesNoType.NotSet; var enableSignatureVerification = YesNoType.No; var compressed = YesNoDefaultType.Default; var displayInternalUI = YesNoType.NotSet; @@ -1779,7 +1769,6 @@ namespace WixToolset.Core break; case "RepairCommand": repairCommand = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - repairable = YesNoType.Yes; allowed = (packageType == WixBundlePackageType.Exe); break; case "UninstallCommand": @@ -1808,11 +1797,6 @@ namespace WixToolset.Core case "Compressed": compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); break; - case "SuppressLooseFilePayloadGeneration": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - suppressLooseFilePayloadGeneration = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msi); - break; case "EnableSignatureVerification": enableSignatureVerification = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; @@ -2076,7 +2060,7 @@ namespace WixToolset.Core case WixBundlePackageType.Exe: this.Core.AddTuple(new WixBundleExePackageTuple(sourceLineNumbers, id) { - Attributes = (YesNoType.Yes == repairable) ? WixBundleExePackageAttributes.Repairable : 0, + Attributes = WixBundleExePackageAttributes.None, DetectCondition = detectCondition, InstallCommand = installCommand, RepairCommand = repairCommand, @@ -2090,7 +2074,6 @@ namespace WixToolset.Core msiAttributes |= (YesNoType.Yes == displayInternalUI) ? WixBundleMsiPackageAttributes.DisplayInternalUI : 0; msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0; msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0; - msiAttributes |= (YesNoType.Yes == suppressLooseFilePayloadGeneration) ? WixBundleMsiPackageAttributes.SuppressLooseFilePayloadGeneration : 0; this.Core.AddTuple(new WixBundleMsiPackageTuple(sourceLineNumbers, id) { @@ -2458,9 +2441,9 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { - var tuple = new WixBundleMsiPropertyTuple(sourceLineNumbers) + var tuple = new WixBundleMsiPropertyTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, packageId, name)) { - WixBundlePackageRef = packageId, + PackageRef = packageId, Name = name, Value = value }; @@ -2514,10 +2497,10 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { - this.Core.AddTuple(new WixBundleSlipstreamMspTuple(sourceLineNumbers) + this.Core.AddTuple(new WixBundleSlipstreamMspTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, packageId, id)) { - WixBundlePackageRef = packageId, - MspWixBundlePackageRef = id + TargetPackageRef = packageId, + MspPackageRef = id }); } } diff --git a/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs index 6cc91487..0bdecf7a 100644 --- a/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs @@ -1,4 +1,4 @@ -// 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. +// 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.ExtensibilityServices { @@ -40,6 +40,15 @@ namespace WixToolset.Core.ExtensibilityServices return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); } + public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) + { + return new ResolvedDirectory + { + DirectoryParent = directoryParent, + Name = name + }; + } + public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) { return new TrackedFile(path, type, sourceLineNumbers); diff --git a/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs b/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs new file mode 100644 index 00000000..15cd4fc9 --- /dev/null +++ b/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs @@ -0,0 +1,91 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class PathResolver : IPathResolver + { + public string GetDirectoryPath(Dictionary directories, Dictionary componentIdGenSeeds, string directory, bool canonicalize) + { + if (!directories.TryGetValue(directory, out var resolvedDirectory)) + { + throw new WixException(ErrorMessages.ExpectedDirectory(directory)); + } + + if (null == resolvedDirectory.Path) + { + if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(directory)) + { + resolvedDirectory.Path = componentIdGenSeeds[directory]; + } + else if (canonicalize && WindowsInstallerStandard.IsStandardDirectory(directory)) + { + // when canonicalization is on, standard directories are treated equally + resolvedDirectory.Path = directory; + } + else + { + string name = resolvedDirectory.Name; + + if (canonicalize) + { + name = name?.ToLowerInvariant(); + } + + if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) + { + resolvedDirectory.Path = name; + } + else + { + var parentPath = this.GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize); + + if (null != resolvedDirectory.Name) + { + resolvedDirectory.Path = Path.Combine(parentPath, name); + } + else + { + resolvedDirectory.Path = parentPath; + } + } + } + } + + return resolvedDirectory.Path; + } + + public string GetFileSourcePath(Dictionary directories, string directoryId, string fileName, bool compressed, bool useLongName) + { + var fileSourcePath = Common.GetName(fileName, true, useLongName); + + if (compressed) + { + // Use just the file name of the file since all uncompressed files must appear + // in the root of the image in a compressed package. + } + else + { + // Get the relative path of where we want the file to be layed out as specified + // in the Directory table. + var directoryPath = this.GetDirectoryPath(directories, null, directoryId, false); + fileSourcePath = Path.Combine(directoryPath, fileSourcePath); + } + + // Strip off "SourceDir" if it's still on there. + if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal)) + { + fileSourcePath = fileSourcePath.Substring(10); + } + + return fileSourcePath; + } + } +} diff --git a/src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs b/src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs new file mode 100644 index 00000000..cc8acfdd --- /dev/null +++ b/src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs @@ -0,0 +1,15 @@ +// 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.ExtensibilityServices +{ + using WixToolset.Extensibility.Data; + + internal class ResolvedDirectory : IResolvedDirectory + { + public string DirectoryParent { get; set; } + + public string Name { get; set; } + + public string Path { get; set; } + } +} diff --git a/src/WixToolset.Core/ExtensibilityServices/WindowsInstallerBackendHelper.cs b/src/WixToolset.Core/ExtensibilityServices/WindowsInstallerBackendHelper.cs index 6e0ffce6..26982ad6 100644 --- a/src/WixToolset.Core/ExtensibilityServices/WindowsInstallerBackendHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/WindowsInstallerBackendHelper.cs @@ -1,8 +1,7 @@ -// 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. +// 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.ExtensibilityServices { - using System; using System.Linq; using WixToolset.Data; using WixToolset.Data.WindowsInstaller; @@ -10,14 +9,9 @@ namespace WixToolset.Core.ExtensibilityServices internal class WindowsInstallerBackendHelper : IWindowsInstallerBackendHelper { - public WindowsInstallerBackendHelper(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } + public bool TryAddTupleToOutputMatchingTableDefinitions(IntermediateTuple tuple, Output output, TableDefinition[] tableDefinitions) => this.TryAddTupleToOutputMatchingTableDefinitions(tuple, output, tableDefinitions, false); - private IServiceProvider ServiceProvider { get; } - - public bool TryAddTupleToOutputMatchingTableDefinitions(IntermediateTuple tuple, Output output, TableDefinition[] tableDefinitions) + public bool TryAddTupleToOutputMatchingTableDefinitions(IntermediateTuple tuple, Output output, TableDefinition[] tableDefinitions, bool columnZeroIsId) { var tableDefinition = tableDefinitions.FirstOrDefault(t => t.Name == tuple.Definition.Name); @@ -28,6 +22,14 @@ namespace WixToolset.Core.ExtensibilityServices var table = output.EnsureTable(tableDefinition); var row = table.CreateRow(tuple.SourceLineNumbers); + var rowOffset = 0; + + if (columnZeroIsId) + { + row[0] = tuple.Id.Id; + rowOffset = 1; + } + for (var i = 0; i < tuple.Fields.Length; ++i) { if (i < tableDefinition.Columns.Length) @@ -36,13 +38,13 @@ namespace WixToolset.Core.ExtensibilityServices switch (column.Type) { - case ColumnType.Number: - row[i] = tuple.AsNumber(i); - break; + case ColumnType.Number: + row[i + rowOffset] = column.Nullable ? tuple.AsNullableNumber(i) : tuple.AsNumber(i); + break; - default: - row[i] = tuple.AsString(i); - break; + default: + row[i + rowOffset] = tuple.AsString(i); + break; } } } diff --git a/src/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/WixToolset.Core/Link/WixGroupingOrdering.cs index 9080775e..563cd565 100644 --- a/src/WixToolset.Core/Link/WixGroupingOrdering.cs +++ b/src/WixToolset.Core/Link/WixGroupingOrdering.cs @@ -12,19 +12,19 @@ namespace WixToolset.Core.Link using WixToolset.Data; using WixToolset.Data.Tuples; using WixToolset.Extensibility.Services; + using WixToolset.Data.Burn; /// /// Grouping and Ordering class of the WiX toolset. /// internal class WixGroupingOrdering { - private readonly IMessaging messageHandler; + private readonly IMessaging Messaging; private List groupTypes; private List itemTypes; private ItemCollection items; private readonly List rowsUsed; private bool loaded; - private bool encounteredError; /// /// Creates a WixGroupingOrdering object. @@ -36,11 +36,10 @@ namespace WixToolset.Core.Link public WixGroupingOrdering(IntermediateSection entrySections, IMessaging messageHandler) { this.EntrySection = entrySections; - this.messageHandler = messageHandler; + this.Messaging = messageHandler; this.rowsUsed = new List(); this.loaded = false; - this.encounteredError = false; } private IntermediateSection EntrySection { get; } @@ -71,7 +70,7 @@ namespace WixToolset.Core.Link Debug.Assert(this.groupTypes.Contains(parentTypeString)); this.CreateOrderedList(parentTypeString, parentId, out var orderedItems); - if (this.encounteredError) + if (this.Messaging.EncounteredError) { return; } @@ -95,7 +94,7 @@ namespace WixToolset.Core.Link Debug.Assert(this.groupTypes.Contains(parentTypeString)); this.LoadFlattenOrderGroups(); - if (this.encounteredError) + if (this.Messaging.EncounteredError) { return; } @@ -127,14 +126,14 @@ namespace WixToolset.Core.Link orderedItems = null; this.LoadFlattenOrderGroups(); - if (this.encounteredError) + if (this.Messaging.EncounteredError) { return; } if (!this.items.TryGetValue(parentType, parentId, out var parentItem)) { - this.messageHandler.Write(ErrorMessages.IdentifierNotFound(parentType, parentId)); + this.Messaging.Write(ErrorMessages.IdentifierNotFound(parentType, parentId)); return; } @@ -216,7 +215,7 @@ namespace WixToolset.Core.Link // dependencies. Group references, however, we can check directly. this.FindCircularGroupReferences(); - if (!this.encounteredError) + if (!this.Messaging.EncounteredError) { this.FlattenGroups(); this.FlattenOrdering(); @@ -304,7 +303,7 @@ namespace WixToolset.Core.Link if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference)) { itemsInKnownLoops.Add(itemsSeen); - this.messageHandler.Write(ErrorMessages.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference)); + this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference)); } } } @@ -376,12 +375,12 @@ namespace WixToolset.Core.Link if (!this.items.TryGetValue(rowItemType, rowItemName, out var item)) { - this.messageHandler.Write(ErrorMessages.IdentifierNotFound(rowItemType, rowItemName)); + this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowItemType, rowItemName)); } if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out var dependsOn)) { - this.messageHandler.Write(ErrorMessages.IdentifierNotFound(rowDependsOnType, rowDependsOnName)); + this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowDependsOnType, rowDependsOnName)); } if (null == item || null == dependsOn) @@ -389,7 +388,7 @@ namespace WixToolset.Core.Link continue; } - item.AddAfter(dependsOn, this.messageHandler); + item.AddAfter(dependsOn, this.Messaging); } } @@ -404,12 +403,12 @@ namespace WixToolset.Core.Link // ordering. foreach (Item item in this.items) { - item.PropagateAfterToChildItems(this.messageHandler); + item.PropagateAfterToChildItems(this.Messaging); } foreach (Item item in this.items) { - item.FlattenAfters(this.messageHandler); + item.FlattenAfters(this.Messaging); } } @@ -668,7 +667,7 @@ namespace WixToolset.Core.Link { if (String.Equals(nameof(ComplexReferenceChildType.Package), this.Type, StringComparison.Ordinal) || (String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal) && - !String.Equals(Compiler.BurnUXContainerId.Id, this.Id, StringComparison.Ordinal))) + !String.Equals(BurnConstants.BurnUXContainerName, this.Id, StringComparison.Ordinal))) { return false; } diff --git a/src/WixToolset.Core/WixToolsetServiceProvider.cs b/src/WixToolset.Core/WixToolsetServiceProvider.cs index 267e4524..c7d6ff1d 100644 --- a/src/WixToolset.Core/WixToolsetServiceProvider.cs +++ b/src/WixToolset.Core/WixToolsetServiceProvider.cs @@ -24,7 +24,8 @@ namespace WixToolset.Core this.AddService((provider, singletons) => AddSingleton(singletons, new ParseHelper(provider))); this.AddService((provider, singletons) => AddSingleton(singletons, new PreprocessHelper(provider))); this.AddService((provider, singletons) => AddSingleton(singletons, new BackendHelper(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new WindowsInstallerBackendHelper(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new PathResolver())); + this.AddService((provider, singletons) => AddSingleton(singletons, new WindowsInstallerBackendHelper())); // Transients. this.AddService((provider, singletons) => new CommandLineArguments(provider)); @@ -47,6 +48,7 @@ namespace WixToolset.Core this.AddService((provider, singletons) => new DecompileResult()); this.AddService((provider, singletons) => new IncludedFile()); this.AddService((provider, singletons) => new PreprocessResult()); + this.AddService((provider, singletons) => new ResolvedDirectory()); this.AddService((provider, singletons) => new ResolveFileResult()); this.AddService((provider, singletons) => new ResolveResult()); this.AddService((provider, singletons) => new ResolvedCabinet()); diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs new file mode 100644 index 00000000..554f4b17 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -0,0 +1,52 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using Xunit; + + public class BundleFixture + { + [Fact] + public void CanBuildSimpleBundle() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Bundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-burnStub", burnStubPath, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); +#if TODO + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); +#endif + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + var msiTuple = section.Tuples.OfType().Single(); + Assert.Equal("test.msi", msiTuple.Id.Id ); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe b/src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe new file mode 100644 index 00000000..2a4f423f Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl new file mode 100644 index 00000000..bc1dee83 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl @@ -0,0 +1,10 @@ + + + + + + ~TestBundle + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs new file mode 100644 index 00000000..89dbb503 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll new file mode 100644 index 00000000..0e461ba8 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll @@ -0,0 +1 @@ +This is Shared.dll. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt new file mode 100644 index 00000000..8b986220 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt @@ -0,0 +1 @@ +This is test.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll new file mode 100644 index 00000000..970efdf0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll @@ -0,0 +1 @@ +This is a fakeba.dll \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi new file mode 100644 index 00000000..0722d60e Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi differ diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index a5eadae3..65034159 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -13,6 +13,7 @@ + @@ -31,6 +32,9 @@ + + + @@ -66,6 +70,9 @@ + + + -- cgit v1.2.3-55-g6feb From 59a8dadcee33343dd724e4e048f0a471f314452f Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 7 Oct 2019 14:41:09 -0700 Subject: Write Font table correctly --- .../Bind/CreateOutputFromIRCommand.cs | 8 ++++++++ src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 2 +- .../WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 75a694d1..13db96fa 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -445,6 +445,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind attributes |= (tuple.Attributes & FileTupleAttributes.System) == FileTupleAttributes.System ? WindowsInstallerConstants.MsidbFileAttributesSystem : 0; attributes |= (tuple.Attributes & FileTupleAttributes.Vital) == FileTupleAttributes.Vital ? WindowsInstallerConstants.MsidbFileAttributesVital : 0; row.Attributes = attributes; + + if (!String.IsNullOrEmpty(tuple.FontTitle)) + { + var fontTable = output.EnsureTable(this.TableDefinitions["Font"]); + var fontRow = fontTable.CreateRow(tuple.SourceLineNumbers); + fontRow[0] = tuple.Id.Id; + fontRow[1] = tuple.FontTitle; + } } private void AddIniFileTuple(IniFileTuple tuple, Output output) diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index c391abac..1ab51714 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -294,7 +294,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] + [Fact] public void PopulatesFontTable() { var folder = TestData.Get(@"TestData"); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs index b7899b87..6fb9ef05 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs @@ -3,7 +3,7 @@ - + -- cgit v1.2.3-55-g6feb From 32ac87b2bf1a3a5bddb0630b65e52d117e5e2323 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 7 Oct 2019 14:41:32 -0700 Subject: Fix ServiceInstall test --- src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 2 +- .../TestData/ServiceInstall/OwnProcess.wxs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 1ab51714..cb798777 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -513,7 +513,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] + [Fact] public void PopulatesServiceInstallTable() { var folder = TestData.Get(@"TestData"); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs index f308335e..596e98a6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs @@ -3,7 +3,7 @@ - + -- cgit v1.2.3-55-g6feb From 664ce5ac707905b631f9a752cab0d2dc1b7d6edc Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 11 Oct 2019 14:59:56 +1000 Subject: Add failing test for CustomTable. --- .../MsiQueryFixture.cs | 34 ++++++++++++++++++++++ .../TestData/CustomTable/CustomTable.wxs | 21 +++++++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 56 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 724126bb..950ac40c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -217,6 +217,40 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesCustomTable1() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTable1" }); + Assert.Equal(new[] + { + "CustomTable1:Row1\ttest.txt", + "CustomTable1:Row2\ttest.txt", + }, results); + } + } + [Fact] public void PopulatesDirectoryTableWithValidDefaultDir() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs new file mode 100644 index 00000000..649b29b6 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + Row1 + test.txt + + + Row2 + test.txt + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 65034159..b8e7c213 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -19,6 +19,7 @@ + -- cgit v1.2.3-55-g6feb From cef530d2414767fa8523dae4beb5de4db5edd6f5 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 11 Oct 2019 15:17:10 +1000 Subject: Add failing test for RegistryValue. --- .../MsiQueryFixture.cs | 32 ++++++++++++++++++++++ .../TestData/Registry/RegistryValue.wxs | 10 +++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 43 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 950ac40c..00a573d4 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -514,6 +514,38 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesRegistryTableFromRegistryValue() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RegistryValue.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + Assert.Equal(new[] + { + "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent", + }, results); + } + } + [Fact] public void PopulatesReserveCostTable() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs new file mode 100644 index 00000000..3d88d4cd --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index b8e7c213..f9d1f5c5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -30,6 +30,7 @@ + -- cgit v1.2.3-55-g6feb From 9dbf5fbb89d146f1ced4a36072a66b9a24fb9015 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 15 Oct 2019 10:07:00 +1000 Subject: Add failing test for Class when IconIndex is 0. --- .../MsiQueryFixture.cs | 35 ++++++++++++++++++++++ .../TestData/Class/IconIndex0.wxs | 11 +++++++ .../TestData/Icon/SampleIcon.wxs | 6 ++++ .../WixToolsetTest.CoreIntegration.csproj | 2 ++ 4 files changed, 54 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index bbce87cd..d5dcba54 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -146,6 +146,41 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesClassTablesWhenIconIndexIsZero() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Class", "IconIndex0.wxs"), + Path.Combine(folder, "Icon", "SampleIcon.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Class" }); + Assert.Equal(new[] + { + "Class:{3FAED4CC-C473-4B8A-BE8B-303871377A4A}\tLocalServer32\tClassComp\t\tFakeClass3FAE\t\t\tSampleIcon\t0\t\t\tProductFeature\t", + }, results); + } + } + [Fact(Skip = "Test demonstrates failure")] public void PopulatesClassTablesWhenProgIdIsNestedUnderAdvertisedClass() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs new file mode 100644 index 00000000..c0dc9bc0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs new file mode 100644 index 00000000..1de84e81 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index f9d1f5c5..f0b2e271 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -18,6 +18,7 @@ + @@ -27,6 +28,7 @@ + -- cgit v1.2.3-55-g6feb From 868956398ebb9ac6cc47266b185ec39c94b57b6e Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 15 Oct 2019 10:52:02 +1000 Subject: Add failing test for TextStyle when Color is null. --- .../MsiQueryFixture.cs | 33 ++++++++++++++++++++++ .../TestData/TextStyle/ColorNull.wxs | 12 ++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 46 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index d5dcba54..6fea6e36 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -647,6 +647,39 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesTextStyleTableWhenColorIsNull() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "TextStyle", "ColorNull.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); + Assert.Equal(new[] + { + "TextStyle:FirstTextStyle\tArial\t2\t\t", + }, results); + } + } + [Fact] public void PopulatesUpgradeTableFromManualUpgrade() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs new file mode 100644 index 00000000..669de6ec --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index f0b2e271..0624f6ae 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -113,6 +113,7 @@ + -- cgit v1.2.3-55-g6feb From aa5a5e492f9a7060720056c27963cf1d06b4737f Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 15 Oct 2019 11:12:57 +1000 Subject: Add failing test for AsString on a number field with 0. --- .../MsiQueryFixture.cs | 32 +++++++++++++++++ .../TestData/TypeLib/Language0.wxs | 11 ++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + .../WixlibQueryFixture.cs | 40 ++++++++++++++++++++-- 4 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 6fea6e36..e2a672b7 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -680,6 +680,38 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void PopulatesTypeLibTableWhenLanguageIsZero() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "TypeLib", "Language0.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "TypeLib" }); + Assert.Equal(new[] + { + "TypeLib:{765BE8EE-BD7F-491E-90D2-C5A972462B50}\t0\tTypeLibComp\t\t\t\tProductFeature\t", + }, results); + } + } + [Fact] public void PopulatesUpgradeTableFromManualUpgrade() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs new file mode 100644 index 00000000..fa64f98f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 0624f6ae..770f528a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -114,6 +114,7 @@ + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs index 88491eac..061eae07 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs @@ -21,18 +21,19 @@ namespace WixToolsetTest.CoreIntegration { var baseFolder = fs.GetFolder(); var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); var result = WixRunner.Execute(new[] { "build", Path.Combine(folder, "DetectOnly.wxs"), "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"test.wixlib"), + "-o", wixlibPath, }); result.AssertSuccess(); - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wixlib")); + var intermediate = Intermediate.Load(wixlibPath); var allTuples = intermediate.Sections.SelectMany(s => s.Tuples); var wixSimpleRefTuples = allTuples.OfType(); var repRef = wixSimpleRefTuples.Where(t => t.Table == "WixAction" && @@ -41,5 +42,40 @@ namespace WixToolsetTest.CoreIntegration Assert.NotNull(repRef); } } + + [Fact(Skip = "Test demonstrates failure")] + public void TypeLibLanguageAsStringReturnsZero() + { + var folder = TestData.Get(@"TestData\TypeLib"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Language0.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixlibPath); + var allTuples = intermediate.Sections.SelectMany(s => s.Tuples); + var typeLibTuple = allTuples.OfType() + .SingleOrDefault(); + Assert.NotNull(typeLibTuple); + + var fields = typeLibTuple.Fields.Select(field => field?.Type == IntermediateFieldType.Bool + ? field.AsNullableNumber()?.ToString() + : field?.AsString()) + .ToList(); + Assert.Equal("0", fields[1]); + } + } } } -- cgit v1.2.3-55-g6feb From 9ca5e0a95d0858a177fe1efdc15a962e5f7c1d84 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 18 Oct 2019 10:50:34 +1000 Subject: Add failing test for decompiler with database that has old schemas and missing foreign key relationships. --- .../DecompileFixture.cs | 31 +++++++++++++++++++++ .../TestData/Class/DecompiledOldClassTableDef.wxs | 27 ++++++++++++++++++ .../TestData/Class/OldClassTableDef.msi | Bin 0 -> 36864 bytes .../WixToolsetTest.CoreIntegration.csproj | 2 ++ 4 files changed, 60 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs index 3a9781df..bace97b3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -65,5 +65,36 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(expected, actualFormatted); } } + + [Fact(Skip = "Test demonstrates failure")] + public void CanDecompileOldClassTableDefinition() + { + // The input MSI was not created using standard methods, it is an example of a real world database that needs to be decompiled. + // The Class/@Feature_ column has length of 32, the File/@Attributes has length of 2, + // and numerous foreign key relationships are missing. + var folder = TestData.Get(@"TestData\Class"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); + + var result = WixRunner.Execute(new[] + { + "decompile", + Path.Combine(folder, "OldClassTableDef.msi"), + "-intermediateFolder", intermediateFolder, + "-o", outputPath + }); + + result.AssertSuccess(); + + var actual = File.ReadAllText(outputPath); + var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + var expected = XDocument.Load(Path.Combine(folder, "DecompiledOldClassTableDef.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + + Assert.Equal(expected, actualFormatted); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs new file mode 100644 index 00000000..86d41c50 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi new file mode 100644 index 00000000..2cd10f09 Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi differ diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 770f528a..ab6f8d98 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -18,7 +18,9 @@ + + -- cgit v1.2.3-55-g6feb From f76653eab39c2df233d1d884542241c9aea4c5ff Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Wed, 23 Oct 2019 18:36:06 -0400 Subject: Undo CreateTuple change and add ErrorTuple test. Strongly-typed tuples are preferred and avoid field-zero/id confusion. --- .../ExtensibilityServices/ParseHelper.cs | 12 ++++++++ .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 36 ++++++++++++++++++++++ .../TestData/ErrorsInUI/Package.en-us.wxl | 9 ++++++ .../TestData/ErrorsInUI/Package.wxs | 21 +++++++++++++ .../TestData/ErrorsInUI/PackageComponents.wxs | 15 +++++++++ .../TestData/ErrorsInUI/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 +++ 7 files changed, 98 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs index 67a78a56..8fbfdd87 100644 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs @@ -913,6 +913,18 @@ namespace WixToolset.Core.ExtensibilityServices { var tuple = tupleDefinition.CreateTuple(sourceLineNumbers, identifier); + if (null != identifier) + { + if (tuple.Definition.FieldDefinitions[0].Type == IntermediateFieldType.Number) + { + tuple.Set(0, Convert.ToInt32(identifier.Id)); + } + else + { + tuple.Set(0, identifier.Id); + } + } + section.Tuples.Add(tuple); return tuple; diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 2d6feb4e..d056a1d6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -247,6 +247,42 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildWithErrorTable() + { + var folder = TestData.Get(@"TestData\ErrorsInUI"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + var error = section.Tuples.OfType().Single(); + Assert.Equal(1234, error.Error); + Assert.Equal("Category 55 Emergency Doomsday Crisis", error.Message.Trim()); + } + } + [Fact] public void CanLoadPdbGeneratedByBuild() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl new file mode 100644 index 00000000..066e16bb --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl @@ -0,0 +1,9 @@ + + + + + A newer version of [ProductName] is already installed. + MsiPackage + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs new file mode 100644 index 00000000..6da3dcbe --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs new file mode 100644 index 00000000..db128695 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs @@ -0,0 +1,15 @@ + + + + + + Category 55 Emergency Doomsday Crisis + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index ab6f8d98..1f8860ef 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -28,6 +28,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 9c714a8f1baa6e0130e5cd00cbdca649cebaf6a5 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 25 Oct 2019 00:48:35 -0700 Subject: Update to WixOutput file structure to fix embedded file handling --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 2 +- .../Bundles/ProcessPayloadsCommand.cs | 2 +- src/WixToolset.Core.TestPackage/WixRunner.cs | 2 +- .../Bind/BindDatabaseCommand.cs | 66 ++++++++++++---- src/WixToolset.Core.WindowsInstaller/MsiBackend.cs | 22 +++--- src/WixToolset.Core.WindowsInstaller/MsmBackend.cs | 31 +++----- src/WixToolset.Core/Bind/ExpectedExtractFile.cs | 4 +- src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | 38 +++++---- .../Bind/ExtractEmbeddedFilesCommand.cs | 36 ++------- src/WixToolset.Core/Bind/ResolveFieldsCommand.cs | 4 +- src/WixToolset.Core/CommandLine/BuildCommand.cs | 38 +++++---- src/WixToolset.Core/Librarian.cs | 14 +--- src/WixToolset.Core/Linker.cs | 2 +- src/test/Example.Extension/Data/example.wir | Bin 588 -> 535 bytes .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 87 ++++++++++++++++++--- .../TestData/SameFileFolders/TestComponents.wxs | 16 ++++ .../TestData/SameFileFolders/data/a/test.txt | 1 + .../TestData/SameFileFolders/data/b/test.txt | 1 + .../TestData/SameFileFolders/data/c/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 + .../WixiplFixture.cs | 12 +-- 21 files changed, 243 insertions(+), 140 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 6b4b9d68..c2164744 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -462,7 +462,7 @@ namespace WixToolset.Core.Burn this.TrackedFiles = trackedFiles; // TODO: Eventually this gets removed - var intermediate = new Intermediate(this.Output.Id, new[] { section }, this.Output.Localizations.ToDictionary(l => l.Culture, StringComparer.OrdinalIgnoreCase), this.Output.EmbedFilePaths); + var intermediate = new Intermediate(this.Output.Id, new[] { section }, this.Output.Localizations.ToDictionary(l => l.Culture, StringComparer.OrdinalIgnoreCase)); var trackIntermediate = this.BackendHelper.TrackFile(Path.Combine(this.IntermediateFolder, Path.GetFileName(Path.ChangeExtension(this.OutputPath, "wir"))), TrackedFileType.Intermediate); intermediate.Save(trackIntermediate.Path); trackedFiles.Add(trackIntermediate); diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs index 0560e336..17251143 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs @@ -56,7 +56,7 @@ namespace WixToolset.Core.Burn.Bundles // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden // in the .wixlib). var sourceFile = payload.SourceFile; - payload.ContentFile = !sourceFile.EmbeddedFileIndex.HasValue; + payload.ContentFile = !sourceFile.Embed; this.UpdatePayloadPackagingType(payload); diff --git a/src/WixToolset.Core.TestPackage/WixRunner.cs b/src/WixToolset.Core.TestPackage/WixRunner.cs index ab5045fa..d2202328 100644 --- a/src/WixToolset.Core.TestPackage/WixRunner.cs +++ b/src/WixToolset.Core.TestPackage/WixRunner.cs @@ -16,7 +16,7 @@ namespace WixToolset.Core.TestPackage return Execute(args, serviceProvider, out messages); } - public static WixRunnerResult Execute(string[] args) + public static WixRunnerResult Execute(params string[] args) { var serviceProvider = new WixToolsetServiceProvider(); var exitCode = Execute(args, serviceProvider, out var messages); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 411f64bf..3e4806a7 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -17,11 +17,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// /// Binds a databse. /// - internal class BindDatabaseCommand + internal class BindDatabaseCommand : IDisposable { // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); + private bool disposed; + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, Validator validator) { this.ServiceProvider = context.ServiceProvider; @@ -92,7 +94,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind public IEnumerable TrackedFiles { get; private set; } - public Pdb Pdb { get; private set; } + public WixOutput Wixout { get; private set; } public void Execute() { @@ -524,29 +526,41 @@ namespace WixToolset.Core.WindowsInstaller.Bind trackedFiles.AddRange(command.TrackedFiles); } - this.Pdb = new Pdb { Output = output }; - - if (!String.IsNullOrEmpty(this.OutputPdbPath)) - { - var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); - trackedFiles.Add(trackPdb); - - this.Pdb.Save(trackPdb.Path); - } + this.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, output); this.FileTransfers = fileTransfers; // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables). - trackedFiles.AddRange(fileFacades.Select(f => this.BackendHelper.TrackFile(f.File.Source.Path, TrackedFileType.Input, f.File.SourceLineNumbers))); + trackedFiles.AddRange(fileFacades.Select(f => this.BackendHelper.TrackFile(f.File.Source.Path, TrackedFileType.Input, f.File.SourceLineNumbers))); this.TrackedFiles = trackedFiles; // TODO: Eventually this gets removed - var intermediate = new Intermediate(this.Intermediate.Id, new[] { section }, this.Intermediate.Localizations.ToDictionary(l => l.Culture, StringComparer.OrdinalIgnoreCase), this.Intermediate.EmbedFilePaths); + var intermediate = new Intermediate(this.Intermediate.Id, new[] { section }, this.Intermediate.Localizations.ToDictionary(l => l.Culture, StringComparer.OrdinalIgnoreCase)); var trackIntermediate = this.BackendHelper.TrackFile(Path.Combine(this.IntermediateFolder, Path.GetFileName(Path.ChangeExtension(this.OutputPath, "wir"))), TrackedFileType.Intermediate); intermediate.Save(trackIntermediate.Path); trackedFiles.Add(trackIntermediate); + } - //transfer = this.BackendHelper.CreateFileTransfer(intermediatePath, Path.ChangeExtension(this.OutputPath, "wir"), true, FileTransferType.Built); - //fileTransfers.Add(transfer); + private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, Output output) + { + WixOutput wixout; + + if (String.IsNullOrEmpty(this.OutputPdbPath)) + { + wixout = WixOutput.Create(); + } + else + { + var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); + trackedFiles.Add(trackPdb); + + wixout = WixOutput.Create(trackPdb.Path); + } + + intermediate.Save(wixout); + + output.Save(wixout); + + return wixout; } #if TODO_FINISH_PATCH @@ -934,5 +948,27 @@ namespace WixToolset.Core.WindowsInstaller.Bind return command.GeneratedTemporaryFiles; } + + #region IDisposable Support + + public void Dispose() + { + this.Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + this.Wixout?.Dispose(); + } + + this.disposed = true; + } + } + + #endregion } } diff --git a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs index 2ebb3f13..5cd7204a 100644 --- a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs @@ -2,7 +2,6 @@ namespace WixToolset.Core.WindowsInstaller { - using System; using WixToolset.Core.WindowsInstaller.Bind; using WixToolset.Core.WindowsInstaller.Inscribe; using WixToolset.Core.WindowsInstaller.Unbind; @@ -26,18 +25,21 @@ namespace WixToolset.Core.WindowsInstaller var validator = Validator.CreateFromContext(context, "darice.cub"); - var command = new BindDatabaseCommand(context, backendExtensions, validator); - command.Execute(); + using (var command = new BindDatabaseCommand(context, backendExtensions, validator)) + { + command.Execute(); - var result = context.ServiceProvider.GetService(); - result.FileTransfers = command.FileTransfers; - result.TrackedFiles = command.TrackedFiles; + var result = context.ServiceProvider.GetService(); + result.FileTransfers = command.FileTransfers; + result.TrackedFiles = command.TrackedFiles; - foreach (var extension in backendExtensions) - { - extension.PostBackendBind(result, command.Pdb); + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result, command.Wixout); + } + + return result; } - return result; } public IDecompileResult Decompile(IDecompileContext context) diff --git a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs index d5281759..f048b4e2 100644 --- a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs @@ -2,7 +2,6 @@ namespace WixToolset.Core.WindowsInstaller { - using System; using WixToolset.Core.WindowsInstaller.Bind; using WixToolset.Core.WindowsInstaller.Unbind; using WixToolset.Data; @@ -25,24 +24,21 @@ namespace WixToolset.Core.WindowsInstaller var validator = Validator.CreateFromContext(context, "mergemod.cub"); - var command = new BindDatabaseCommand(context, backendExtensions, validator); - command.Execute(); + using (var command = new BindDatabaseCommand(context, backendExtensions, validator)) + { + command.Execute(); - var result = context.ServiceProvider.GetService(); - result.FileTransfers = command.FileTransfers; - result.TrackedFiles = command.TrackedFiles; + var result = context.ServiceProvider.GetService(); + result.FileTransfers = command.FileTransfers; + result.TrackedFiles = command.TrackedFiles; - foreach (var extension in backendExtensions) - { - extension.PostBackendBind(result, command.Pdb); - } + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result, command.Wixout); + } - if (!String.IsNullOrEmpty(context.OutputPdbPath)) - { - command.Pdb?.Save(context.OutputPdbPath); + return result; } - - return result; } public IDecompileResult Decompile(IDecompileContext context) @@ -67,10 +63,7 @@ namespace WixToolset.Core.WindowsInstaller return result; } - public bool Inscribe(IInscribeContext context) - { - return false; - } + public bool Inscribe(IInscribeContext context) => false; public Intermediate Unbind(IUnbindContext context) { diff --git a/src/WixToolset.Core/Bind/ExpectedExtractFile.cs b/src/WixToolset.Core/Bind/ExpectedExtractFile.cs index afad12fc..b27cdfee 100644 --- a/src/WixToolset.Core/Bind/ExpectedExtractFile.cs +++ b/src/WixToolset.Core/Bind/ExpectedExtractFile.cs @@ -1,4 +1,4 @@ -// 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. +// 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.Bind { @@ -9,7 +9,7 @@ namespace WixToolset.Core.Bind { public Uri Uri { get; set; } - public int EmbeddedFileIndex { get; set; } + public string EmbeddedFileId { get; set; } public string OutputPath { get; set; } } diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs index 644e5c63..35c8a2f0 100644 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs +++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs @@ -15,7 +15,7 @@ namespace WixToolset.Core.Bind /// internal class ExtractEmbeddedFiles { - private Dictionary> filesWithEmbeddedFiles = new Dictionary>(); + private readonly Dictionary> filesWithEmbeddedFiles = new Dictionary>(); public IEnumerable Uris => this.filesWithEmbeddedFiles.Keys; @@ -23,28 +23,28 @@ namespace WixToolset.Core.Bind /// Adds an embedded file index to track and returns the path where the embedded file will be extracted. Duplicates will return the same extract path. /// /// Uri to file containing the embedded files. - /// Index of the embedded file to extract. - /// Path where temporary files should be placed. + /// Id of the embedded file to extract. + /// Folder where extracted files should be placed. /// The extract path for the embedded file. - public string AddEmbeddedFileIndex(Uri uri, int embeddedFileIndex, string tempPath) + public string AddEmbeddedFileToExtract(Uri uri, string embeddedFileId, string extractFolder) { // If the uri to the file that contains the embedded file does not already have embedded files // being extracted, create the dictionary to track that. if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) { - extracts = new SortedList(); + extracts = new SortedList(StringComparer.OrdinalIgnoreCase); this.filesWithEmbeddedFiles.Add(uri, extracts); } // If the embedded file is not already tracked in the dictionary of extracts, add it. - if (!extracts.TryGetValue(embeddedFileIndex, out var extractPath)) + if (!extracts.TryGetValue(embeddedFileId, out var extractPath)) { - string localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath); - string unique = this.HashUri(uri.AbsoluteUri); - string extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileIndex); + var localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath); + var unique = this.HashUri(uri.AbsoluteUri); + var extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileId); - extractPath = Path.Combine(tempPath, extractedName); - extracts.Add(embeddedFileIndex, extractPath); + extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName)); + extracts.Add(embeddedFileId, extractPath); } return extractPath; @@ -52,35 +52,39 @@ namespace WixToolset.Core.Bind public IEnumerable GetExpectedEmbeddedFiles() { + var files = new List(); + foreach (var uriWithExtracts in this.filesWithEmbeddedFiles) { foreach (var extracts in uriWithExtracts.Value) { - yield return new ExpectedExtractFile + files.Add(new ExpectedExtractFile { Uri = uriWithExtracts.Key, - EmbeddedFileIndex = extracts.Key, + EmbeddedFileId = extracts.Key, OutputPath = extracts.Value, - }; + }); } } + + return files; } public IEnumerable GetExtractFilesForUri(Uri uri) { if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) { - extracts = new SortedList(); + extracts = new SortedList(StringComparer.OrdinalIgnoreCase); } - return extracts.Select(e => new ExpectedExtractFile() { Uri = uri, EmbeddedFileIndex = e.Key, OutputPath = e.Value }); + return extracts.Select(e => new ExpectedExtractFile { Uri = uri, EmbeddedFileId = e.Key, OutputPath = e.Value }); } private string HashUri(string uri) { using (SHA1 sha1 = new SHA1CryptoServiceProvider()) { - byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(uri)); + var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(uri)); return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); } } diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs index d82609db..683c3c50 100644 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs +++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs @@ -2,10 +2,9 @@ namespace WixToolset.Core.Bind { + using System; using System.Collections.Generic; - using System.IO; using System.Linq; - using System.Reflection; using WixToolset.Data; using WixToolset.Extensibility.Data; @@ -26,41 +25,18 @@ namespace WixToolset.Core.Bind { var baseUri = expectedEmbeddedFileByUri.Key; - Stream stream = null; - try + using (var wixout = WixOutput.Read(baseUri)) { - // If the embedded files are stored in an assembly resource stream (usually - // a .wixlib embedded in a WixExtension). - if ("embeddedresource" == baseUri.Scheme) - { - var assemblyPath = Path.GetFullPath(baseUri.LocalPath); - var resourceName = baseUri.Fragment.TrimStart('#'); - - var assembly = Assembly.LoadFile(assemblyPath); - stream = assembly.GetManifestResourceStream(resourceName); - } - else // normal file (usually a binary .wixlib on disk). - { - stream = File.OpenRead(baseUri.LocalPath); - } + var uniqueIds = new SortedSet(StringComparer.OrdinalIgnoreCase); - using (var fs = FileStructure.Read(stream)) + foreach (var embeddedFile in expectedEmbeddedFileByUri) { - var uniqueIndicies = new SortedSet(); - - foreach (var embeddedFile in expectedEmbeddedFileByUri) + if (uniqueIds.Add(embeddedFile.EmbeddedFileId)) { - if (uniqueIndicies.Add(embeddedFile.EmbeddedFileIndex)) - { - fs.ExtractEmbeddedFile(embeddedFile.EmbeddedFileIndex, embeddedFile.OutputPath); - } + wixout.ExtractEmbeddedFile(embeddedFile.EmbeddedFileId, embeddedFile.OutputPath); } } } - finally - { - stream?.Close(); - } } } } diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs index 2c213402..19a26915 100644 --- a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs @@ -98,9 +98,9 @@ namespace WixToolset.Core.Bind #endif // File is embedded and path to it was not modified above. - if (objectField.EmbeddedFileIndex.HasValue && isDefault) + if (isDefault && objectField.Embed) { - var extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.BaseUri, objectField.EmbeddedFileIndex.Value, this.IntermediateFolder); + var extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileToExtract(objectField.BaseUri, objectField.Path, this.IntermediateFolder); // Set the path to the embedded file once where it will be extracted. field.Set(extractPath); diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index 972258fe..5ee60984 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -100,29 +100,38 @@ namespace WixToolset.Core.CommandLine if (this.OutputType == OutputType.Library) { - var wixlib = this.LibraryPhase(wixobjs, wxls, this.commandLine.BindFiles, this.commandLine.BindPaths); - - if (!this.Messaging.EncounteredError) + using (new IntermediateFieldContext("wix.lib")) { - wixlib.Save(this.commandLine.OutputFile); + var wixlib = this.LibraryPhase(wixobjs, wxls, this.commandLine.BindFiles, this.commandLine.BindPaths); + + if (!this.Messaging.EncounteredError) + { + wixlib.Save(this.commandLine.OutputFile); + } } } else { - if (wixipl == null) - { - wixipl = this.LinkPhase(wixobjs, this.commandLine.LibraryFilePaths, creator); - } - - if (!this.Messaging.EncounteredError) + using (new IntermediateFieldContext("wix.link")) { - if (this.OutputType == OutputType.IntermediatePostLink) + if (wixipl == null) { - wixipl.Save(this.commandLine.OutputFile); + wixipl = this.LinkPhase(wixobjs, this.commandLine.LibraryFilePaths, creator); } - else + + if (!this.Messaging.EncounteredError) { - this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths, this.commandLine.BurnStubPath); + if (this.OutputType == OutputType.IntermediatePostLink) + { + wixipl.Save(this.commandLine.OutputFile); + } + else + { + using (new IntermediateFieldContext("wix.bind")) + { + this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths, this.commandLine.BurnStubPath); + } + } } } } @@ -469,6 +478,7 @@ namespace WixToolset.Core.CommandLine break; } + case "bf": case "bindfiles": this.BindFiles = true; return true; diff --git a/src/WixToolset.Core/Librarian.cs b/src/WixToolset.Core/Librarian.cs index 3c1810ac..5c0fb302 100644 --- a/src/WixToolset.Core/Librarian.cs +++ b/src/WixToolset.Core/Librarian.cs @@ -56,14 +56,14 @@ namespace WixToolset.Core return null; } - var embedFilePaths = this.ResolveFilePathsToEmbed(context, sections); + this.ResolveFilePathsToEmbed(context, sections); foreach (var section in sections) { section.LibraryId = context.LibraryId; } - library = new Intermediate(context.LibraryId, sections, localizationsByCulture, embedFilePaths); + library = new Intermediate(context.LibraryId, sections, localizationsByCulture); this.Validate(library); } @@ -78,10 +78,8 @@ namespace WixToolset.Core return this.Messaging.EncounteredError ? null : library; } - private List ResolveFilePathsToEmbed(ILibraryContext context, IEnumerable sections) + private void ResolveFilePathsToEmbed(ILibraryContext context, IEnumerable sections) { - var embedFilePaths = new List(); - // Resolve paths to files that are to be embedded in the library. if (context.BindFiles) { @@ -104,9 +102,7 @@ namespace WixToolset.Core if (!String.IsNullOrEmpty(file)) { // File was successfully resolved so track the embedded index as the embedded file index. - field.Set(new IntermediateFieldPathValue { EmbeddedFileIndex = embedFilePaths.Count }); - - embedFilePaths.Add(file); + field.Set(new IntermediateFieldPathValue { Embed = true, Path = file }); } else { @@ -116,8 +112,6 @@ namespace WixToolset.Core } } } - - return embedFilePaths; } private void Validate(Intermediate library) diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index 81696840..6ef252b7 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -564,7 +564,7 @@ namespace WixToolset.Core var collate = new CollateLocalizationsCommand(this.Messaging, localizations); var localizationsByCulture = collate.Execute(); - intermediate = new Intermediate(resolvedSection.Id, new[] { resolvedSection }, localizationsByCulture, null); + intermediate = new Intermediate(resolvedSection.Id, new[] { resolvedSection }, localizationsByCulture); #if MOVE_TO_BACKEND this.CheckOutputConsistency(output); diff --git a/src/test/Example.Extension/Data/example.wir b/src/test/Example.Extension/Data/example.wir index 8e32f901..d1ee8b90 100644 Binary files a/src/test/Example.Extension/Data/example.wir and b/src/test/Example.Extension/Data/example.wir differ diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index d056a1d6..e201b61f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -312,9 +312,8 @@ namespace WixToolsetTest.CoreIntegration var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); Assert.True(File.Exists(pdbPath)); - var pdb = Pdb.Load(pdbPath, suppressVersionCheck: true); - Assert.NotNull(pdb); - Assert.NotNull(pdb.Output); + var output = Output.Load(pdbPath, suppressVersionCheck: true); + Assert.NotNull(output); } } @@ -448,6 +447,74 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildBinaryWixlib() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute( + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-bindfiles", + "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); + + result.AssertSuccess(); + + using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) + { + Assert.NotNull(wixout.GetDataStream("wix-ir.json")); + + var text = wixout.GetData("wix-ir/test.txt"); + Assert.Equal("This is test.txt.", text); + } + } + } + + [Fact] + public void CanBuildBinaryWixlibWithCollidingFilenames() + { + var folder = TestData.Get(@"TestData\SameFileFolders"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute( + "build", + Path.Combine(folder, "TestComponents.wxs"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-bindfiles", + "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); + + result.AssertSuccess(); + + using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) + { + Assert.NotNull(wixout.GetDataStream("wix-ir.json")); + + var text = wixout.GetData("wix-ir/test.txt"); + Assert.Equal(@"This is a\test.txt.", text); + + var text2 = wixout.GetData("wix-ir/test.txt-1"); + Assert.Equal(@"This is b\test.txt.", text2); + + var text3 = wixout.GetData("wix-ir/test.txt-2"); + Assert.Equal(@"This is c\test.txt.", text3); + } + } + } + [Fact] public void CanBuildWithIncludePath() { @@ -459,8 +526,7 @@ namespace WixToolsetTest.CoreIntegration var baseFolder = fs.GetFolder(); var intermediateFolder = Path.Combine(baseFolder, "obj"); - var result = WixRunner.Execute(new[] - { + var result = WixRunner.Execute( "build", Path.Combine(folder, "Package.wxs"), Path.Combine(folder, "PackageComponents.wxs"), @@ -468,8 +534,7 @@ namespace WixToolsetTest.CoreIntegration "-bindpath", bindpath, "-intermediateFolder", intermediateFolder, "-o", Path.Combine(baseFolder, @"bin\test.msi"), - "-i", bindpath, - }); + "-i", bindpath); result.AssertSuccess(); @@ -635,8 +700,8 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); - var pdb = Pdb.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"), false); - var caRows = pdb.Output.Tables["CustomAction"].Rows.Single(); + var output = Output.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"), false); + var caRows = output.Tables["CustomAction"].Rows.Single(); Assert.Equal("SetINSTALLLOCATION", caRows.FieldAsString(0)); Assert.Equal("51", caRows.FieldAsString(1)); Assert.Equal("INSTALLLOCATION", caRows.FieldAsString(2)); @@ -711,8 +776,8 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); - var pdb = Pdb.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"), false); - Assert.NotEmpty(pdb.Output.SubStorages); + var output = Output.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"), false); + Assert.NotEmpty(output.SubStorages); } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs new file mode 100644 index 00000000..765e6778 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt new file mode 100644 index 00000000..1970cae6 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt @@ -0,0 +1 @@ +This is a\test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt new file mode 100644 index 00000000..fa2c7082 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt @@ -0,0 +1 @@ +This is b\test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt new file mode 100644 index 00000000..1c0cbda6 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt @@ -0,0 +1 @@ +This is c\test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 1f8860ef..dba30bd6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -36,6 +36,10 @@ + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs index 668c273a..1d359241 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs @@ -128,14 +128,14 @@ namespace WixToolsetTest.CoreIntegration { var binary = section.Tuples.OfType().Single(); var path = binary[BinaryTupleFields.Data].AsPath().Path; - Assert.Contains("Example.Extension", path); - Assert.EndsWith(@"\0", path); + Assert.StartsWith(Path.Combine(baseFolder, @"obj\Example.Extension"), path); + Assert.EndsWith(@"wix-ir\example.txt", path); Assert.Equal(@"BinFromWir", binary.Id.Id); } } } - [Fact(Skip = "Test demonstrates failure")] + [Fact] public void CanBuildWixiplUsingExtensionLibrary() { var folder = TestData.Get(@"TestData\Wixipl"); @@ -171,7 +171,7 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"obj\test.wir")); + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); var section = intermediate.Sections.Single(); { @@ -183,8 +183,8 @@ namespace WixToolsetTest.CoreIntegration { var binary = section.Tuples.OfType().Single(); var path = binary[BinaryTupleFields.Data].AsPath().Path; - Assert.Contains("Example.Extension", path); - Assert.EndsWith(@"\0", path); + Assert.StartsWith(Path.Combine(baseFolder, @"obj\test"), path); + Assert.EndsWith(@"wix-ir\example.txt", path); Assert.Equal(@"BinFromWir", binary.Id.Id); } } -- cgit v1.2.3-55-g6feb From f01d284101e95d490497062c2dc9065423d0cf37 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 25 Oct 2019 02:47:47 -0700 Subject: Fix MsiAssembly table processing --- .../Bind/AssemblyName.cs | 4 ++-- .../Bind/CreateOutputFromIRCommand.cs | 17 +++++++++++++++++ .../WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 2 +- .../TestData/Assembly/Win32Assembly.wxs | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs index 0df1a7e9..759ba303 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs @@ -16,7 +16,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.Architecture = architecture; this.StrongNamedSigned = !String.IsNullOrEmpty(publicKeyToken); - this.PublicKeyToken = publicKeyToken ?? "null"; + this.PublicKeyToken = publicKeyToken; this.Type = type; } @@ -46,7 +46,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind assemblyName.Append(", Culture="); assemblyName.Append(this.Culture); assemblyName.Append(", PublicKeyToken="); - assemblyName.Append(this.PublicKeyToken); + assemblyName.Append(this.PublicKeyToken ?? "null"); if (!String.IsNullOrEmpty(this.Architecture)) { diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 17cac83a..081644cb 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -60,6 +60,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind output.EnsureTable(this.TableDefinitions["Signature"]); break; + case TupleDefinitionType.Assembly: + this.AddAssemblyTuple((AssemblyTuple)tuple, output); + break; + case TupleDefinitionType.Binary: this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true); break; @@ -239,6 +243,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } + private void AddAssemblyTuple(AssemblyTuple tuple, Output output) + { + var attributes = tuple.Type == AssemblyType.Win32Assembly ? 1 : (int?)null; + + var table = output.EnsureTable(this.TableDefinitions["MsiAssembly"]); + var row = table.CreateRow(tuple.SourceLineNumbers); + row[0] = tuple.ComponentRef; + row[1] = tuple.FeatureRef; + row[2] = tuple.ManifestFileRef; + row[3] = tuple.ApplicationFileRef; + row[4] = attributes; + } + private void AddBBControlTuple(BBControlTuple tuple, Output output) { var attributes = tuple.Attributes; diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 6ebdb993..1b302065 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -480,7 +480,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] + [Fact] public void PopulatesMsiAssemblyTables() { var folder = TestData.Get(@"TestData"); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs index 980c5ca4..45cc7114 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs @@ -3,7 +3,7 @@ - + -- cgit v1.2.3-55-g6feb From 1e3e48ac376ca689d524fe69a7f1a40fcd1573df Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 27 Oct 2019 10:10:47 +1000 Subject: Add failing test for AppId. --- .../MsiQueryFixture.cs | 33 ++++++++++++++++++++++ .../TestData/AppId/Advertised.wxs | 11 ++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 45 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 1b302065..fa14f06c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -9,6 +9,39 @@ namespace WixToolsetTest.CoreIntegration public class MsiQueryFixture { + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesAppIdTableWhenAdvertised() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppId", "Advertised.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppId" }); + Assert.Equal(new[] + { + "AppId:{D6040299-B15C-4C94-AE26-0C9B60D14C35}\t\t\t\t\t\t", + }, results); + } + } + [Fact] public void PopulatesAppSearchTablesFromComponentSearch() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs new file mode 100644 index 00000000..b34c547d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index dba30bd6..c3a956be 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -14,6 +14,7 @@ + -- cgit v1.2.3-55-g6feb From 34e002b3a9043ff3062c676c74fb124b5feef784 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Fri, 1 Nov 2019 19:24:45 -0400 Subject: Fix error checking on CustomAction/@Error. --- src/WixToolset.Core/Compiler.cs | 4 ++-- src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 1 + .../TestData/CustomAction/UnscheduledCustomAction.wxs | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index ae99b673..ee460edc 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -3501,9 +3501,9 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryKey", "Directory", "FileKey", "Property")); } } - else if (CustomActionTargetType.TextData == targetType && CustomActionSourceType.Directory != sourceType && CustomActionSourceType.Property != sourceType) + else if (CustomActionTargetType.TextData == targetType && CustomActionSourceType.Directory != sourceType && CustomActionSourceType.Property != sourceType && CustomActionSourceType.File != sourceType) { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property")); + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property", "Error")); } else if (!String.IsNullOrEmpty(innerText)) // inner text cannot be specified with non-script CAs { diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 7f06eea6..7c3dddd7 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -281,6 +281,7 @@ namespace WixToolsetTest.CoreIntegration { "Binary:Binary1\t[Binary data]", "CustomAction:CustomAction1\t1\tBinary1\tInvalidEntryPoint\t", + "CustomAction:DiscardOptimismAllBeingsWhoProceed\t19\t\tAbandon hope all ye who enter here.\t", }, results); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs index d9633869..e4b066e9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs @@ -7,5 +7,6 @@ + -- cgit v1.2.3-55-g6feb From e6f381b0ce2011ced88697ca7ddaae8a053b57d7 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 4 Nov 2019 13:15:39 -0500 Subject: Tolerate missing RegistryValue/@Value when @Type="binary". --- src/WixToolset.Core/Compiler_2.cs | 4 ++-- src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 1 + .../TestData/Registry/RegistryValue.wxs | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index 10416850..2f578e61 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -2006,11 +2006,11 @@ namespace WixToolset.Core //} // value may be set by child MultiStringValue elements, so it must be checked here - if (null == value) + if (null == value && valueType != RegistryValueType.Binary) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); } - else if (0 == value.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values + else if (0 == value?.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values { this.Core.Write(ErrorMessages.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name)); } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 7c3dddd7..fff37618 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -610,6 +610,7 @@ namespace WixToolsetTest.CoreIntegration var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); Assert.Equal(new[] { + "Registry:reg04OIwIchl.9ZTjisTT6NzGSsQSM\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMiscComponent", "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent", }, results); } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs index 3d88d4cd..fe6e179e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs @@ -4,6 +4,7 @@ + -- cgit v1.2.3-55-g6feb From 0f65aaaca2faf1b6fc233c445216d547f08c6fa5 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Tue, 5 Nov 2019 19:27:06 -0500 Subject: Move creation of hidden properties... ...for deferred CAs with HideTarget="yes" to the MSI backend where it belongs. --- .../Bind/CreateSpecialPropertiesCommand.cs | 15 ++++++++++++--- src/WixToolset.Core/Compiler.cs | 10 +--------- .../WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 5 +++++ .../TestData/CustomAction/UnscheduledCustomAction.wxs | 3 ++- 4 files changed, 20 insertions(+), 13 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs index e6c089a1..8f769904 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs @@ -20,9 +20,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind public void Execute() { // Create lists of the properties that contribute to the special lists of properties. - SortedSet adminProperties = new SortedSet(); - SortedSet secureProperties = new SortedSet(); - SortedSet hiddenProperties = new SortedSet(); + var adminProperties = new SortedSet(); + var secureProperties = new SortedSet(); + var hiddenProperties = new SortedSet(); foreach (var wixPropertyRow in this.Section.Tuples.OfType()) { @@ -42,6 +42,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } + // Hide properties for in-script custom actions that have HideTarget set. + var hideTargetCustomActions = this.Section.Tuples.OfType().Where( + ca => ca.Hidden + && (ca.ExecutionType == CustomActionExecutionType.Deferred + || ca.ExecutionType == CustomActionExecutionType.Commit + || ca.ExecutionType == CustomActionExecutionType.Rollback)) + .Select(ca => ca.Id.Id); + hiddenProperties.UnionWith(hideTargetCustomActions); + if (0 < adminProperties.Count) { var tuple = new PropertyTuple(null, new Identifier(AccessModifier.Private, "AdminProperties")); diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index ee460edc..b983981b 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -3561,6 +3561,7 @@ namespace WixToolset.Core PatchUninstall = patchUninstall, TSAware = tsAware, Win64 = win64, + Hidden = hidden, }; this.Core.AddTuple(tuple); @@ -3569,15 +3570,6 @@ namespace WixToolset.Core { this.Core.AddTuple(new WixSuppressModularizationTuple(sourceLineNumbers, id)); } - - // For deferred CAs that specify HideTarget we should also hide the CA data property for the action. - if (hidden && - (CustomActionExecutionType.Deferred == executionType || - CustomActionExecutionType.Commit == executionType || - CustomActionExecutionType.Rollback == executionType)) - { - this.AddWixPropertyRow(sourceLineNumbers, id, false, false, hidden); - } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index fff37618..febf3b62 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -3,6 +3,7 @@ namespace WixToolsetTest.CoreIntegration { using System.IO; + using System.Linq; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using Xunit; @@ -281,8 +282,12 @@ namespace WixToolsetTest.CoreIntegration { "Binary:Binary1\t[Binary data]", "CustomAction:CustomAction1\t1\tBinary1\tInvalidEntryPoint\t", + "CustomAction:CustomActionWithHiddenTarget\t9217\tBinary1\tInvalidEntryPoint\t", "CustomAction:DiscardOptimismAllBeingsWhoProceed\t19\t\tAbandon hope all ye who enter here.\t", }, results); + var properties = Query.QueryDatabase(msiPath, new[] { "Property" }); + var hiddenProperties = properties.Where(q => q.StartsWith("Property:MsiHiddenProperties")).Single(); + Assert.Equal("Property:MsiHiddenProperties\tCustomActionWithHiddenTarget", hiddenProperties); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs index e4b066e9..00ac2810 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs @@ -6,7 +6,8 @@ - + + -- cgit v1.2.3-55-g6feb From 97a70bbc1b90cb26f8c77d83e703689d15d08761 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 5 Nov 2019 22:33:33 -0800 Subject: Fix resolution of !(bind.ProductVersion.MsiId) bind variables Fixes wixtoolset/issues#4830 --- src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs | 5 +++++ src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs | 7 +++---- src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs | 6 ++++++ .../TestData/SimpleBundle/Bundle.wxs | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs index 5fcf172f..7e65a9cf 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs @@ -135,6 +135,11 @@ namespace WixToolset.Core.Burn.Bundles this.Facade.PackageTuple.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS"); } + if (String.IsNullOrEmpty(this.Facade.PackageTuple.Version)) + { + this.Facade.PackageTuple.Version = msiPackage.ProductVersion; + } + var payloadNames = this.GetPayloadTargetNames(packagePayload.Id.Id); var msiPropertyNames = this.GetMsiPropertyNames(packagePayload.Id.Id); diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs index 22710aca..be0e4578 100644 --- a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs @@ -153,12 +153,11 @@ namespace WixToolset.Core.Bind } else { - string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", variableId, variableScope).ToLower(CultureInfo.InvariantCulture); - string resolvedValue = variableDefaultValue; + var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", variableId, variableScope).ToLower(CultureInfo.InvariantCulture); - if (resolutionData.ContainsKey(key)) + if (!resolutionData.TryGetValue(key, out var resolvedValue)) { - resolvedValue = resolutionData[key]; + resolvedValue = variableDefaultValue; } if ("bind" == variableNamespace) diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index 554f4b17..f32208a4 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -44,6 +44,12 @@ namespace WixToolsetTest.CoreIntegration var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); var section = intermediate.Sections.Single(); + var bundleTuple = section.Tuples.OfType().Single(); + Assert.Equal("1.0.0.0", bundleTuple.Version); + + var previousVersion = bundleTuple.Fields[(int)WixBundleTupleFields.Version].PreviousValue; + Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString()); + var msiTuple = section.Tuples.OfType().Single(); Assert.Equal("test.msi", msiTuple.Id.Id ); } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs index 89dbb503..7ef1fc05 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs @@ -1,6 +1,6 @@ - + -- cgit v1.2.3-55-g6feb From 58cd3477c3ae3d7880991ee1651a862a45371e08 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Wed, 6 Nov 2019 18:17:18 -0500 Subject: Add preprocessor variables with WiX version info. $(sys.WIXMAJORVERSION) $(sys.WIXVERSION) --- .../ExtensibilityServices/PreprocessHelper.cs | 6 ++++ .../PreprocessorFixture.cs | 33 ++++++++++++++++++++-- .../TestData/Variables/Package.wxs | 4 +++ 3 files changed, 41 insertions(+), 2 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs index dfee0046..60726a02 100644 --- a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs @@ -248,6 +248,12 @@ namespace WixToolset.Core.ExtensibilityServices throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", context.Platform.ToString()); } + case "WIXMAJORVERSION": + return ThisAssembly.AssemblyFileVersion.Split('.')[0]; + + case "WIXVERSION": + return ThisAssembly.AssemblyFileVersion; + default: return null; } diff --git a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs index 633a1b46..4e48cbe1 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs @@ -7,6 +7,7 @@ namespace WixToolsetTest.CoreIntegration using WixBuildTools.TestSupport; using WixToolset.Core; using WixToolset.Core.TestPackage; + using WixToolset.Data; using WixToolset.Extensibility.Data; using Xunit; @@ -61,8 +62,36 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); - var warnings = result.Messages.Where(message => message.Id == 1118); - Assert.Single(warnings); + var warning = result.Messages.Where(message => message.Id == (int)WarningMessages.Ids.VariableDeclarationCollision); + Assert.Single(warning); + } + } + + [Fact] + public void WixVersionVariablesWork() + { + var folder = TestData.Get(@"TestData\Variables"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var warning = result.Messages.Where(message => message.Id == (int)WarningMessages.Ids.PreprocessorWarning); + Assert.Single(warning); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs index 896b7e3f..9f5e3f34 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs @@ -6,6 +6,10 @@ += 4 AND $(sys.WIXMAJORVERSION) < 5 ?> + + + -- cgit v1.2.3-55-g6feb From bc085061963069953d609284ab48d16d7e1ccc99 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 7 Nov 2019 14:59:16 +1000 Subject: Add failing test for TrueType Font. --- .../MsiQueryFixture.cs | 35 +++++++++++++++++++++- .../TestData/Font/TrueType.wxs | 10 +++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index febf3b62..52f57297 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -403,7 +403,7 @@ namespace WixToolsetTest.CoreIntegration } [Fact] - public void PopulatesFontTable() + public void PopulatesFontTableFromFontTitle() { var folder = TestData.Get(@"TestData"); @@ -435,6 +435,39 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesFontTableFromTrueType() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Font", "TrueType.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Font" }); + Assert.Equal(new[] + { + "Font:TrueTypeFontComp.ttf\t", + }, results); + } + } + [Fact] public void PopulatesInstallExecuteSequenceTable() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs new file mode 100644 index 00000000..ff94ce52 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index c3a956be..4e0fb0db 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -35,6 +35,7 @@ + -- cgit v1.2.3-55-g6feb From 38d85261d5a64f97d6260b5bf07d101711ca9ed9 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 7 Nov 2019 15:27:31 +1000 Subject: Update PopulatesCustomActionTable to verify scheduling of a custom action. --- .../MsiQueryFixture.cs | 69 ++++++++++++++++++++-- .../CustomAction/UnscheduledCustomAction.wxs | 16 +++++ 2 files changed, 80 insertions(+), 5 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 52f57297..2a36e11c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -252,7 +252,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact] + [Fact(Skip = "Test demonstrates failure")] public void PopulatesCustomActionTable() { var folder = TestData.Get(@"TestData"); @@ -277,17 +277,76 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Binary", "CustomAction" }); + var results = Query.QueryDatabase(msiPath, new[] { + "AdminExecuteSequence", + "AdminUISequence", + "AdvtExecuteSequence", + "Binary", + "CustomAction", + "InstallExecuteSequence", + "InstallUISequence", + "Property", + }).Where(x => !x.StartsWith("Property:") || x.StartsWith("Property:MsiHiddenProperties\t")).ToArray(); Assert.Equal(new[] { + "AdminExecuteSequence:CostFinalize\t\t1000", + "AdminExecuteSequence:CostInitialize\t\t800", + "AdminExecuteSequence:CustomAction2\t\t801", + "AdminExecuteSequence:FileCost\t\t900", + "AdminExecuteSequence:InstallAdminPackage\t\t3900", + "AdminExecuteSequence:InstallFiles\t\t4000", + "AdminExecuteSequence:InstallFinalize\t\t6600", + "AdminExecuteSequence:InstallInitialize\t\t1500", + "AdminExecuteSequence:InstallValidate\t\t1400", + "AdminUISequence:CostFinalize\t\t1000", + "AdminUISequence:CostInitialize\t\t800", + "AdminUISequence:CustomAction2\t\t801", + "AdminUISequence:ExecuteAction\t\t1300", + "AdminUISequence:FileCost\t\t900", + "AdvtExecuteSequence:CostFinalize\t\t1000", + "AdvtExecuteSequence:CostInitialize\t\t800", + "AdvtExecuteSequence:CustomAction2\t\t801", + "AdvtExecuteSequence:InstallFinalize\t\t6600", + "AdvtExecuteSequence:InstallInitialize\t\t1500", + "AdvtExecuteSequence:InstallValidate\t\t1400", + "AdvtExecuteSequence:PublishFeatures\t\t6300", + "AdvtExecuteSequence:PublishProduct\t\t6400", "Binary:Binary1\t[Binary data]", "CustomAction:CustomAction1\t1\tBinary1\tInvalidEntryPoint\t", + "CustomAction:CustomAction2\t51\tTestAdvtExecuteSequenceProperty\t1\t", "CustomAction:CustomActionWithHiddenTarget\t9217\tBinary1\tInvalidEntryPoint\t", "CustomAction:DiscardOptimismAllBeingsWhoProceed\t19\t\tAbandon hope all ye who enter here.\t", + "InstallExecuteSequence:CostFinalize\t\t1000", + "InstallExecuteSequence:CostInitialize\t\t800", + "InstallExecuteSequence:CustomAction2\t\t801", + "InstallExecuteSequence:FileCost\t\t900", + "InstallExecuteSequence:FindRelatedProducts\t\t25", + "InstallExecuteSequence:InstallFiles\t\t4000", + "InstallExecuteSequence:InstallFinalize\t\t6600", + "InstallExecuteSequence:InstallInitialize\t\t1500", + "InstallExecuteSequence:InstallValidate\t\t1400", + "InstallExecuteSequence:LaunchConditions\t\t100", + "InstallExecuteSequence:MigrateFeatureStates\t\t1200", + "InstallExecuteSequence:ProcessComponents\t\t1600", + "InstallExecuteSequence:PublishFeatures\t\t6300", + "InstallExecuteSequence:PublishProduct\t\t6400", + "InstallExecuteSequence:RegisterProduct\t\t6100", + "InstallExecuteSequence:RegisterUser\t\t6000", + "InstallExecuteSequence:RemoveExistingProducts\t\t1401", + "InstallExecuteSequence:RemoveFiles\t\t3500", + "InstallExecuteSequence:UnpublishFeatures\t\t1800", + "InstallExecuteSequence:ValidateProductID\t\t700", + "InstallUISequence:CostFinalize\t\t1000", + "InstallUISequence:CostInitialize\t\t800", + "InstallUISequence:CustomAction2\t\t801", + "InstallUISequence:ExecuteAction\t\t1300", + "InstallUISequence:FileCost\t\t900", + "InstallUISequence:FindRelatedProducts\t\t25", + "InstallUISequence:LaunchConditions\t\t100", + "InstallUISequence:MigrateFeatureStates\t\t1200", + "InstallUISequence:ValidateProductID\t\t700", + "Property:MsiHiddenProperties\tCustomActionWithHiddenTarget", }, results); - var properties = Query.QueryDatabase(msiPath, new[] { "Property" }); - var hiddenProperties = properties.Where(q => q.StartsWith("Property:MsiHiddenProperties")).Single(); - Assert.Equal("Property:MsiHiddenProperties\tCustomActionWithHiddenTarget", hiddenProperties); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs index 00ac2810..780529d6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs @@ -9,5 +9,21 @@ + + + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 62c2eaf958c0b1a410fdf91dd0edcb542c60ef07 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 7 Nov 2019 15:47:12 +1000 Subject: Add test for RemoveRegistryKey. --- .../MsiQueryFixture.cs | 32 ++++++++++++++++++++++ .../TestData/Registry/RemoveRegistryKey.wxs | 11 ++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 44 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 2a36e11c..3c20e997 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -713,6 +713,38 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void PopulatesRegistryTableFromRemoveRegistryKey() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RemoveRegistryKey.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + Assert.Equal(new[] + { + "Registry:RemoveAKeyName\t2\tAKeyName\t-\t\tRemoveRegistryKeyComp", + }, results); + } + } + [Fact] public void PopulatesReserveCostTable() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs new file mode 100644 index 00000000..a55a1e18 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 4e0fb0db..b0e0d855 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -45,6 +45,7 @@ + -- cgit v1.2.3-55-g6feb From e03595bdca426a03ad740e4c312f028f97f465ec Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 8 Nov 2019 13:37:15 +1000 Subject: Add failing test for decompiling a non-advertised shortcut with complex Target. --- .../DecompileFixture.cs | 28 +++++++++++++++++++++ .../TestData/Shortcut/DecompiledShortcuts.wxs | 24 ++++++++++++++++++ .../TestData/Shortcut/shortcuts.msi | Bin 0 -> 32768 bytes .../WixToolsetTest.CoreIntegration.csproj | 2 ++ 4 files changed, 54 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs index bace97b3..d6cf4742 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -96,5 +96,33 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(expected, actualFormatted); } } + + [Fact(Skip = "Test demonstrates failure")] + public void CanDecompileShortcuts() + { + var folder = TestData.Get(@"TestData\Shortcut"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); + + var result = WixRunner.Execute(new[] + { + "decompile", + Path.Combine(folder, "shortcuts.msi"), + "-intermediateFolder", intermediateFolder, + "-o", outputPath + }); + + result.AssertSuccess(); + + var actual = File.ReadAllText(outputPath); + var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + var expected = XDocument.Load(Path.Combine(folder, "DecompiledShortcuts.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + + Assert.Equal(expected, actualFormatted); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs new file mode 100644 index 00000000..3a9e401c --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi new file mode 100644 index 00000000..3a24d1a8 Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi differ diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index b0e0d855..b0139b91 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -48,7 +48,9 @@ + + -- cgit v1.2.3-55-g6feb From 8a7d727f1ab0dfef956db726d64985311291505e Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 8 Nov 2019 14:46:30 +1000 Subject: Add failing test for getting Shortcut/@Name from wixlib. --- .../MsiQueryFixture.cs | 3 ++- .../TestData/Shortcut/ShortcutProperty.wxs | 2 +- .../WixlibQueryFixture.cs | 30 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 3c20e997..2be582c9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -672,10 +672,11 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty" }); + var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty", "Shortcut" }); Assert.Equal(new[] { "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue", + "Shortcut:TheShortcut\tINSTALLFOLDER\td|\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", }, results); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs index d0a041b8..27f2ab9b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs @@ -4,7 +4,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs index 7f9b9686..53bc5910 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs @@ -12,6 +12,36 @@ namespace WixToolsetTest.CoreIntegration public class WixlibQueryFixture { + [Fact(Skip = "Test demonstrates failure")] + public void ShortcutNameWithPreprocessorVariableIsResolved() + { + var folder = TestData.Get(@"TestData\Shortcut"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ShortcutProperty.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixlibPath); + var allTuples = intermediate.Sections.SelectMany(s => s.Tuples); + var shortcutTuple = allTuples.OfType() + .SingleOrDefault(); + Assert.NotNull(shortcutTuple); + Assert.Equal("d", shortcutTuple.Name); + } + } + [Fact] public void UpgradeProducesReferenceToRemoveExistingProducts() { -- cgit v1.2.3-55-g6feb From e29c25090e26c8cca52232d580528840d1161b73 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Fri, 8 Nov 2019 14:54:15 -0500 Subject: Ensure upgrade action properties are secure. --- .../Bind/CreateSpecialPropertiesCommand.cs | 4 ++++ src/WixToolset.Core/Compiler_2.cs | 3 --- .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 21 ++++++++++++++++----- .../TestData/ManualUpgrade/Package.wxs | 1 + 4 files changed, 21 insertions(+), 8 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs index 8f769904..0d165f80 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs @@ -51,6 +51,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind .Select(ca => ca.Id.Id); hiddenProperties.UnionWith(hideTargetCustomActions); + // Ensure upgrade action properties are secure. + var actionProperties = this.Section.Tuples.OfType().Select(u => u.ActionProperty); + secureProperties.UnionWith(actionProperties); + if (0 < adminProperties.Count) { var tuple = new PropertyTuple(null, new Identifier(AccessModifier.Private, "AdminProperties")); diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index 2f578e61..3e50a32d 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -4919,9 +4919,6 @@ namespace WixToolset.Core this.Core.AddTuple(tuple); - // Ensure the action property is secure. - this.AddWixPropertyRow(sourceLineNumbers, new Identifier(AccessModifier.Private, actionProperty), false, true, false); - // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence // if at least one row in Upgrade table lacks the OnlyDetect attribute. if (!onlyDetect) diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 21b6e9ce..4d1e35f9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -372,16 +372,27 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(0, result); + var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(pdbPath)); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\test.txt"))); - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var intermediate = Intermediate.Load(pdbPath); var section = intermediate.Sections.Single(); - var fileTuple = section.Tuples.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileTuple[FileTupleFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileTuple[FileTupleFields.Source].PreviousValue.AsPath().Path); + var upgradeTuple = section.Tuples.OfType().Single(); + Assert.False(upgradeTuple.ExcludeLanguages); + Assert.True(upgradeTuple.IgnoreRemoveFailures); + Assert.False(upgradeTuple.VersionMaxInclusive); + Assert.True(upgradeTuple.VersionMinInclusive); + Assert.Equal("13.0.0", upgradeTuple.VersionMax); + Assert.Equal("12.0.0", upgradeTuple.VersionMin); + Assert.False(upgradeTuple.OnlyDetect); + Assert.Equal("BLAHBLAHBLAH", upgradeTuple.ActionProperty); + + var pdb = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: false); + var secureProperties = pdb.Tables["Property"].Rows.Where(row => row.GetKey() == "SecureCustomProperties").Single(); + Assert.Contains("BLAHBLAHBLAH", secureProperties.FieldAsString(1)); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs index d674eb59..38125b57 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs @@ -6,6 +6,7 @@ + -- cgit v1.2.3-55-g6feb From df709d87c25945c10b9d29273dd90b6df6359a99 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Sun, 10 Nov 2019 18:19:36 -0500 Subject: Clean up upgrade properties; support --- src/WixToolset.Core/Compiler.cs | 6 ------ src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs | 4 ++-- .../WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs | 2 ++ 3 files changed, 4 insertions(+), 8 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index b983981b..56d3a8b4 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -7137,9 +7137,6 @@ namespace WixToolset.Core this.Core.AddTuple(tuple); - // Ensure the action property is secure. - this.AddWixPropertyRow(sourceLineNumbers, new Identifier(AccessModifier.Public, Common.UpgradeDetectedProperty), false, true, false); - // Add launch condition that blocks upgrades if (blockUpgrades) { @@ -7167,9 +7164,6 @@ namespace WixToolset.Core this.Core.AddTuple(upgradeTuple); - // Ensure the action property is secure. - this.AddWixPropertyRow(sourceLineNumbers, new Identifier(AccessModifier.Public, Common.DowngradeDetectedProperty), false, true, false); - var conditionTuple = new LaunchConditionTuple(sourceLineNumbers) { Condition = Common.DowngradePreventedCondition, diff --git a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs index 60726a02..215c7bc4 100644 --- a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs @@ -154,10 +154,10 @@ namespace WixToolset.Core.ExtensibilityServices public string GetVariableValue(IPreprocessContext context, string variable, bool allowMissingPrefix) { - // Strip the "$(" off the front. + // Strip the "$(" off the front and the ")" off the back. if (variable.StartsWith("$(", StringComparison.Ordinal)) { - variable = variable.Substring(2); + variable = variable.Substring(2, variable.Length - 3); } var parts = variable.Split(VariableSplitter, 2); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs index 9f5e3f34..57c24f57 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs @@ -6,9 +6,11 @@ + = 4 AND $(sys.WIXMAJORVERSION) < 5 ?> + -- cgit v1.2.3-55-g6feb From e1d974378d049004c73d65dbb43a405f67ececd4 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 12 Nov 2019 13:33:23 +1000 Subject: Update DefaultDir test for duplicate ShortName and Name. --- src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 3 ++- .../WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 48d16ac1..83f53aca 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -384,7 +384,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact] + [Fact(Skip = "Test demonstrates failure")] public void PopulatesDirectoryTableWithValidDefaultDir() { var folder = TestData.Get(@"TestData"); @@ -411,6 +411,7 @@ namespace WixToolsetTest.CoreIntegration var results = Query.QueryDatabase(msiPath, new[] { "Directory" }); Assert.Equal(new[] { + "Directory:DUPLICATENAMEANDSHORTNAME\tINSTALLFOLDER\tduplicat", "Directory:INSTALLFOLDER\tProgramFilesFolder\toekcr5lq|MsiPackage", "Directory:NAMEANDSHORTNAME\tINSTALLFOLDER\tSHORTNAM|NameAndShortName", "Directory:NAMEANDSHORTSOURCENAME\tINSTALLFOLDER\tNAMEASSN|NameAndShortSourceName", diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs index aeb3d554..a217fa34 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs @@ -8,6 +8,7 @@ + -- cgit v1.2.3-55-g6feb From a79a2bc97a428f635cccd58935c3bcd059743da4 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 14 Nov 2019 09:23:19 +1000 Subject: Add failing test for decompiling the AdvtExecuteSequence table. --- .../DecompileFixture.cs | 28 ++++++++++++++++ .../SequenceTables/DecompiledSequenceTables.wxs | 37 +++++++++++++++++++++ .../TestData/SequenceTables/SequenceTables.msi | Bin 0 -> 32768 bytes .../WixToolsetTest.CoreIntegration.csproj | 2 ++ 4 files changed, 67 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs index d6cf4742..c2520896 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -97,6 +97,34 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void CanDecompileSequenceTables() + { + var folder = TestData.Get(@"TestData\SequenceTables"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); + + var result = WixRunner.Execute(new[] + { + "decompile", + Path.Combine(folder, "SequenceTables.msi"), + "-intermediateFolder", intermediateFolder, + "-o", outputPath + }); + + result.AssertSuccess(); + + var actual = File.ReadAllText(outputPath); + var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + var expected = XDocument.Load(Path.Combine(folder, "DecompiledSequenceTables.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + + Assert.Equal(expected, actualFormatted); + } + } + [Fact(Skip = "Test demonstrates failure")] public void CanDecompileShortcuts() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs new file mode 100644 index 00000000..b8adf6e4 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi new file mode 100644 index 00000000..7f894091 Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi differ diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index b0139b91..370f0ff5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -47,6 +47,8 @@ + + -- cgit v1.2.3-55-g6feb From e34ea332def4718110e9a7efcf9e12bf7456c753 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 14 Nov 2019 12:13:44 +1000 Subject: Add failing test for decompiling a directory search under a registry search. --- .../DecompileFixture.cs | 28 +++++++++++++++++++++ .../DecompiledNestedDirSearchUnderRegSearch.wxs | 28 +++++++++++++++++++++ .../AppSearch/NestedDirSearchUnderRegSearch.msi | Bin 0 -> 36864 bytes .../WixToolsetTest.CoreIntegration.csproj | 2 ++ 4 files changed, 58 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs index c2520896..c7c80f6e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -66,6 +66,34 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void CanDecompileNestedDirSearchUnderRegSearch() + { + var folder = TestData.Get(@"TestData\AppSearch"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); + + var result = WixRunner.Execute(new[] + { + "decompile", + Path.Combine(folder, "NestedDirSearchUnderRegSearch.msi"), + "-intermediateFolder", intermediateFolder, + "-o", outputPath + }); + + result.AssertSuccess(); + + var actual = File.ReadAllText(outputPath); + var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + var expected = XDocument.Load(Path.Combine(folder, "DecompiledNestedDirSearchUnderRegSearch.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + + Assert.Equal(expected, actualFormatted); + } + } + [Fact(Skip = "Test demonstrates failure")] public void CanDecompileOldClassTableDefinition() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs new file mode 100644 index 00000000..94ddfe19 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi new file mode 100644 index 00000000..7e0f8060 Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi differ diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 370f0ff5..75a55c31 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -16,8 +16,10 @@ + + -- cgit v1.2.3-55-g6feb From 94b3c44ea27e29253a26e18bf0c70295d0fc48e5 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Thu, 14 Nov 2019 18:10:00 -0500 Subject: Fix EnvironmentTuple nullable fields. Add test. --- src/WixToolset.Core/Compiler.cs | 2 +- .../MsiQueryFixture.cs | 44 ++++++++++++++++++++++ .../TestData/Environment/Environment.wxs | 13 +++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 56d3a8b4..179c5a37 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -5203,7 +5203,7 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); } - if (!part.HasValue && action == EnvironmentActionType.Create) + if (part.HasValue && action == EnvironmentActionType.Create) { this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 81f780dc..068ae2b7 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -427,6 +427,41 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void PopulatesEnvironmentTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Environment", "Environment.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Environment" }); + Assert.Equal(new[] + { + "Environment:WixEnvironmentTest1\t=-WixEnvTest1\t\tWixEnvironmentTest", + "Environment:WixEnvironmentTest2\t+-WixEnvTest1\t\tWixEnvironmentTest", + "Environment:WixEnvironmentTest3\t!-WixEnvTest1\t\tWixEnvironmentTest", + "Environment:WixEnvironmentTest4\t=-*WIX\t[INSTALLFOLDER]\tWixEnvironmentTest", + }, results); + } + } + [Fact] public void PopulatesFeatureTableWithParent() { @@ -942,6 +977,15 @@ namespace WixToolsetTest.CoreIntegration "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", "Upgrade:{B05772EA-82B8-4DE0-B7EB-45B5F0CCFE6D}\t1.0.0\t\t\t256\t\tRELPRODFOUND", }, results); + + var prefix = "Property:SecureCustomProperties\t"; + var secureProperties = Query.QueryDatabase(msiPath, new[] { "Property" }).Where(p => p.StartsWith(prefix)).Single(); + Assert.Equal(new[] + { + "RELPRODFOUND", + "WIX_DOWNGRADE_DETECTED", + "WIX_UPGRADE_DETECTED", + }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p)); } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs new file mode 100644 index 00000000..284801e2 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 75a55c31..0330adf6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -31,6 +31,7 @@ + -- cgit v1.2.3-55-g6feb From 2c73e671599f4d05bb98b38dbc79750a1cf04b45 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 25 Nov 2019 17:24:30 +1000 Subject: Fix test CanDecompileNestedDirSearchUnderRegSearch. --- .../Decompile/Decompiler.cs | 66 ++++++++++++++-------- .../DecompileFixture.cs | 2 +- .../DecompiledNestedDirSearchUnderRegSearch.wxs | 2 +- 3 files changed, 46 insertions(+), 24 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index 5afaace9..961e1a13 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -2292,6 +2292,14 @@ namespace WixToolset.Core.WindowsInstaller usedDrLocator = true; } } + else if ("RegLocator" == parentLocatorRow.TableDefinition.Name) + { + var parentSearchElement = (Wix.IParentElement)this.core.GetIndexedElement(parentLocatorRow); + + parentSearchElement.AddChild(searchElement); + usedSearchElements[searchElement] = null; + usedDrLocator = true; + } } // keep track of unused DrLocator rows @@ -2362,7 +2370,8 @@ namespace WixToolset.Core.WindowsInstaller } else { - if ("DrLocator" == locatorRow.TableDefinition.Name) + if ("DrLocator" == locatorRow.TableDefinition.Name || + "RegLocator" == locatorRow.TableDefinition.Name) { unusedSearchElements.Add(searchElement); } @@ -2385,32 +2394,45 @@ namespace WixToolset.Core.WindowsInstaller { var used = false; - foreach (Wix.ISchemaElement schemaElement in unusedSearchElement.Children) + Wix.DirectorySearch leafDirectorySearch = null; + var parentElement = unusedSearchElement; + var updatedLeaf = true; + while (updatedLeaf) { - var directorySearch = schemaElement as Wix.DirectorySearch; - if (null != directorySearch) + updatedLeaf = false; + foreach (var schemaElement in parentElement.Children) { - var appSearchProperties = (StringCollection)appSearches[directorySearch.Id]; - - var unusedSearchSchemaElement = unusedSearchElement as Wix.ISchemaElement; - if (null != appSearchProperties) + if (schemaElement is Wix.DirectorySearch directorySearch) { - var property = this.EnsureProperty(appSearchProperties[0]); - - property.AddChild(unusedSearchSchemaElement); - used = true; + parentElement = leafDirectorySearch = directorySearch; + updatedLeaf = true; break; } - else if (ccpSearches.Contains(directorySearch.Id)) - { - complianceCheck.AddChild(unusedSearchSchemaElement); - used = true; - break; - } - else - { - // TODO: warn - } + } + } + + if (leafDirectorySearch != null) + { + var appSearchProperties = (StringCollection)appSearches[leafDirectorySearch.Id]; + + var unusedSearchSchemaElement = unusedSearchElement as Wix.ISchemaElement; + if (null != appSearchProperties) + { + var property = this.EnsureProperty(appSearchProperties[0]); + + property.AddChild(unusedSearchSchemaElement); + used = true; + break; + } + else if (ccpSearches.Contains(leafDirectorySearch.Id)) + { + complianceCheck.AddChild(unusedSearchSchemaElement); + used = true; + break; + } + else + { + // TODO: warn } } diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs index 5765cdfa..9893a525 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -66,7 +66,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] + [Fact] public void CanDecompileNestedDirSearchUnderRegSearch() { var folder = TestData.Get(@"TestData\AppSearch"); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs index 94ddfe19..6d78b2db 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs @@ -18,7 +18,7 @@ - + -- cgit v1.2.3-55-g6feb From dda4a326407eb8a0fb5d094ee1e2b5b85d419042 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 26 Nov 2019 08:24:48 +1000 Subject: Fix decompiling multiple AppSearches with nested searches. --- .../Decompile/Decompiler.cs | 24 +++++++++++++-------- .../DecompiledNestedDirSearchUnderRegSearch.wxs | 21 +++++++++++++++++- .../AppSearch/NestedDirSearchUnderRegSearch.msi | Bin 36864 -> 33045 bytes 3 files changed, 35 insertions(+), 10 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index 961e1a13..b1222d3d 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -2000,7 +2000,7 @@ namespace WixToolset.Core.WindowsInstaller var drLocators = new Hashtable(); var locators = new Hashtable(); var usedSearchElements = new Hashtable(); - var unusedSearchElements = new ArrayList(); + var unusedSearchElements = new Dictionary(); Wix.ComplianceCheck complianceCheck = null; @@ -2131,6 +2131,7 @@ namespace WixToolset.Core.WindowsInstaller } else if ("DrLocator" == locatorRow.TableDefinition.Name && null != locatorRow[1]) { + var drSearchElement = (Wix.DirectorySearch)searchElement; var parentSignature = Convert.ToString(locatorRow[1]); if ("CCP_DRIVE" == parentSignature) @@ -2266,7 +2267,7 @@ namespace WixToolset.Core.WindowsInstaller } parentSearchElement = directorySeachRef; - unusedSearchElements.Add(directorySeachRef); + unusedSearchElements.Add(directorySeachRef.Id, directorySeachRef); } if (!usedSearchElements.Contains(searchElement)) @@ -2305,7 +2306,7 @@ namespace WixToolset.Core.WindowsInstaller // keep track of unused DrLocator rows if (!usedDrLocator) { - unusedSearchElements.Add(searchElement); + unusedSearchElements.Add(drSearchElement.Id, drSearchElement); } } else @@ -2370,10 +2371,13 @@ namespace WixToolset.Core.WindowsInstaller } else { - if ("DrLocator" == locatorRow.TableDefinition.Name || - "RegLocator" == locatorRow.TableDefinition.Name) + if (searchElement is Wix.DirectorySearch directorySearch) { - unusedSearchElements.Add(searchElement); + unusedSearchElements.Add(directorySearch.Id, directorySearch); + } + else if (searchElement is Wix.RegistrySearch registrySearch) + { + unusedSearchElements.Add(registrySearch.Id, registrySearch); } else { @@ -2390,8 +2394,12 @@ namespace WixToolset.Core.WindowsInstaller } } - foreach (Wix.IParentElement unusedSearchElement in unusedSearchElements) + // Iterate through the unused elements through a sorted list of their ids so the output is deterministic. + var unusedSearchElementKeys = unusedSearchElements.Keys.ToList(); + unusedSearchElementKeys.Sort(); + foreach (var unusedSearchElementKey in unusedSearchElementKeys) { + var unusedSearchElement = unusedSearchElements[unusedSearchElementKey]; var used = false; Wix.DirectorySearch leafDirectorySearch = null; @@ -2422,13 +2430,11 @@ namespace WixToolset.Core.WindowsInstaller property.AddChild(unusedSearchSchemaElement); used = true; - break; } else if (ccpSearches.Contains(leafDirectorySearch.Id)) { complianceCheck.AddChild(unusedSearchSchemaElement); used = true; - break; } else { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs index 6d78b2db..26649485 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs @@ -1,6 +1,6 @@ - + @@ -17,6 +17,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi index 7e0f8060..ea1296c3 100644 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi and b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi differ -- cgit v1.2.3-55-g6feb From 3add0e56ddc57ddcfeb92c3b4cbb7ad0eee7674f Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Wed, 11 Dec 2019 19:08:30 -0500 Subject: Add unit tests written during discovery of errors elsewhere because why not keep 'em? --- src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs | 6 +++--- src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 1 + .../TestData/Environment/Environment.wxs | 1 + .../TestData/ErrorsInUI/PackageComponents.wxs | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 4d1e35f9..81289e5a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -277,9 +277,9 @@ namespace WixToolsetTest.CoreIntegration var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); var section = intermediate.Sections.Single(); - var error = section.Tuples.OfType().Single(); - Assert.Equal(1234, error.Error); - Assert.Equal("Category 55 Emergency Doomsday Crisis", error.Message.Trim()); + var errors = section.Tuples.OfType().ToDictionary(t => t.Error); + Assert.Equal("Category 55 Emergency Doomsday Crisis", errors[1234].Message.Trim()); + Assert.Equal(" ", errors[5678].Message); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 5a78bbc2..200ba7b8 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -454,6 +454,7 @@ namespace WixToolsetTest.CoreIntegration var results = Query.QueryDatabase(msiPath, new[] { "Environment" }); Assert.Equal(new[] { + "Environment:PATH\t=-*PATH\t[INSTALLFOLDER]; ;[~]\tWixEnvironmentTest", "Environment:WixEnvironmentTest1\t=-WixEnvTest1\t\tWixEnvironmentTest", "Environment:WixEnvironmentTest2\t+-WixEnvTest1\t\tWixEnvironmentTest", "Environment:WixEnvironmentTest3\t!-WixEnvTest1\t\tWixEnvironmentTest", diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs index 284801e2..de9744a7 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs @@ -7,6 +7,7 @@ + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs index db128695..c9c65fc7 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs @@ -5,6 +5,7 @@ Category 55 Emergency Doomsday Crisis + -- cgit v1.2.3-55-g6feb From 69651a71ff2fdc1e9897b878782d79dcc1f9b896 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 6 Jan 2020 14:54:31 -0500 Subject: Ensure Errors have ids so they can be referenced. --- src/WixToolset.Core/Compiler.cs | 2 +- src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs | 6 ++++++ .../WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 2a020ad5..6b9fe59e 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -5297,7 +5297,7 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { - var tuple = new ErrorTuple(sourceLineNumbers) + var tuple = new ErrorTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, id)) { Error = id, Message = Common.GetInnerText(node) diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 81289e5a..a12ad469 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -280,6 +280,12 @@ namespace WixToolsetTest.CoreIntegration var errors = section.Tuples.OfType().ToDictionary(t => t.Error); Assert.Equal("Category 55 Emergency Doomsday Crisis", errors[1234].Message.Trim()); Assert.Equal(" ", errors[5678].Message); + + var customAction1 = section.Tuples.OfType().Where(t => t.Id.Id == "CanWeReferenceAnError_YesWeCan").Single(); + Assert.Equal("1234", customAction1.Target); + + var customAction2 = section.Tuples.OfType().Where(t => t.Id.Id == "TextErrorsWorkOKToo").Single(); + Assert.Equal("If you see this, something went wrong.", customAction2.Target); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs index 6da3dcbe..a4c01d7e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs @@ -6,6 +6,9 @@ + + + -- cgit v1.2.3-55-g6feb From c2bc01b47cca2a70ddeb2cc9e9b2e3b9906bb647 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Tue, 21 Jan 2020 19:12:53 -0500 Subject: Fix ServiceControl/@Wait translation. --- .../Bind/CreateOutputFromIRCommand.cs | 5 ++++- src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 5 +++-- .../TestData/ServiceInstall/OwnProcess.wxs | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 16517e91..31d0b3a6 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -803,7 +803,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[1] = tuple.Name; row[2] = events; row[3] = tuple.Arguments; - row[4] = tuple.Wait; + if (tuple.Wait.HasValue) + { + row[4] = tuple.Wait.Value ? 1 : 0; + } row[5] = tuple.ComponentRef; } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 200ba7b8..18380417 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -817,7 +817,7 @@ namespace WixToolsetTest.CoreIntegration } [Fact] - public void PopulatesServiceInstallTable() + public void PopulatesServiceTables() { var folder = TestData.Get(@"TestData"); @@ -841,9 +841,10 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "ServiceInstall" }); + var results = Query.QueryDatabase(msiPath, new[] { "ServiceInstall", "ServiceControl" }); Assert.Equal(new[] { + "ServiceControl:SampleService\tSampleService\t161\t\t1\ttest.txt", "ServiceInstall:SampleService\tSampleService\t\t16\t4\t0\t\t\t\t\t\ttest.txt\t", }, results); } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs index 596e98a6..65cba20e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs @@ -4,7 +4,8 @@ - + + -- cgit v1.2.3-55-g6feb From 54c6cf7e151b4e816cedc393e9c520eb818524de Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Thu, 23 Jan 2020 14:46:40 -0500 Subject: Fix CustomTable/@Unreal="yes" binding. --- .../Bind/LoadTableDefinitionsCommand.cs | 26 +++++++++---------- .../MsiQueryFixture.cs | 30 ++++++++++++++++++++++ .../TestData/CustomTable/CustomTable.wxs | 13 ++++++++++ 3 files changed, 56 insertions(+), 13 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs index 05f865fa..92ddad6f 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs @@ -32,19 +32,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind return this.TableDefinitions; } - private TableDefinition CreateCustomTable(WixCustomTableTuple row) + private TableDefinition CreateCustomTable(WixCustomTableTuple tuple) { - var columnNames = row.ColumnNames.Split('\t'); - var columnTypes = row.ColumnTypes.Split('\t'); - var primaryKeys = row.PrimaryKeys.Split('\t'); - var minValues = row.MinValues?.Split('\t'); - var maxValues = row.MaxValues?.Split('\t'); - var keyTables = row.KeyTables?.Split('\t'); - var keyColumns = row.KeyColumns?.Split('\t'); - var categories = row.Categories?.Split('\t'); - var sets = row.Sets?.Split('\t'); - var descriptions = row.Descriptions?.Split('\t'); - var modularizations = row.Modularizations?.Split('\t'); + var columnNames = tuple.ColumnNames.Split('\t'); + var columnTypes = tuple.ColumnTypes.Split('\t'); + var primaryKeys = tuple.PrimaryKeys.Split('\t'); + var minValues = tuple.MinValues?.Split('\t'); + var maxValues = tuple.MaxValues?.Split('\t'); + var keyTables = tuple.KeyTables?.Split('\t'); + var keyColumns = tuple.KeyColumns?.Split('\t'); + var categories = tuple.Categories?.Split('\t'); + var sets = tuple.Sets?.Split('\t'); + var descriptions = tuple.Descriptions?.Split('\t'); + var modularizations = tuple.Modularizations?.Split('\t'); var currentPrimaryKey = 0; @@ -206,7 +206,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind columns.Add(columnDefinition); } - var customTable = new TableDefinition(row.Id.Id, columns/*, unreal: bootstrapperApplicationData, bootstrapperApplicationData*/); + var customTable = new TableDefinition(tuple.Id.Id, columns, tuple.Unreal); return customTable; } } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 18380417..38ef2e2e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -384,6 +384,36 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void UnrealCustomTableIsNotPresentInMsi() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" }); + Assert.Empty(results); + } + } + [Fact] public void PopulatesDirectoryTableWithValidDefaultDir() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs index 649b29b6..8eb4fbf9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs @@ -17,5 +17,18 @@ test.txt + + + + + + RowA + test.txt + + + RowB + test.txt + + -- cgit v1.2.3-55-g6feb From 6ff680e386b1543ad1a58d1b1d465ce8aa20bc7d Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 24 Jan 2020 15:27:20 -0800 Subject: Start on new patch infrastructure --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 1 - .../Bundles/CreateNonUXContainers.cs | 4 +- .../Bind/AssignMediaCommand.cs | 28 +- .../Bind/AttachPatchTransformsCommand.cs | 1322 ++++++++++++++++++++ .../Bind/BindDatabaseCommand.cs | 490 ++++---- .../Bind/BindTransformCommand.cs | 197 ++- .../Bind/CabinetBuilder.cs | 6 +- .../Bind/CabinetResolver.cs | 4 +- .../Bind/CopyTransformDataCommand.cs | 222 ++-- .../Bind/CreateDeltaPatchesCommand.cs | 2 +- .../Bind/CreateIdtFileCommand.cs | 16 +- .../Bind/CreateOutputFromIRCommand.cs | 353 +++--- .../Bind/CreatePatchTransformsCommand.cs | 90 ++ .../Bind/ExtractMergeModuleFilesCommand.cs | 40 +- .../Bind/GenerateDatabaseCommand.cs | 535 ++++---- .../Bind/GenerateTransformCommand.cs | 588 +++++++++ .../Bind/GetFileFacadesCommand.cs | 2 +- .../Bind/GetFileFacadesFromTransforms.cs | 585 +++++++++ .../Bind/LoadTableDefinitionsCommand.cs | 21 +- .../Bind/MergeModulesCommand.cs | 8 +- .../Bind/PatchTransform.cs | 246 ++++ .../Bind/ProcessUncompressedFilesCommand.cs | 8 +- .../Bind/SequenceActionsCommand.cs | 13 +- .../Bind/UpdateFileFacadesCommand.cs | 102 +- .../Bind/UpdateMediaSequencesCommand.cs | 20 +- .../Data/tables.xml | 4 + src/WixToolset.Core.WindowsInstaller/Differ.cs | 4 + .../Inscribe/InscribeMsiPackageCommand.cs | 80 +- src/WixToolset.Core.WindowsInstaller/MspBackend.cs | 68 +- src/WixToolset.Core.WindowsInstaller/MstBackend.cs | 2 +- .../Ole32/Storage.cs | 2 +- src/WixToolset.Core.WindowsInstaller/Patch.cs | 7 +- .../PatchTransform.cs | 7 +- .../Unbind/UnbindMsiOrMsmCommand.cs | 2 +- .../Unbind/UnbindTranformCommand.cs | 125 +- .../WindowsInstallerBackendFactory.cs | 5 +- src/WixToolset.Core/Bind/FileFacade.cs | 128 +- src/WixToolset.Core/Bind/ResolveFieldsCommand.cs | 8 +- src/WixToolset.Core/Compiler.cs | 48 +- src/WixToolset.Core/Compiler_2.cs | 6 - src/WixToolset.Core/Compiler_Patch.cs | 3 +- src/WixToolset.Core/Compiler_UI.cs | 6 +- src/WixToolset.Core/Resolver.cs | 2 +- .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 112 ++ .../PreprocessorFixture.cs | 28 - .../TestData/PatchFamilyFilter/.data/Av1.0.0.txt | 1 + .../TestData/PatchFamilyFilter/.data/Av1.0.1.txt | 1 + .../TestData/PatchFamilyFilter/.data/Bv1.0.0.txt | 1 + .../TestData/PatchFamilyFilter/.data/Bv1.0.1.txt | 1 + .../TestData/PatchFamilyFilter/Package.wxs | 31 + .../TestData/PatchFamilyFilter/Patch.wxs | 23 + .../TestData/PatchSingle/.data/Av1.0.0.txt | 1 + .../TestData/PatchSingle/.data/Av1.0.1.txt | 1 + .../TestData/PatchSingle/.data/Bv1.0.0.txt | 1 + .../TestData/PatchSingle/.data/Bv1.0.1.txt | 1 + .../TestData/PatchSingle/Package.wxs | 31 + .../TestData/PatchSingle/Patch.wxs | 16 + .../WixToolsetTest.CoreIntegration.csproj | 14 +- 58 files changed, 4481 insertions(+), 1192 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index c2164744..283cd115 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -208,7 +208,6 @@ namespace WixToolset.Core.Burn variableCache.Add(String.Concat("packageManufacturer.", facade.PackageId), msiPackage.Manufacturer); } } - } break; diff --git a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs index 612e0e11..34e601a7 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs @@ -113,7 +113,7 @@ namespace WixToolset.Core.Burn.Bundles ++attachedContainerIndex; } - this.CreateContainer(container, containerPayloads, null); + this.CreateContainer(container, containerPayloads); } } @@ -122,7 +122,7 @@ namespace WixToolset.Core.Burn.Bundles this.FileTransfers = fileTransfers; } - private void CreateContainer(WixBundleContainerTuple container, IEnumerable containerPayloads, string manifestFile) + private void CreateContainer(WixBundleContainerTuple container, IEnumerable containerPayloads) { var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); command.Execute(); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs index 2199bbde..2bfd587f 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs @@ -57,7 +57,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var mediaRows = new Dictionary(); - List uncompressedFiles = new List(); + var uncompressedFiles = new List(); var mediaTable = this.Section.Tuples.OfType().ToList(); var mediaTemplateTable = this.Section.Tuples.OfType().ToList(); @@ -109,8 +109,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind ulong currentPreCabSize = 0; ulong maxPreCabSizeInBytes; - int maxPreCabSizeInMB = 0; - int currentCabIndex = 0; + var maxPreCabSizeInMB = 0; + var currentCabIndex = 0; MediaTuple currentMediaRow = null; @@ -131,7 +131,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate; } - string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); + var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); try { @@ -170,13 +170,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind { // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. var cabinetFiles = filesByCabinetMedia[currentMediaRow]; - facade.File.DiskId = currentCabIndex; + facade.DiskId = currentCabIndex; cabinetFiles.Add(facade); continue; } // Update current cab size. - currentPreCabSize += (ulong)facade.File.FileSize; + currentPreCabSize += (ulong)facade.FileSize; if (currentPreCabSize > maxPreCabSizeInBytes) { @@ -186,10 +186,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind filesByCabinetMedia.Add(currentMediaRow, new List()); var cabinetFileRows = filesByCabinetMedia[currentMediaRow]; - facade.File.DiskId = currentCabIndex; + facade.DiskId = currentCabIndex; cabinetFileRows.Add(facade); // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize - currentPreCabSize = (ulong)facade.File.FileSize; + currentPreCabSize = (ulong)facade.FileSize; } else { @@ -204,7 +204,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Associate current file with current cab. var cabinetFiles = filesByCabinetMedia[currentMediaRow]; - facade.File.DiskId = currentCabIndex; + facade.DiskId = currentCabIndex; cabinetFiles.Add(facade); } } @@ -260,18 +260,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - foreach (FileFacade facade in fileFacades) + foreach (var facade in fileFacades) { if (!mediaRows.TryGetValue(facade.DiskId, out var mediaRow)) { - this.Messaging.Write(ErrorMessages.MissingMedia(facade.File.SourceLineNumbers, facade.DiskId)); + this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId)); continue; } // When building a product, if the current file is to be uncompressed or if // the package set not to be compressed, don't cab it. - var compressed = (facade.File.Attributes & FileTupleAttributes.Compressed) == FileTupleAttributes.Compressed; - var uncompressed = (facade.File.Attributes & FileTupleAttributes.Uncompressed) == FileTupleAttributes.Uncompressed; + var compressed = facade.Compressed; + var uncompressed = facade.Uncompressed; if (SectionType.Product == this.Section.Type && (uncompressed || (!compressed && !this.FilesCompressed))) { uncompressedFiles.Add(facade); @@ -284,7 +284,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else { - this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.Id.Id, facade.DiskId)); + this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.SourceLineNumber, facade.Id, facade.DiskId)); } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs new file mode 100644 index 00000000..aa5ca20a --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs @@ -0,0 +1,1322 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Text.RegularExpressions; + using WixToolset.Core.WindowsInstaller; + using WixToolset.Core.WindowsInstaller.Msi; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Services; + + /// + /// Include transforms in a patch. + /// + internal class AttachPatchTransformsCommand + { + private static readonly string[] PatchUninstallBreakingTables = new[] + { + "AppId", + "BindImage", + "Class", + "Complus", + "CreateFolder", + "DuplicateFile", + "Environment", + "Extension", + "Font", + "IniFile", + "IsolatedComponent", + "LockPermissions", + "MIME", + "MoveFile", + "MsiLockPermissionsEx", + "MsiServiceConfig", + "MsiServiceConfigFailureActions", + "ODBCAttribute", + "ODBCDataSource", + "ODBCDriver", + "ODBCSourceAttribute", + "ODBCTranslator", + "ProgId", + "PublishComponent", + "RemoveIniFile", + "SelfReg", + "ServiceControl", + "ServiceInstall", + "TypeLib", + "Verb", + }; + + private readonly TableDefinitionCollection tableDefinitions; + + public AttachPatchTransformsCommand(IMessaging messaging, Intermediate intermediate, IEnumerable transforms) + { + this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandardInternal.GetTableDefinitions()); + this.Messaging = messaging; + this.Intermediate = intermediate; + this.Transforms = transforms; + } + + private IMessaging Messaging { get; } + + private Intermediate Intermediate { get; } + + private IEnumerable Transforms { get; } + + public IEnumerable SubStorages { get; private set; } + + public IEnumerable Execute() + { + var subStorages = new List(); + + if (this.Transforms == null || !this.Transforms.Any()) + { + this.Messaging.Write(ErrorMessages.PatchWithoutTransforms()); + return subStorages; + } + + var summaryInfo = this.ExtractPatchSummaryInfo(); + + var section = this.Intermediate.Sections.First(); + + var tuples = this.Intermediate.Sections.SelectMany(s => s.Tuples).ToList(); + + // Get the patch id from the WixPatchId tuple. + var patchIdTuple = tuples.OfType().FirstOrDefault(); + + if (String.IsNullOrEmpty(patchIdTuple.Id?.Id)) + { + this.Messaging.Write(ErrorMessages.ExpectedPatchIdInWixMsp()); + return subStorages; + } + + if (String.IsNullOrEmpty(patchIdTuple.ClientPatchId)) + { + this.Messaging.Write(ErrorMessages.ExpectedClientPatchIdInWixMsp()); + return subStorages; + } + + // enumerate patch.Media to map diskId to Media row + var patchMediaByDiskId = tuples.OfType().ToDictionary(t => t.DiskId); + + if (patchMediaByDiskId.Count == 0) + { + this.Messaging.Write(ErrorMessages.ExpectedMediaRowsInWixMsp()); + return subStorages; + } + + // populate MSP summary information + var patchMetadata = this.PopulateSummaryInformation(summaryInfo, tuples, patchIdTuple, section.Codepage); + + // enumerate transforms + var productCodes = new SortedSet(); + var transformNames = new List(); + var validTransform = new List>(); + + var baselineTuplesById = tuples.OfType().ToDictionary(t => t.Id.Id); + + foreach (var mainTransform in this.Transforms) + { + var baselineTuple = baselineTuplesById[mainTransform.Baseline]; + + var patchRefTuples = tuples.OfType().ToList(); + if (patchRefTuples.Count > 0) + { + if (!this.ReduceTransform(mainTransform.Transform, patchRefTuples)) + { + // transform has none of the content authored into this patch + continue; + } + } + + // Validate the transform doesn't break any patch specific rules. + this.Validate(mainTransform); + + // ensure consistent File.Sequence within each Media + var mediaTuple = patchMediaByDiskId[baselineTuple.DiskId]; + + // Ensure that files are sequenced after the last file in any transform. + var transformMediaTable = mainTransform.Transform.Tables["Media"]; + if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) + { + foreach (MediaRow transformMediaRow in transformMediaTable.Rows) + { + if (mediaTuple.LastSequence < transformMediaRow.LastSequence) + { + // The Binder will pre-increment the sequence. + mediaTuple.LastSequence = transformMediaRow.LastSequence; + } + } + } + + // Use the Media/@DiskId if greater than the last sequence for backward compatibility. + if (mediaTuple.LastSequence < mediaTuple.DiskId) + { + mediaTuple.LastSequence = mediaTuple.DiskId; + } + + // Ignore media table in the transform. + mainTransform.Transform.Tables.Remove("Media"); + mainTransform.Transform.Tables.Remove("WixMedia"); + mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); + + var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchIdTuple, mainTransform.Transform, mediaTuple, baselineTuple, out var productCode); + + productCode = productCode.ToUpperInvariant(); + productCodes.Add(productCode); + validTransform.Add(Tuple.Create(productCode, mainTransform.Transform)); + + // attach these transforms to the patch object + // TODO: is this an acceptable way to auto-generate transform stream names? + var transformName = mainTransform.Baseline + "." + validTransform.Count.ToString(CultureInfo.InvariantCulture); + subStorages.Add(new SubStorage(transformName, mainTransform.Transform)); + subStorages.Add(new SubStorage("#" + transformName, pairedTransform)); + + transformNames.Add(":" + transformName); + transformNames.Add(":#" + transformName); + } + + if (validTransform.Count == 0) + { + this.Messaging.Write(ErrorMessages.PatchWithoutValidTransforms()); + return subStorages; + } + + // Validate that a patch authored as removable is actually removable + if (patchMetadata.TryGetValue("AllowRemoval", out var allowRemoval) && allowRemoval.Value == "1") + { + var uninstallable = true; + + foreach (var entry in validTransform) + { + uninstallable &= this.CheckUninstallableTransform(entry.Item1, entry.Item2); + } + + if (!uninstallable) + { + this.Messaging.Write(ErrorMessages.PatchNotRemovable()); + return subStorages; + } + } + + // Finish filling tables with transform-dependent data. + productCodes = FinalizePatchProductCodes(tuples, productCodes); + + // Semicolon delimited list of the product codes that can accept the patch. + summaryInfo.Add(SumaryInformationType.PatchProductCodes, new SummaryInformationTuple(patchIdTuple.SourceLineNumbers) + { + PropertyId = SumaryInformationType.PatchProductCodes, + Value = String.Join(";", productCodes) + }); + + // Semicolon delimited list of transform substorage names in the order they are applied. + summaryInfo.Add(SumaryInformationType.TransformNames, new SummaryInformationTuple(patchIdTuple.SourceLineNumbers) + { + PropertyId = SumaryInformationType.TransformNames, + Value = String.Join(";", transformNames) + }); + + // Put the summary information that was extracted back in now that it is updated. + foreach (var readSummaryInfo in summaryInfo.Values.OrderBy(s => s.PropertyId)) + { + section.Tuples.Add(readSummaryInfo); + } + + this.SubStorages = subStorages; + + return subStorages; + } + + private Dictionary ExtractPatchSummaryInfo() + { + var result = new Dictionary(); + + foreach (var section in this.Intermediate.Sections) + { + for (var i = section.Tuples.Count - 1; i >= 0; i--) + { + if (section.Tuples[i] is SummaryInformationTuple patchSummaryInfo) + { + // Remove all summary information from the tuples and remember those that + // are not calculated or reserved. + section.Tuples.RemoveAt(i); + + if (patchSummaryInfo.PropertyId != SumaryInformationType.PatchProductCodes && + patchSummaryInfo.PropertyId != SumaryInformationType.PatchCode && + patchSummaryInfo.PropertyId != SumaryInformationType.PatchInstallerRequirement && + patchSummaryInfo.PropertyId != SumaryInformationType.Reserved11 && + patchSummaryInfo.PropertyId != SumaryInformationType.Reserved14 && + patchSummaryInfo.PropertyId != SumaryInformationType.Reserved16) + { + result.Add(patchSummaryInfo.PropertyId, patchSummaryInfo); + } + } + } + } + + return result; + } + + private Dictionary PopulateSummaryInformation(Dictionary summaryInfo, List tuples, WixPatchIdTuple patchIdTuple, int codepage) + { + // PID_CODEPAGE + if (!summaryInfo.ContainsKey(SumaryInformationType.Codepage)) + { + // Set the code page by default to the same code page for the + // string pool in the database. + AddSummaryInformation(SumaryInformationType.Codepage, codepage.ToString(CultureInfo.InvariantCulture), patchIdTuple.SourceLineNumbers); + } + + // GUID patch code for the patch. + AddSummaryInformation(SumaryInformationType.PatchCode, patchIdTuple.Id.Id, patchIdTuple.SourceLineNumbers); + + // Indicates the minimum Windows Installer version that is required to install the patch. + AddSummaryInformation(SumaryInformationType.PatchInstallerRequirement, ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture), patchIdTuple.SourceLineNumbers); + + if (!summaryInfo.ContainsKey(SumaryInformationType.Security)) + { + AddSummaryInformation(SumaryInformationType.Security, "4", patchIdTuple.SourceLineNumbers); // Read-only enforced; + } + + // Use authored comments or default to display name. + MsiPatchMetadataTuple commentsTuple = null; + + var metadataTuples = tuples.OfType().Where(t => String.IsNullOrEmpty(t.Company)).ToDictionary(t => t.Property); + + if (!summaryInfo.ContainsKey(SumaryInformationType.Title) && + metadataTuples.TryGetValue("DisplayName", out var displayName)) + { + AddSummaryInformation(SumaryInformationType.Title, displayName.Value, displayName.SourceLineNumbers); + + // Default comments to use display name as-is. + commentsTuple = displayName; + } + + // TODO: This code below seems unnecessary given the codepage is set at the top of this method. + //if (!summaryInfo.ContainsKey(SumaryInformationType.Codepage) && + // metadataValues.TryGetValue("CodePage", out var codepage)) + //{ + // AddSummaryInformation(SumaryInformationType.Codepage, codepage); + //} + + if (!summaryInfo.ContainsKey(SumaryInformationType.PatchPackageName) && + metadataTuples.TryGetValue("Description", out var description)) + { + AddSummaryInformation(SumaryInformationType.PatchPackageName, description.Value, description.SourceLineNumbers); + } + + if (!summaryInfo.ContainsKey(SumaryInformationType.Author) && + metadataTuples.TryGetValue("ManufacturerName", out var manufacturer)) + { + AddSummaryInformation(SumaryInformationType.Author, manufacturer.Value, manufacturer.SourceLineNumbers); + } + + // Special metadata marshalled through the build. + //var wixMetadataValues = tuples.OfType().ToDictionary(t => t.Id.Id, t => t.Value); + + //if (wixMetadataValues.TryGetValue("Comments", out var wixComments)) + if (metadataTuples.TryGetValue("Comments", out var wixComments)) + { + commentsTuple = wixComments; + } + + // Write the package comments to summary info. + if (!summaryInfo.ContainsKey(SumaryInformationType.Comments) && + commentsTuple != null) + { + AddSummaryInformation(SumaryInformationType.Comments, commentsTuple.Value, commentsTuple.SourceLineNumbers); + } + + return metadataTuples; + + void AddSummaryInformation(SumaryInformationType type, string value, SourceLineNumber sourceLineNumber) + { + summaryInfo.Add(type, new SummaryInformationTuple(sourceLineNumber) + { + PropertyId = type, + Value = value + }); + } + } + + /// + /// Ensure transform is uninstallable. + /// + /// Product code in transform. + /// Transform generated by torch. + /// True if the transform is uninstallable + private bool CheckUninstallableTransform(string productCode, WindowsInstallerData transform) + { + var success = true; + + foreach (var tableName in PatchUninstallBreakingTables) + { + if (transform.TryGetTable(tableName, out var table)) + { + foreach (var row in table.Rows) + { + if (row.Operation == RowOperation.Add) + { + success = false; + + var primaryKey = row.GetPrimaryKey('/') ?? String.Empty; + + this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey)); + } + } + } + } + + return success; + } + + /// + /// Reduce the transform according to the patch references. + /// + /// transform generated by torch. + /// Table contains patch family filter. + /// true if the transform is not empty + private bool ReduceTransform(WindowsInstallerData transform, IEnumerable patchRefTuples) + { + // identify sections to keep + var oldSections = new Dictionary(); + var newSections = new Dictionary(); + var tableKeyRows = new Dictionary>(); + var sequenceList = new List(); + var componentFeatureAddsIndex = new Dictionary>(); + var customActionTable = new Dictionary(); + var directoryTableAdds = new Dictionary(); + var featureTableAdds = new Dictionary(); + var keptComponents = new Dictionary(); + var keptDirectories = new Dictionary(); + var keptFeatures = new Dictionary(); + var keptLockPermissions = new HashSet(); + var keptMsiLockPermissionExs = new HashSet(); + + var componentCreateFolderIndex = new Dictionary>(); + var directoryLockPermissionsIndex = new Dictionary>(); + var directoryMsiLockPermissionsExIndex = new Dictionary>(); + + foreach (var patchRefTuple in patchRefTuples) + { + var tableName = patchRefTuple.Table; + var key = patchRefTuple.PrimaryKeys; + + // Short circuit filtering if all changes should be included. + if ("*" == tableName && "*" == key) + { + RemoveProductCodeFromTransform(transform); + return true; + } + + if (!transform.Tables.TryGetTable(tableName, out var table)) + { + // Table not found. + continue; + } + + // Index the table. + if (!tableKeyRows.TryGetValue(tableName, out var keyRows)) + { + keyRows = new Dictionary(); + tableKeyRows.Add(tableName, keyRows); + + foreach (var newRow in table.Rows) + { + var primaryKey = newRow.GetPrimaryKey(); + keyRows.Add(primaryKey, newRow); + } + } + + if (!keyRows.TryGetValue(key, out var row)) + { + // Row not found. + continue; + } + + // Differ.sectionDelimiter + var sections = row.SectionId.Split('/'); + oldSections[sections[0]] = row; + newSections[sections[1]] = row; + } + + // throw away sections not referenced + var keptRows = 0; + Table directoryTable = null; + Table featureTable = null; + Table lockPermissionsTable = null; + Table msiLockPermissionsTable = null; + + foreach (var table in transform.Tables) + { + if ("_SummaryInformation" == table.Name) + { + continue; + } + + if (table.Name == "AdminExecuteSequence" + || table.Name == "AdminUISequence" + || table.Name == "AdvtExecuteSequence" + || table.Name == "InstallUISequence" + || table.Name == "InstallExecuteSequence") + { + sequenceList.Add(table); + continue; + } + + for (var i = 0; i < table.Rows.Count; i++) + { + var row = table.Rows[i]; + + if (table.Name == "CreateFolder") + { + var createFolderComponentId = row.FieldAsString(1); + + if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList)) + { + directoryList = new List(); + componentCreateFolderIndex.Add(createFolderComponentId, directoryList); + } + + directoryList.Add(row.FieldAsString(0)); + } + + if (table.Name == "CustomAction") + { + customActionTable.Add(row.FieldAsString(0), row); + } + + if (table.Name == "Directory") + { + directoryTable = table; + if (RowOperation.Add == row.Operation) + { + directoryTableAdds.Add(row.FieldAsString(0), row); + } + } + + if (table.Name == "Feature") + { + featureTable = table; + if (RowOperation.Add == row.Operation) + { + featureTableAdds.Add(row.FieldAsString(0), row); + } + } + + if (table.Name == "FeatureComponents") + { + if (RowOperation.Add == row.Operation) + { + var featureId = row.FieldAsString(0); + var componentId = row.FieldAsString(1); + + if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList)) + { + featureList = new List(); + componentFeatureAddsIndex.Add(componentId, featureList); + } + + featureList.Add(featureId); + } + } + + if (table.Name == "LockPermissions") + { + lockPermissionsTable = table; + if ("CreateFolder" == row.FieldAsString(1)) + { + var directoryId = row.FieldAsString(0); + + if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList)) + { + rowList = new List(); + directoryLockPermissionsIndex.Add(directoryId, rowList); + } + + rowList.Add(row); + } + } + + if (table.Name == "MsiLockPermissionsEx") + { + msiLockPermissionsTable = table; + if ("CreateFolder" == row.FieldAsString(1)) + { + var directoryId = row.FieldAsString(0); + + if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList)) + { + rowList = new List(); + directoryMsiLockPermissionsExIndex.Add(directoryId, rowList); + } + + rowList.Add(row); + } + } + + if (null == row.SectionId) + { + table.Rows.RemoveAt(i); + i--; + } + else + { + var sections = row.SectionId.Split('/'); + // ignore the row without section id. + if (0 == sections[0].Length && 0 == sections[1].Length) + { + table.Rows.RemoveAt(i); + i--; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + if ("Component" == table.Name) + { + keptComponents.Add(row.FieldAsString(0), row); + } + + if ("Directory" == table.Name) + { + keptDirectories.Add(row.FieldAsString(0), row); + } + + if ("Feature" == table.Name) + { + keptFeatures.Add(row.FieldAsString(0), row); + } + + keptRows++; + } + else + { + table.Rows.RemoveAt(i); + i--; + } + } + } + } + + keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); + + if (null != directoryTable) + { + foreach (var componentRow in keptComponents.Values) + { + var componentId = componentRow.FieldAsString(0); + + if (RowOperation.Add == componentRow.Operation) + { + // Make sure each added component has its required directory and feature heirarchy. + var directoryId = componentRow.FieldAsString(2); + while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow)) + { + if (!keptDirectories.ContainsKey(directoryId)) + { + directoryTable.Rows.Add(directoryRow); + keptDirectories.Add(directoryId, directoryRow); + keptRows++; + } + + directoryId = directoryRow.FieldAsString(1); + } + + if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds)) + { + foreach (var featureId in componentFeatureIds) + { + var currentFeatureId = featureId; + while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow)) + { + if (!keptFeatures.ContainsKey(currentFeatureId)) + { + featureTable.Rows.Add(featureRow); + keptFeatures.Add(currentFeatureId, featureRow); + keptRows++; + } + + currentFeatureId = featureRow.FieldAsString(1); + } + } + } + } + + // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept. + foreach (var keptComponentId in keptComponents.Keys) + { + if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList)) + { + foreach (var directoryId in directoryList) + { + if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList)) + { + foreach (var lockPermissionsRow in lockPermissionsRowList) + { + var key = lockPermissionsRow.GetPrimaryKey('/'); + if (keptLockPermissions.Add(key)) + { + lockPermissionsTable.Rows.Add(lockPermissionsRow); + keptRows++; + } + } + } + + if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList)) + { + foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList) + { + var key = msiLockPermissionsExRow.GetPrimaryKey('/'); + if (keptMsiLockPermissionExs.Add(key)) + { + msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow); + keptRows++; + } + } + } + } + } + } + } + } + + keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); + + // Delete tables that are empty. + var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name); + + foreach (var tableName in tablesToDelete) + { + transform.Tables.Remove(tableName); + } + + return keptRows > 0; + } + + private void Validate(PatchTransform patchTransform) + { + var transformPath = patchTransform.Baseline; // TODO: this is used in error messages, how best to set it? + var transform = patchTransform.Transform; + + // Changing the ProdocutCode in a patch transform is not recommended. + if (transform.TryGetTable("Property", out var propertyTable)) + { + foreach (var row in propertyTable.Rows) + { + // Only interested in modified rows; fast check. + if (RowOperation.Modify == row.Operation && + "ProductCode".Equals(row.FieldAsString(0), StringComparison.Ordinal)) + { + this.Messaging.Write(WarningMessages.MajorUpgradePatchNotRecommended()); + } + } + } + + // If there is nothing in the component table we can return early because the remaining checks are component based. + if (!transform.TryGetTable("Component", out var componentTable)) + { + return; + } + + // Index Feature table row operations + var featureOps = new Dictionary(); + if (transform.TryGetTable("Feature", out var featureTable)) + { + foreach (var row in featureTable.Rows) + { + featureOps[row.FieldAsString(0)] = row.Operation; + } + } + + // Index Component table and check for keypath modifications + var componentKeyPath = new Dictionary(); + var deletedComponent = new Dictionary(); + foreach (var row in componentTable.Rows) + { + var id = row.FieldAsString(0); + var keypath = row.FieldAsString(5) ?? String.Empty; + + componentKeyPath.Add(id, keypath); + + if (RowOperation.Delete == row.Operation) + { + deletedComponent.Add(id, row); + } + else if (RowOperation.Modify == row.Operation) + { + if (row.Fields[1].Modified) + { + // Changing the guid of a component is equal to deleting the old one and adding a new one. + deletedComponent.Add(id, row); + } + + // If the keypath is modified its an error + if (row.Fields[5].Modified) + { + this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, id, transformPath)); + } + } + } + + // Verify changes in the file table + if (transform.TryGetTable("File", out var fileTable)) + { + var componentWithChangedKeyPath = new Dictionary(); + foreach (var row in fileTable.Rows) + { + if (RowOperation.None == row.Operation) + { + continue; + } + + var fileId = row.FieldAsString(0); + var componentId = row.FieldAsString(1); + + // If this file is the keypath of a component + if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal)) + { + if (row.Fields[2].Modified) + { + // You can't change the filename of a file that is the keypath of a component. + this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, componentId, transformPath)); + } + + if (!componentWithChangedKeyPath.ContainsKey(componentId)) + { + componentWithChangedKeyPath.Add(componentId, fileId); + } + } + + if (RowOperation.Delete == row.Operation) + { + // If the file is removed from a component that is not deleted. + if (!deletedComponent.ContainsKey(componentId)) + { + var foundRemoveFileEntry = false; + var filename = Common.GetName(row.FieldAsString(2), false, true); + + if (transform.TryGetTable("RemoveFile", out var removeFileTable)) + { + foreach (var removeFileRow in removeFileTable.Rows) + { + if (RowOperation.Delete == removeFileRow.Operation) + { + continue; + } + + if (componentId == removeFileRow.FieldAsString(1)) + { + // Check if there is a RemoveFile entry for this file + if (null != removeFileRow[2]) + { + var removeFileName = Common.GetName(removeFileRow.FieldAsString(2), false, true); + + // Convert the MSI format for a wildcard string to Regex format. + removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); + + var regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + if (regex.IsMatch(filename)) + { + foundRemoveFileEntry = true; + break; + } + } + } + } + } + + if (!foundRemoveFileEntry) + { + this.Messaging.Write(WarningMessages.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId)); + } + } + } + } + } + + var featureComponentsTable = transform.Tables["FeatureComponents"]; + + if (0 < deletedComponent.Count) + { + // Index FeatureComponents table. + var featureComponents = new Dictionary>(); + + if (null != featureComponentsTable) + { + foreach (var row in featureComponentsTable.Rows) + { + var componentId = row.FieldAsString(1); + + if (!featureComponents.TryGetValue(componentId, out var features)) + { + features = new List(); + featureComponents.Add(componentId, features); + } + + features.Add(row.FieldAsString(0)); + } + } + + // Check to make sure if a component was deleted, the feature was too. + foreach (var entry in deletedComponent) + { + if (featureComponents.TryGetValue(entry.Key, out var features)) + { + foreach (var featureId in features) + { + if (!featureOps.TryGetValue(featureId, out var op) || op != RowOperation.Delete) + { + // The feature was not deleted. + this.Messaging.Write(ErrorMessages.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, transformPath)); + } + } + } + } + } + + // Warn if new components are added to existing features + if (null != featureComponentsTable) + { + foreach (var row in featureComponentsTable.Rows) + { + if (RowOperation.Add == row.Operation) + { + // Check if the feature is in the Feature table + var feature_ = row.FieldAsString(0); + var component_ = row.FieldAsString(1); + + // Features may not be present if not referenced + if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_]) + { + this.Messaging.Write(WarningMessages.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, transformPath)); + } + } + } + } + } + + /// + /// Remove the ProductCode property from the transform. + /// + /// The transform. + /// + /// Changing the ProductCode is not supported in a patch. + /// + private static void RemoveProductCodeFromTransform(WindowsInstallerData transform) + { + if (transform.Tables.TryGetTable("Property", out var propertyTable)) + { + for (var i = 0; i < propertyTable.Rows.Count; ++i) + { + var propertyRow = propertyTable.Rows[i]; + var property = (string)propertyRow[0]; + + if ("ProductCode" == property) + { + propertyTable.Rows.RemoveAt(i); + break; + } + } + } + } + + /// + /// Check if the section is in a PatchFamily. + /// + /// Section id in target wixout + /// Section id in upgrade wixout + /// Dictionary contains section id should be kept in the baseline wixout. + /// Dictionary contains section id should be kept in the upgrade wixout. + /// true if section in patch family + private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary oldSections, Dictionary newSections) + { + var result = false; + + if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection))) + { + result = true; + } + else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(newSection))) + { + result = true; + } + + return result; + } + + /// + /// Reduce the transform sequence tables. + /// + /// ArrayList of tables to be reduced + /// Hashtable contains section id should be kept in the baseline wixout. + /// Hashtable contains section id should be kept in the target wixout. + /// Hashtable contains all the rows in the CustomAction table. + /// Number of rows left + private static int ReduceTransformSequenceTable(List
sequenceList, Dictionary oldSections, Dictionary newSections, Dictionary customAction) + { + var keptRows = 0; + + foreach (var currentTable in sequenceList) + { + for (var i = 0; i < currentTable.Rows.Count; i++) + { + var row = currentTable.Rows[i]; + var actionName = row.Fields[0].Data.ToString(); + var sections = row.SectionId.Split('/'); + var isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0); + + if (row.Operation == RowOperation.None) + { + // ignore the rows without section id. + if (isSectionIdEmpty) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + currentTable.Rows.RemoveAt(i); + i--; + } + } + else if (row.Operation == RowOperation.Modify) + { + var sequenceChanged = row.Fields[2].Modified; + var conditionChanged = row.Fields[1].Modified; + + if (sequenceChanged && !conditionChanged) + { + keptRows++; + } + else if (!sequenceChanged && conditionChanged) + { + if (isSectionIdEmpty) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + currentTable.Rows.RemoveAt(i); + i--; + } + } + else if (sequenceChanged && conditionChanged) + { + if (isSectionIdEmpty) + { + row.Fields[1].Modified = false; + keptRows++; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + row.Fields[1].Modified = false; + keptRows++; + } + } + } + else if (row.Operation == RowOperation.Delete) + { + if (isSectionIdEmpty) + { + // it is a stardard action which is added by wix, we should keep this action. + row.Operation = RowOperation.None; + keptRows++; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + if (customAction.ContainsKey(actionName)) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else + { + // it is a stardard action, we should keep this action. + row.Operation = RowOperation.None; + keptRows++; + } + } + } + else if (row.Operation == RowOperation.Add) + { + if (isSectionIdEmpty) + { + keptRows++; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + if (customAction.ContainsKey(actionName)) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else + { + keptRows++; + } + } + } + } + } + + return keptRows; + } + + /// + /// Create the #transform for the given main transform. + /// + private WindowsInstallerData BuildPairedTransform(Dictionary summaryInfo, Dictionary patchMetadata, WixPatchIdTuple patchIdTuple, WindowsInstallerData mainTransform, MediaTuple mediaTuple, WixPatchBaselineTuple baselineTuple, out string productCode) + { + productCode = null; + + var pairedTransform = new WindowsInstallerData(null) + { + Type = OutputType.Transform, + Codepage = mainTransform.Codepage + }; + + // lookup productVersion property to correct summaryInformation + var newProductVersion = mainTransform.Tables["Property"]?.Rows.FirstOrDefault(r => r.FieldAsString(0) == "ProductVersion")?.FieldAsString(1); + + var mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; + var mainSummaryRows = mainSummaryTable.Rows.ToDictionary(r => r.FieldAsInteger(0)); + + var baselineValidationFlags = ((int)baselineTuple.ValidationFlags).ToString(CultureInfo.InvariantCulture); + + if (!mainSummaryRows.ContainsKey((int)SumaryInformationType.TransformValidationFlags)) + { + var mainSummaryRow = mainSummaryTable.CreateRow(baselineTuple.SourceLineNumbers); + mainSummaryRow[0] = (int)SumaryInformationType.TransformValidationFlags; + mainSummaryRow[1] = baselineValidationFlags; + } + + // copy summary information from core transform + var pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); + + foreach (var mainSummaryRow in mainSummaryTable.Rows) + { + var type = (SumaryInformationType)mainSummaryRow.FieldAsInteger(0); + var value = mainSummaryRow.FieldAsString(1); + switch (type) + { + case SumaryInformationType.TransformProductCodes: + var propertyData = value.Split(';'); + var oldProductVersion = propertyData[0].Substring(38); + var upgradeCode = propertyData[2]; + productCode = propertyData[0].Substring(0, 38); + + if (newProductVersion == null) + { + newProductVersion = oldProductVersion; + } + + // Force mainTranform to 'old;new;upgrade' and pairedTransform to 'new;new;upgrade' + mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); + value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); + break; + case SumaryInformationType.TransformValidationFlags: // use validation flags authored into the patch XML. + value = baselineValidationFlags; + mainSummaryRow[1] = value; + break; + } + + var pairedSummaryRow = pairedSummaryTable.CreateRow(mainSummaryRow.SourceLineNumbers); + pairedSummaryRow[0] = mainSummaryRow[0]; + pairedSummaryRow[1] = value; + } + + if (productCode == null) + { + this.Messaging.Write(ErrorMessages.CouldNotDetermineProductCodeFromTransformSummaryInfo()); + return null; + } + + // copy File table + if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count) + { +#if TODO_PATCHING + // We require file source information. + var mainWixFileTable = mainTransform.Tables["WixFile"]; + if (null == mainWixFileTable) + { + this.Messaging.Write(ErrorMessages.AdminImageRequired(productCode)); + return null; + } + + var mainFileRows = new RowDictionary(mainFileTable); + + var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); + { + var mainFileRow = mainFileRows[mainWixFileRow.File]; + + // set File.Sequence to non null to satisfy transform bind + mainFileRow.Sequence = 1; + + // delete's don't need rows in the paired transform + if (mainFileRow.Operation == RowOperation.Delete) + { + continue; + } + + var pairedFileRow = (FileRow)pairedFileTable.CreateRow(null); + pairedFileRow.Operation = RowOperation.Modify; + for (var i = 0; i < mainFileRow.Fields.Length; i++) + { + pairedFileRow[i] = mainFileRow[i]; + } + + // override authored media for patch bind + mainWixFileRow.DiskId = mediaTuple.DiskId; + + // suppress any change to File.Sequence to avoid bloat + mainFileRow.Fields[7].Modified = false; + + // force File row to appear in the transform + switch (mainFileRow.Operation) + { + case RowOperation.Modify: + case RowOperation.Add: + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = mainFileRow.Operation; + break; + default: + pairedFileRow.Fields[6].Modified = false; + break; + } + } +#endif + } + + // Add Media row to pairedTransform + var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); + var pairedMediaRow = pairedMediaTable.CreateRow(mediaTuple.SourceLineNumbers); + pairedMediaRow.Operation = RowOperation.Add; + pairedMediaRow[0] = mediaTuple.DiskId; + pairedMediaRow[1] = mediaTuple.LastSequence ?? 0; + pairedMediaRow[2] = mediaTuple.DiskPrompt; + pairedMediaRow[3] = mediaTuple.Cabinet; + pairedMediaRow[4] = mediaTuple.VolumeLabel; + pairedMediaRow[5] = mediaTuple.Source; + + // Add PatchPackage for this Media + var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); + pairedPackageTable.Operation = TableOperation.Add; + var pairedPackageRow = pairedPackageTable.CreateRow(mediaTuple.SourceLineNumbers); + pairedPackageRow.Operation = RowOperation.Add; + pairedPackageRow[0] = patchIdTuple.Id.Id; + pairedPackageRow[1] = mediaTuple.DiskId; + + // Add the property to the patch transform's Property table. + var pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); + pairedPropertyTable.Operation = TableOperation.Add; + + // Add property to both identify client patches and whether those patches are removable or not + patchMetadata.TryGetValue("AllowRemoval", out var allowRemovalTuple); + + var pairedPropertyRow = pairedPropertyTable.CreateRow(allowRemovalTuple?.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = String.Concat(patchIdTuple.ClientPatchId, ".AllowRemoval"); + pairedPropertyRow[1] = allowRemovalTuple?.Value ?? "0"; + + // Add this patch code GUID to the patch transform to identify + // which patches are installed, including in multi-patch + // installations. + pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdTuple.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = String.Concat(patchIdTuple.ClientPatchId, ".PatchCode"); + pairedPropertyRow[1] = patchIdTuple.Id.Id; + + // Add PATCHNEWPACKAGECODE to apply to admin layouts. + pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdTuple.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; + pairedPropertyRow[1] = patchIdTuple.Id.Id; + + // Add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts. + if (summaryInfo.TryGetValue(SumaryInformationType.Subject, out var subjectTuple)) + { + pairedPropertyRow = pairedPropertyTable.CreateRow(subjectTuple.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; + pairedPropertyRow[1] = subjectTuple.Value; + } + + if (summaryInfo.TryGetValue(SumaryInformationType.Comments, out var commentsTuple)) + { + pairedPropertyRow = pairedPropertyTable.CreateRow(commentsTuple.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; + pairedPropertyRow[1] = commentsTuple.Value; + } + + return pairedTransform; + } + + private static SortedSet FinalizePatchProductCodes(List tuples, SortedSet productCodes) + { + var patchTargetTuples = tuples.OfType().ToList(); + + if (patchTargetTuples.Count > 0) + { + var targets = new SortedSet(); + var replace = true; + foreach (var wixPatchTargetRow in patchTargetTuples) + { + var target = wixPatchTargetRow.ProductCode.ToUpperInvariant(); + if (target == "*") + { + replace = false; + } + else + { + targets.Add(target); + } + } + + // Replace the target ProductCodes with the authored list. + if (replace) + { + productCodes = targets; + } + else + { + // Copy the authored target ProductCodes into the list. + foreach (var target in targets) + { + productCodes.Add(target); + } + } + } + + return productCodes; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 175203ce..34104ef5 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -24,7 +24,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind private bool disposed; - public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, Validator validator) + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, Validator validator):this(context, backendExtension, null, validator) + { + } + + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, Validator validator) { this.ServiceProvider = context.ServiceProvider; @@ -45,6 +49,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.OutputPath = context.OutputPath; this.OutputPdbPath = context.OutputPdbPath; this.IntermediateFolder = context.IntermediateFolder; + this.SubStorages = subStorages; this.Validator = validator; this.BackendExtensions = backendExtension; @@ -76,6 +81,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IEnumerable BackendExtensions { get; } + private IEnumerable SubStorages { get; } + private Intermediate Intermediate { get; } private string OutputPath { get; } @@ -112,18 +119,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Load standard tables, authored custom tables, and extension custom tables. TableDefinitionCollection tableDefinitions; { - var command = new LoadTableDefinitionsCommand(section); + var command = new LoadTableDefinitionsCommand(section, this.BackendExtensions); command.Execute(); tableDefinitions = command.TableDefinitions; - - foreach (var backendExtension in this.BackendExtensions) - { - foreach (var tableDefinition in backendExtension.TableDefinitions) - { - tableDefinitions.Add(tableDefinition); - } - } } // Process the summary information table before the other tables. @@ -186,8 +185,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Sequence all the actions. { - var command = new SequenceActionsCommand(section); - command.Messaging = this.Messaging; + var command = new SequenceActionsCommand(this.Messaging, section); command.Execute(); } @@ -196,7 +194,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind command.Execute(); } -#if TODO_FINISH_PATCH +#if TODO_PATCHING ////if (OutputType.Patch == this.Output.Type) ////{ //// foreach (SubStorage substorage in this.Output.SubStorages) @@ -223,130 +221,162 @@ namespace WixToolset.Core.WindowsInstaller.Bind return; } - this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); + // Call extension + var ExtensionSaidSkip = false; - // This must occur after all variables and source paths have been resolved. - List fileFacades; + WindowsInstallerData output; + if (ExtensionSaidSkip) { - var command = new GetFileFacadesCommand(section); + // Time to create the output object, since we're bypassing everything that touches files. + var command = new CreateOutputFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions); command.Execute(); - fileFacades = command.FileFacades; + output = command.Output; } - - // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). + else { - var command = new ExtractEmbeddedFilesCommand(this.ExpectedEmbeddedFiles); - command.Execute(); - } + this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); - // Gather information about files that do not come from merge modules. - { - var command = new UpdateFileFacadesCommand(this.Messaging, section); - command.FileFacades = fileFacades; - command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule); - command.OverwriteHash = true; - command.TableDefinitions = tableDefinitions; - command.VariableCache = variableCache; - command.Execute(); - } + // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). + { + var command = new ExtractEmbeddedFilesCommand(this.ExpectedEmbeddedFiles); + command.Execute(); + } - // Now that the variable cache is populated, resolve any delayed fields. - if (this.DelayedFields.Any()) - { - var command = new ResolveDelayedFieldsCommand(this.Messaging, this.DelayedFields, variableCache); - command.Execute(); - } + // This must occur after all variables and source paths have been resolved. + List fileFacades; + { + var command = new GetFileFacadesCommand(section); + command.Execute(); - // Set generated component guids. - { - var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, this.PathResolver, section); - command.Execute(); - } + fileFacades = command.FileFacades; + } - // Retrieve file information from merge modules. - if (SectionType.Product == section.Type) - { - var wixMergeTuples = section.Tuples.OfType().ToList(); + // Retrieve file information from merge modules. + if (SectionType.Product == section.Type) + { + var wixMergeTuples = section.Tuples.OfType().ToList(); + + if (wixMergeTuples.Any()) + { + containsMergeModules = true; + + var command = new ExtractMergeModuleFilesCommand(this.Messaging, section, wixMergeTuples); + command.FileFacades = fileFacades; + command.OutputInstallerVersion = installerVersion; + command.SuppressLayout = this.SuppressLayout; + command.IntermediateFolder = this.IntermediateFolder; + command.Execute(); - if (wixMergeTuples.Any()) + fileFacades.AddRange(command.MergeModulesFileFacades); + } + } + else if (SectionType.Patch == section.Type) { - containsMergeModules = true; + // Merge transform data into the output object. + //IEnumerable filesFromTransform = this.CopyFromTransformData(this.Output); + + //var command = new CopyTransformDataCommand(this.Messaging, /*output*/this.SubStorages, tableDefinitions, copyOutFileRows: true); + //command.Output = output; + //command.TableDefinitions = this.TableDefinitions; + //command.CopyOutFileRows = true; + var command = new GetFileFacadesFromTransforms(this.Messaging, this.SubStorages, tableDefinitions); + command.Execute(); + var filesFromTransforms = command.FileFacades; - var command = new ExtractMergeModuleFilesCommand(this.Messaging, section, wixMergeTuples); + fileFacades.AddRange(filesFromTransforms); + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return; + } + + // Gather information about files that do not come from merge modules. + { + var command = new UpdateFileFacadesCommand(this.Messaging, section); command.FileFacades = fileFacades; - command.OutputInstallerVersion = installerVersion; - command.SuppressLayout = this.SuppressLayout; - command.IntermediateFolder = this.IntermediateFolder; + command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule); + command.OverwriteHash = true; + command.TableDefinitions = tableDefinitions; + command.VariableCache = variableCache; command.Execute(); - - fileFacades.AddRange(command.MergeModulesFileFacades); } - } -#if TODO_FINISH_PATCH - else if (OutputType.Patch == this.Output.Type) - { - // Merge transform data into the output object. - IEnumerable filesFromTransform = this.CopyFromTransformData(this.Output); - fileFacades.AddRange(filesFromTransform); - } -#endif + // Assign files to media. + Dictionary assignedMediaRows; + Dictionary> filesByCabinetMedia; + IEnumerable uncompressedFiles; + { + var command = new AssignMediaCommand(section, this.Messaging); + command.FileFacades = fileFacades; + command.FilesCompressed = compressed; + command.Execute(); - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return; - } + assignedMediaRows = command.MediaRows; + filesByCabinetMedia = command.FileFacadesByCabinetMedia; + uncompressedFiles = command.UncompressedFileFacades; + } - // Assign files to media. - Dictionary assignedMediaRows; - Dictionary> filesByCabinetMedia; - IEnumerable uncompressedFiles; - { - var command = new AssignMediaCommand(section, this.Messaging); - command.FileFacades = fileFacades; - command.FilesCompressed = compressed; - command.Execute(); + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return; + } - assignedMediaRows = command.MediaRows; - filesByCabinetMedia = command.FileFacadesByCabinetMedia; - uncompressedFiles = command.UncompressedFileFacades; - } + // Now that the variable cache is populated, resolve any delayed fields. + if (this.DelayedFields.Any()) + { + var command = new ResolveDelayedFieldsCommand(this.Messaging, this.DelayedFields, variableCache); + command.Execute(); + } - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return; - } + // Set generated component guids. + { + var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, this.PathResolver, section); + command.Execute(); + } - // Time to create the output object. Try to put as much above here as possible, updating the IR is better. - WindowsInstallerData output; - { - var command = new CreateOutputFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions); - command.Execute(); + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return; + } - output = command.Output; - } + // Time to create the output object. Try to put as much above here as possible, updating the IR is better. + { + var command = new CreateOutputFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions); + command.Execute(); - // Update file sequence. - { - var command = new UpdateMediaSequencesCommand(output, fileFacades); - command.Execute(); - } + output = command.Output; + } - // Modularize identifiers. - if (OutputType.Module == output.Type) - { - var command = new ModularizeCommand(output, modularizationGuid, section.Tuples.OfType()); - command.Execute(); - } - else // we can create instance transforms since Component Guids are set. - { + // Update file sequence. + { + var command = new UpdateMediaSequencesCommand(output, fileFacades); + command.Execute(); + } + + // Modularize identifiers. + if (OutputType.Module == output.Type) + { + var command = new ModularizeCommand(output, modularizationGuid, section.Tuples.OfType()); + command.Execute(); + } + else if (output.Type == OutputType.Patch) + { + foreach (var storage in this.SubStorages) + { + output.SubStorages.Add(storage); + } + } + else // we can create instance transforms since Component Guids are set. + { #if TODO_FIX_INSTANCE_TRANSFORM - this.CreateInstanceTransforms(this.Output); + this.CreateInstanceTransforms(this.Output); #endif - } + } #if TODO_FINISH_UPDATE // Extended binder extensions can be called now that fields are resolved. @@ -384,116 +414,121 @@ namespace WixToolset.Core.WindowsInstaller.Bind } #endif - // Stop processing if an error previously occurred. - if (this.Messaging.EncounteredError) - { - return; - } + this.ValidateComponentGuids(output); - // Ensure the intermediate folder is created since delta patches will be - // created there. - Directory.CreateDirectory(this.IntermediateFolder); + // Stop processing if an error previously occurred. + if (this.Messaging.EncounteredError) + { + return; + } - if (SectionType.Patch == section.Type && this.DeltaBinaryPatch) - { - var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Tuples.OfType().FirstOrDefault()); - command.Execute(); - } + // Ensure the intermediate folder is created since delta patches will be + // created there. + Directory.CreateDirectory(this.IntermediateFolder); - // create cabinet files and process uncompressed files - var layoutDirectory = Path.GetDirectoryName(this.OutputPath); - if (!this.SuppressLayout || OutputType.Module == output.Type) - { - this.Messaging.Write(VerboseMessages.CreatingCabinetFiles()); - - var command = new CreateCabinetsCommand(this.ServiceProvider, this.BackendHelper); - command.CabbingThreadCount = this.CabbingThreadCount; - command.CabCachePath = this.CabCachePath; - command.DefaultCompressionLevel = this.DefaultCompressionLevel; - command.Output = output; - command.Messaging = this.Messaging; - command.BackendExtensions = this.BackendExtensions; - command.LayoutDirectory = layoutDirectory; - command.Compressed = compressed; - command.FileRowsByCabinet = filesByCabinetMedia; - command.ResolveMedia = this.ResolveMedia; - command.TableDefinitions = tableDefinitions; - command.TempFilesLocation = this.IntermediateFolder; - command.Execute(); + if (SectionType.Patch == section.Type && this.DeltaBinaryPatch) + { + var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Tuples.OfType().FirstOrDefault()); + command.Execute(); + } - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - } + // create cabinet files and process uncompressed files + var layoutDirectory = Path.GetDirectoryName(this.OutputPath); + if (!this.SuppressLayout || OutputType.Module == output.Type) + { + this.Messaging.Write(VerboseMessages.CreatingCabinetFiles()); + + var command = new CreateCabinetsCommand(this.ServiceProvider, this.BackendHelper); + command.CabbingThreadCount = this.CabbingThreadCount; + command.CabCachePath = this.CabCachePath; + command.DefaultCompressionLevel = this.DefaultCompressionLevel; + command.Output = output; + command.Messaging = this.Messaging; + command.BackendExtensions = this.BackendExtensions; + command.LayoutDirectory = layoutDirectory; + command.Compressed = compressed; + command.FileRowsByCabinet = filesByCabinetMedia; + command.ResolveMedia = this.ResolveMedia; + command.TableDefinitions = tableDefinitions; + command.TempFilesLocation = this.IntermediateFolder; + command.Execute(); -#if TODO_FINISH_PATCH - if (OutputType.Patch == this.Output.Type) - { - // copy output data back into the transforms - this.CopyToTransformData(this.Output); - } -#endif + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + } - this.ValidateComponentGuids(output); +#if DELETE + if (OutputType.Patch == output.Type) + { + // Copy output data back into the transforms. +#if TODO_PATCHING + var command = new CopyTransformDataCommand(this.Messaging, output, tableDefinitions, copyOutFileRows: false); + command.Execute(); - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return; - } + this.CopyToTransformData(this.Output); +#endif + } +#endif - // Generate database file. - this.Messaging.Write(VerboseMessages.GeneratingDatabase()); + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return; + } - { - var trackMsi = this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final); - trackedFiles.Add(trackMsi); + // Generate database file. + this.Messaging.Write(VerboseMessages.GeneratingDatabase()); - var temporaryFiles = this.GenerateDatabase(output, tableDefinitions, trackMsi.Path, false, false); - trackedFiles.AddRange(temporaryFiles); - } + { + var trackMsi = this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final); + trackedFiles.Add(trackMsi); - // Stop processing if an error previously occurred. - if (this.Messaging.EncounteredError) - { - return; - } + var temporaryFiles = this.GenerateDatabase(output, tableDefinitions, trackMsi.Path, false, false); + trackedFiles.AddRange(temporaryFiles); + } - // Merge modules. - if (containsMergeModules) - { - this.Messaging.Write(VerboseMessages.MergingModules()); + // Stop processing if an error previously occurred. + if (this.Messaging.EncounteredError) + { + return; + } - // Add back possibly suppressed sequence tables since all sequence tables must be present - // for the merge process to work. We'll drop the suppressed sequence tables again as - // necessary. - foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) + // Merge modules. + if (containsMergeModules) { - var sequenceTableName = sequence.ToString(); - var sequenceTable = output.Tables[sequenceTableName]; + this.Messaging.Write(VerboseMessages.MergingModules()); - if (null == sequenceTable) + // Add back possibly suppressed sequence tables since all sequence tables must be present + // for the merge process to work. We'll drop the suppressed sequence tables again as + // necessary. + foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) { - sequenceTable = output.EnsureTable(tableDefinitions[sequenceTableName]); - } + var sequenceTableName = sequence.ToString(); + var sequenceTable = output.Tables[sequenceTableName]; - if (0 == sequenceTable.Rows.Count) - { - suppressedTableNames.Add(sequenceTableName); + if (null == sequenceTable) + { + sequenceTable = output.EnsureTable(tableDefinitions[sequenceTableName]); + } + + if (0 == sequenceTable.Rows.Count) + { + suppressedTableNames.Add(sequenceTableName); + } } - } - var command = new MergeModulesCommand(); - command.FileFacades = fileFacades; - command.Output = output; - command.OutputPath = this.OutputPath; - command.SuppressedTableNames = suppressedTableNames; - command.Execute(); - } + var command = new MergeModulesCommand(); + command.FileFacades = fileFacades; + command.Output = output; + command.OutputPath = this.OutputPath; + command.SuppressedTableNames = suppressedTableNames; + command.Execute(); + } - if (this.Messaging.EncounteredError) - { - return; - } + if (this.Messaging.EncounteredError) + { + return; + } #if TODO_FINISH_VALIDATION // Validate the output if there is an MSI validator. @@ -519,27 +554,29 @@ namespace WixToolset.Core.WindowsInstaller.Bind } #endif - // Process uncompressed files. - if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) - { - var command = new ProcessUncompressedFilesCommand(section, this.BackendHelper, this.PathResolver); - command.Compressed = compressed; - command.FileFacades = uncompressedFiles; - command.LayoutDirectory = layoutDirectory; - command.LongNamesInImage = longNames; - command.ResolveMedia = this.ResolveMedia; - command.DatabasePath = this.OutputPath; - command.Execute(); + // Process uncompressed files. + if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) + { + var command = new ProcessUncompressedFilesCommand(section, this.BackendHelper, this.PathResolver); + command.Compressed = compressed; + command.FileFacades = uncompressedFiles; + command.LayoutDirectory = layoutDirectory; + command.LongNamesInImage = longNames; + command.ResolveMedia = this.ResolveMedia; + command.DatabasePath = this.OutputPath; + command.Execute(); - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + } + + // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables). + trackedFiles.AddRange(fileFacades.Select(f => this.BackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber))); } this.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, output); this.FileTransfers = fileTransfers; - // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables). - trackedFiles.AddRange(fileFacades.Select(f => this.BackendHelper.TrackFile(f.File.Source.Path, TrackedFileType.Input, f.File.SourceLineNumbers))); this.TrackedFiles = trackedFiles; } @@ -566,7 +603,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind return wixout; } -#if TODO_FINISH_PATCH +#if TODO_PATCHING /// /// Copy file data between transform substorages and the patch output object /// @@ -936,28 +973,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// Whether to use a subdirectory based on the file name for intermediate files. private IEnumerable GenerateDatabase(WindowsInstallerData output, TableDefinitionCollection tableDefinitions, string databaseFile, bool keepAddedColumns, bool useSubdirectory) { - var command = new GenerateDatabaseCommand(); - command.BackendHelper = this.BackendHelper; - command.Extensions = this.FileSystemExtensions; - command.Output = output; - command.OutputPath = databaseFile; - command.KeepAddedColumns = keepAddedColumns; - command.UseSubDirectory = useSubdirectory; - command.SuppressAddingValidationRows = this.SuppressAddingValidationRows; - command.TableDefinitions = tableDefinitions; - command.IntermediateFolder = this.IntermediateFolder; - command.Codepage = this.Codepage; + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemExtensions, output, databaseFile, tableDefinitions, this.IntermediateFolder, this.Codepage, keepAddedColumns, this.SuppressAddingValidationRows, useSubdirectory); command.Execute(); return command.GeneratedTemporaryFiles; } - #region IDisposable Support +#region IDisposable Support - public void Dispose() - { - this.Dispose(true); - } + public void Dispose() => this.Dispose(true); protected virtual void Dispose(bool disposing) { @@ -972,6 +996,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - #endregion +#endregion } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs index 8757024e..ea6e4f31 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs @@ -15,24 +15,37 @@ namespace WixToolset.Core.WindowsInstaller.Bind internal class BindTransformCommand { - public IEnumerable Extensions { private get; set; } + public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable extensions, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Extensions = extensions; + this.IntermediateFolder = intermediateFolder; + this.Transform = transform; + this.OutputPath = outputPath; + this.TableDefinitions = tableDefinitions; + } + + private IMessaging Messaging { get; } - public TableDefinitionCollection TableDefinitions { private get; set; } + private IBackendHelper BackendHelper { get; } - public string TempFilesLocation { private get; set; } + private IEnumerable Extensions { get; } - public WindowsInstallerData Transform { private get; set; } + private TableDefinitionCollection TableDefinitions { get; } - public IMessaging Messaging { private get; set; } + private string IntermediateFolder { get; } - public string OutputPath { private get; set; } + private WindowsInstallerData Transform { get; } + + private string OutputPath { get; } public void Execute() { - int transformFlags = 0; + var transformFlags = 0; - WindowsInstallerData targetOutput = new WindowsInstallerData(null); - WindowsInstallerData updatedOutput = new WindowsInstallerData(null); + var targetOutput = new WindowsInstallerData(null); + var updatedOutput = new WindowsInstallerData(null); // TODO: handle added columns @@ -49,8 +62,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind string targetUpgradeCode = null; string updatedUpgradeCode = null; - Table propertyTable = this.Transform.Tables["Property"]; - if (null != propertyTable) + if (this.Transform.TryGetTable("Property", out var propertyTable)) { for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) { @@ -68,18 +80,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); - Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); + var targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + var updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + var targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); + var updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); // process special summary information values - foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows) + foreach (var row in this.Transform.Tables["_SummaryInformation"].Rows) { - if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) + var summaryId = row.FieldAsInteger(0); + var summaryData = row.FieldAsString(1); + + if ((int)SummaryInformation.Transform.CodePage == summaryId) { // convert from a web name if provided - string codePage = (string)row.Fields[1].Data; + var codePage = summaryData; if (null == codePage) { codePage = "0"; @@ -89,7 +104,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); } - string previousCodePage = (string)row.Fields[1].PreviousData; + var previousCodePage = row.Fields[1].PreviousData; if (null == previousCodePage) { previousCodePage = "0"; @@ -99,50 +114,50 @@ namespace WixToolset.Core.WindowsInstaller.Bind previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); } - Row targetCodePageRow = targetSummaryInfo.CreateRow(null); + var targetCodePageRow = targetSummaryInfo.CreateRow(null); targetCodePageRow[0] = 1; // PID_CODEPAGE targetCodePageRow[1] = previousCodePage; - Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null); + var updatedCodePageRow = updatedSummaryInfo.CreateRow(null); updatedCodePageRow[0] = 1; // PID_CODEPAGE updatedCodePageRow[1] = codePage; } - else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] || - (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId || + (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == summaryId) { // the target language - string[] propertyData = ((string)row[1]).Split(';'); - string lang = 2 == propertyData.Length ? propertyData[1] : "0"; + var propertyData = summaryData.Split(';'); + var lang = 2 == propertyData.Length ? propertyData[1] : "0"; - Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo; - Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable; + var tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetSummaryInfo : updatedSummaryInfo; + var tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetPropertyTable : updatedPropertyTable; - Row productLanguageRow = tempPropertyTable.CreateRow(null); + var productLanguageRow = tempPropertyTable.CreateRow(null); productLanguageRow[0] = "ProductLanguage"; productLanguageRow[1] = lang; // set the platform;language on the MSI to be generated - Row templateRow = tempSummaryInfo.CreateRow(null); + var templateRow = tempSummaryInfo.CreateRow(null); templateRow[0] = 7; // PID_TEMPLATE - templateRow[1] = (string)row[1]; + templateRow[1] = summaryData; } - else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) + else if ((int)SummaryInformation.Transform.ProductCodes == summaryId) { - string[] propertyData = ((string)row[1]).Split(';'); + var propertyData = summaryData.Split(';'); - Row targetProductCodeRow = targetPropertyTable.CreateRow(null); + var targetProductCodeRow = targetPropertyTable.CreateRow(null); targetProductCodeRow[0] = "ProductCode"; targetProductCodeRow[1] = propertyData[0].Substring(0, 38); - Row targetProductVersionRow = targetPropertyTable.CreateRow(null); + var targetProductVersionRow = targetPropertyTable.CreateRow(null); targetProductVersionRow[0] = "ProductVersion"; targetProductVersionRow[1] = propertyData[0].Substring(38); - Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null); + var updatedProductCodeRow = updatedPropertyTable.CreateRow(null); updatedProductCodeRow[0] = "ProductCode"; updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); - Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null); + var updatedProductVersionRow = updatedPropertyTable.CreateRow(null); updatedProductVersionRow[0] = "ProductVersion"; updatedProductVersionRow[1] = propertyData[1].Substring(38); @@ -153,7 +168,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind targetUpgradeCode = propertyData[2]; if (!String.IsNullOrEmpty(targetUpgradeCode)) { - Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); + var targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); targetUpgradeCodeRow[0] = "UpgradeCode"; targetUpgradeCodeRow[1] = targetUpgradeCode; @@ -167,16 +182,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (!String.IsNullOrEmpty(updatedUpgradeCode)) { - Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); + var updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); updatedUpgradeCodeRow[0] = "UpgradeCode"; updatedUpgradeCodeRow[1] = updatedUpgradeCode; } } - else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) + else if ((int)SummaryInformation.Transform.ValidationFlags == summaryId) { - transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); + transformFlags = Convert.ToInt32(summaryData, CultureInfo.InvariantCulture); } - else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0]) + else if ((int)SummaryInformation.Transform.Reserved11 == summaryId) { // PID_LASTPRINTED should be null for transforms row.Operation = RowOperation.None; @@ -184,11 +199,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind else { // add everything else as is - Row targetRow = targetSummaryInfo.CreateRow(null); + var targetRow = targetSummaryInfo.CreateRow(null); targetRow[0] = row[0]; targetRow[1] = row[1]; - Row updatedRow = updatedSummaryInfo.CreateRow(null); + var updatedRow = updatedSummaryInfo.CreateRow(null); updatedRow[0] = row[0]; updatedRow[1] = row[1]; } @@ -205,7 +220,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind string emptyFile = null; - foreach (Table table in this.Transform.Tables) + foreach (var table in this.Transform.Tables) { // Ignore unreal tables when building transforms except the _Stream table. // These tables are ignored when generating the database so there is no reason @@ -231,20 +246,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind } // process row operations - foreach (Row row in table.Rows) + foreach (var row in table.Rows) { switch (row.Operation) { case RowOperation.Add: - Table updatedTable = updatedOutput.EnsureTable(table.Definition); + var updatedTable = updatedOutput.EnsureTable(table.Definition); updatedTable.Rows.Add(row); continue; + case RowOperation.Delete: - Table targetTable = targetOutput.EnsureTable(table.Definition); + var targetTable = targetOutput.EnsureTable(table.Definition); targetTable.Rows.Add(row); // fill-in non-primary key values - foreach (Field field in row.Fields) + foreach (var field in row.Fields) { if (!field.Column.PrimaryKey) { @@ -256,7 +272,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { if (null == emptyFile) { - emptyFile = Path.Combine(this.TempFilesLocation, "empty"); + emptyFile = Path.Combine(this.IntermediateFolder, "empty"); } field.Data = emptyFile; @@ -273,7 +289,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Assure that the file table's sequence is populated if ("File" == table.Name) { - foreach (Row fileRow in table.Rows) + foreach (var fileRow in table.Rows) { if (null == fileRow[7]) { @@ -290,60 +306,48 @@ namespace WixToolset.Core.WindowsInstaller.Bind } // process modified and unmodified rows - bool modifiedRow = false; - Row targetRow = new Row(null, table.Definition); - Row updatedRow = row; - for (int i = 0; i < row.Fields.Length; i++) + var modifiedRow = false; + var targetRow = new Row(null, table.Definition); + var updatedRow = row; + for (var i = 0; i < row.Fields.Length; i++) { - Field updatedField = row.Fields[i]; + var updatedField = row.Fields[i]; if (updatedField.Modified) { // set a different value in the target row to ensure this value will be modified during transform generation if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) { - if (null == updatedField.Data || 1 != (int)updatedField.Data) - { - targetRow[i] = 1; - } - else - { - targetRow[i] = 2; - } + var data = updatedField.AsNullableInteger(); + targetRow[i] = (data == 1) ? 2 : 1; } else if (ColumnType.Object == updatedField.Column.Type) { if (null == emptyFile) { - emptyFile = Path.Combine(this.TempFilesLocation, "empty"); + emptyFile = Path.Combine(this.IntermediateFolder, "empty"); } targetRow[i] = emptyFile; } else { - if ("0" != (string)updatedField.Data) - { - targetRow[i] = "0"; - } - else - { - targetRow[i] = "1"; - } + var data = updatedField.AsString(); + targetRow[i] = (data == "0") ? "1" : "0"; } modifiedRow = true; } else if (ColumnType.Object == updatedField.Column.Type) { - ObjectField objectField = (ObjectField)updatedField; + var objectField = (ObjectField)updatedField; // create an empty file for comparing against if (null == objectField.PreviousData) { if (null == emptyFile) { - emptyFile = Path.Combine(this.TempFilesLocation, "empty"); + emptyFile = Path.Combine(this.IntermediateFolder, "empty"); } targetRow[i] = emptyFile; @@ -372,10 +376,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]))) { - Table targetTable = targetOutput.EnsureTable(table.Definition); + var targetTable = targetOutput.EnsureTable(table.Definition); targetTable.Rows.Add(targetRow); - Table updatedTable = updatedOutput.EnsureTable(table.Definition); + var updatedTable = updatedOutput.EnsureTable(table.Definition); updatedTable.Rows.Add(updatedRow); } } @@ -392,38 +396,36 @@ namespace WixToolset.Core.WindowsInstaller.Bind return; } - string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); - string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi")); - string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi")); + var transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); + var targetDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_target.msi")); + var updatedDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_updated.msi")); try { if (!String.IsNullOrEmpty(emptyFile)) { - using (FileStream fileStream = File.Create(emptyFile)) + using (var fileStream = File.Create(emptyFile)) { } } - this.GenerateDatabase(targetOutput, targetDatabaseFile, false); - this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true); + this.GenerateDatabase(targetOutput, targetDatabaseFile, keepAddedColumns: false); + this.GenerateDatabase(updatedOutput, updatedDatabaseFile, keepAddedColumns: true); // make sure the directory exists Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); // create the transform file - using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) + using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) + using (var updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) { - using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) + if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) { - if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) - { - updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); - } - else - { - this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); - } + updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); + } + else + { + this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); } } } @@ -458,16 +460,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns) { - var command = new GenerateDatabaseCommand(); - command.Codepage = output.Codepage; - command.Extensions = this.Extensions; - command.KeepAddedColumns = keepAddedColumns; - command.Output = output; - command.OutputPath = outputPath; - command.TableDefinitions = this.TableDefinitions; - command.IntermediateFolder = this.TempFilesLocation; - command.SuppressAddingValidationRows = true; - command.UseSubDirectory = true; + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.Extensions, output, outputPath, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true ); command.Execute(); } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs index bf1140d8..ec4e0818 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs @@ -152,7 +152,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row { - if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes) + if ((ulong)facade.FileSize >= maxPreCompressedSizeInBytes) { // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting maxCabinetSize = this.MaximumCabinetSizeForLargeFileSplitting; @@ -166,8 +166,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind var files = cabinetWorkItem.FileFacades .Select(facade => facade.Hash == null ? - new CabinetCompressFile(facade.File.Source.Path, facade.File.Id.Id) : - new CabinetCompressFile(facade.File.Source.Path, facade.File.Id.Id, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4)) + new CabinetCompressFile(facade.SourcePath, facade.Id) : + new CabinetCompressFile(facade.SourcePath, facade.Id, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4)) .ToList(); var cab = new Cabinet(cabinetPath); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs index 3fa3f3a0..79b1c619 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs @@ -112,8 +112,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IBindFileWithPath CreateBindFileWithPath(FileFacade facade) { var result = this.ServiceProvider.GetService(); - result.Id = facade.File.Id.Id; - result.Path = facade.File.Source.Path; + result.Id = facade.Id; + result.Path = facade.SourcePath; return result; } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs index 107f3208..0dcce61b 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs @@ -1,12 +1,15 @@ // 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. +#if DELETE + namespace WixToolset.Core.WindowsInstaller.Bind { using System; using System.Collections.Generic; using System.Diagnostics; + using System.IO; + using System.Linq; using WixToolset.Core.Bind; - using WixToolset.Core.Native; using WixToolset.Data; using WixToolset.Data.Tuples; using WixToolset.Data.WindowsInstaller; @@ -16,15 +19,23 @@ namespace WixToolset.Core.WindowsInstaller.Bind internal class CopyTransformDataCommand { - public bool CopyOutFileRows { private get; set; } + public CopyTransformDataCommand(IMessaging messaging, WindowsInstallerData output, TableDefinitionCollection tableDefinitions, bool copyOutFileRows) + { + this.Messaging = messaging; + this.Output = output; + this.TableDefinitions = tableDefinitions; + this.CopyOutFileRows = copyOutFileRows; + } - public IEnumerable Extensions { private get; set; } + private bool CopyOutFileRows { get; } - public IMessaging Messaging { private get; set; } + public IEnumerable Extensions { get; } - public WindowsInstallerData Output { private get; set; } + private IMessaging Messaging { get; } - public TableDefinitionCollection TableDefinitions { private get; set; } + private WindowsInstallerData Output { get; } + + private TableDefinitionCollection TableDefinitions { get; } public IEnumerable FileFacades { get; private set; } @@ -32,18 +43,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind { Debug.Assert(OutputType.Patch != this.Output.Type); - List allFileRows = this.CopyOutFileRows ? new List() : null; + var allFileRows = this.CopyOutFileRows ? new List() : null; -#if REVISIT_FOR_PATCHING // TODO: Fix this patching related code to work correctly with FileFacades. - bool copyToPatch = (allFileRows != null); - bool copyFromPatch = !copyToPatch; + var copyToPatch = (allFileRows != null); + var copyFromPatch = !copyToPatch; - RowDictionary patchMediaRows = new RowDictionary(); + var patchMediaRows = new RowDictionary(); - Dictionary> patchMediaFileRows = new Dictionary>(); + var patchMediaFileRows = new Dictionary>(); - Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); - Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]); + var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); + var patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]); if (copyFromPatch) { @@ -51,8 +61,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind foreach (WixFileRow patchFileRow in patchFileTable.Rows) { int diskId = patchFileRow.DiskId; - RowDictionary mediaFileRows; - if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) { mediaFileRows = new RowDictionary(); patchMediaFileRows.Add(diskId, mediaFileRows); @@ -61,24 +70,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind mediaFileRows.Add(patchFileRow); } - Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]); + var patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]); patchMediaRows = new RowDictionary(patchMediaTable); } - // index paired transforms - Dictionary pairedTransforms = new Dictionary(); - foreach (SubStorage substorage in this.Output.SubStorages) - { - if (substorage.Name.StartsWith("#")) - { - pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data); - } - } + // Index paired transforms by name without the "#" prefix. + var pairedTransforms = this.Output.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name.Substring(1), s => s.Data); + //Dictionary pairedTransforms = new Dictionary(); + //foreach (SubStorage substorage in this.Output.SubStorages) + //{ + // if (substorage.Name.StartsWith("#")) + // { + // pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data); + // } + //} try { - // copy File bind data into substorages - foreach (SubStorage substorage in this.Output.SubStorages) + // Copy File bind data into substorages + foreach (var substorage in this.Output.SubStorages) { if (substorage.Name.StartsWith("#")) { @@ -86,25 +96,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind continue; } - Output mainTransform = substorage.Data; - Table mainWixFileTable = mainTransform.Tables["WixFile"]; - Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"]; + var mainTransform = substorage.Data; + var mainWixFileTable = mainTransform.Tables["WixFile"]; + var mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"]; this.FileManagerCore.ActiveSubStorage = substorage; - RowDictionary mainWixFiles = new RowDictionary(mainWixFileTable); - RowDictionary mainMsiFileHashIndex = new RowDictionary(); + var mainWixFiles = new RowDictionary(mainWixFileTable); + var mainMsiFileHashIndex = new RowDictionary(); - Table mainFileTable = mainTransform.Tables["File"]; - Output pairedTransform = (Output)pairedTransforms[substorage.Name]; + var mainFileTable = mainTransform.Tables["File"]; + var pairedTransform = pairedTransforms[substorage.Name]; // copy Media.LastSequence and index the MsiFileHash table if it exists. if (copyFromPatch) { - Table pairedMediaTable = pairedTransform.Tables["Media"]; + var pairedMediaTable = pairedTransform.Tables["Media"]; foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) { - MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); + var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); pairedMediaRow.Fields[1] = patchMediaRow.Fields[1]; } @@ -118,8 +128,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind } // Index File table of pairedTransform - Table pairedFileTable = pairedTransform.Tables["File"]; - RowDictionary pairedFileRows = new RowDictionary(pairedFileTable); + var pairedFileTable = pairedTransform.Tables["File"]; + var pairedFileRows = new RowDictionary(pairedFileTable); if (null != mainFileTable) { @@ -140,12 +150,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind continue; } - WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File); + var mainWixFileRow = mainWixFiles.Get(mainFileRow.File); if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes. { - ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6]; - FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File); + var objectField = (ObjectField)mainWixFileRow.Fields[6]; + var pairedFileRow = pairedFileRows.Get(mainFileRow.File); // If the file is new, we always need to add it to the patch. if (mainFileRow.Operation != RowOperation.Add) @@ -169,8 +179,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (null != pairedFileRow) { // Always patch-added, but never non-compressed. - pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; - pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; pairedFileRow.Fields[6].Modified = true; pairedFileRow.Operation = RowOperation.Modify; } @@ -179,14 +189,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind { // The File is same. We need mark all the attributes as unchanged. mainFileRow.Operation = RowOperation.None; - foreach (Field field in mainFileRow.Fields) + foreach (var field in mainFileRow.Fields) { field.Modified = false; } if (null != pairedFileRow) { - pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded; pairedFileRow.Fields[6].Modified = false; pairedFileRow.Operation = RowOperation.None; } @@ -197,8 +207,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind else if (null != pairedFileRow) // RowOperation.Add { // Always patch-added, but never non-compressed. - pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; - pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; pairedFileRow.Fields[6].Modified = true; pairedFileRow.Operation = RowOperation.Add; } @@ -207,20 +217,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind // index patch files by diskId+fileId int diskId = mainWixFileRow.DiskId; - RowDictionary mediaFileRows; - if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows)) + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) { mediaFileRows = new RowDictionary(); patchMediaFileRows.Add(diskId, mediaFileRows); } - string fileId = mainFileRow.File; - WixFileRow patchFileRow = mediaFileRows.Get(fileId); + var fileId = mainFileRow.File; + var patchFileRow = mediaFileRows.Get(fileId); if (copyToPatch) { if (null == patchFileRow) { - FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); + var patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); patchActualFileRow.CopyFrom(mainFileRow); patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); @@ -237,7 +246,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // make sure Source is same. Otherwise we are silently ignoring a file. if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase)) { - Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source)); + this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source)); } // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. @@ -249,11 +258,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind // copy data from the patch back to the transform if (null != patchFileRow) { - FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId); - for (int i = 0; i < patchFileRow.Fields.Length; i++) + var pairedFileRow = pairedFileRows.Get(fileId); + for (var i = 0; i < patchFileRow.Fields.Length; i++) { - string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString(); - string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString(); + var patchValue = patchFileRow[i] == null ? String.Empty : patchFileRow.FieldAsString(i); + var mainValue = mainFileRow[i] == null ? String.Empty : mainFileRow.FieldAsString(i); if (1 == i) { @@ -298,17 +307,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind } // copy MsiFileHash row for this File - Row patchHashRow; - if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow)) + if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) { patchHashRow = patchFileRow.Hash; } if (null != patchHashRow) { - Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); - Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); - for (int i = 0; i < patchHashRow.Fields.Length; i++) + var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); + var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); + for (var i = 0; i < patchHashRow.Fields.Length; i++) { mainHashRow[i] = patchHashRow[i]; if (i > 1) @@ -326,12 +334,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind List patchAssemblyNameRows = patchFileRow.AssemblyNames; if (null != patchAssemblyNameRows) { - Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); - foreach (Row patchAssemblyNameRow in patchAssemblyNameRows) + var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); + foreach (var patchAssemblyNameRow in patchAssemblyNameRows) { // Copy if there isn't an identical modified/added row already in the transform. - bool foundMatchingModifiedRow = false; - foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows) + var foundMatchingModifiedRow = false; + foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows) { if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) { @@ -342,8 +350,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (!foundMatchingModifiedRow) { - Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); - for (int i = 0; i < patchAssemblyNameRow.Fields.Length; i++) + var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); + for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++) { mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; } @@ -359,34 +367,36 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (null != patchFileRow.Patch) { // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. - AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); - AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); + this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); + this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); // Add to Patch table - Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); + var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); if (0 == patchTable.Rows.Count) { patchTable.Operation = TableOperation.Add; } - Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); + var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); patchRow[0] = patchFileRow.File; patchRow[1] = patchFileRow.Sequence; - FileInfo patchFile = new FileInfo(patchFileRow.Source); + var patchFile = new FileInfo(patchFileRow.Source); patchRow[2] = (int)patchFile.Length; patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; - string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; - if (MsiInterop.MsiMaxStreamNameLength < streamName.Length) + var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; + if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) { streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); - Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); + + var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); if (0 == patchHeadersTable.Rows.Count) { patchHeadersTable.Operation = TableOperation.Add; } - Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); + + var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); patchHeadersRow[0] = streamName; patchHeadersRow[1] = patchFileRow.Patch; patchRow[5] = streamName; @@ -420,7 +430,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { this.FileManagerCore.ActiveSubStorage = null; } -#endif + this.FileFacades = allFileRows; } @@ -509,17 +519,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind foreach (var row in sequenceTable.Rows) { var actionName = row.FieldAsString(0); - if (String.Equals("PatchFiles", actionName, StringComparison.Ordinal)) - { - hasPatchFilesAction = true; - } - else if (String.Equals("InstallFiles", actionName, StringComparison.Ordinal)) + switch (actionName) { - installFilesSequence = row.FieldAsInteger(2); - } - else if (String.Equals("DuplicateFiles", actionName, StringComparison.Ordinal)) - { - duplicateFilesSequence = row.FieldAsInteger(2); + case "PatchFiles": + hasPatchFilesAction = true; + break; + + case "InstallFiles": + installFilesSequence = row.FieldAsInteger(2); + break; + + case "DuplicateFiles": + duplicateFilesSequence = row.FieldAsInteger(2); + break; } } } @@ -531,8 +543,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// The output to validate. private void ValidateFileRowChanges(WindowsInstallerData transform) { - Table componentTable = transform.Tables["Component"]; - Table fileTable = transform.Tables["File"]; + var componentTable = transform.Tables["Component"]; + var fileTable = transform.Tables["File"]; // There's no sense validating keypaths if the transform has no component or file table if (componentTable == null || fileTable == null) @@ -540,31 +552,31 @@ namespace WixToolset.Core.WindowsInstaller.Bind return; } - Dictionary componentKeyPath = new Dictionary(componentTable.Rows.Count); + var componentKeyPath = new Dictionary(componentTable.Rows.Count); // Index the Component table for non-directory & non-registry key paths. - foreach (Row row in componentTable.Rows) + foreach (var row in componentTable.Rows) { - if (null != row.Fields[5].Data && - 0 != ((int)row.Fields[3].Data & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) + var keyPath = row.FieldAsString(5); + if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) { - componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString()); + componentKeyPath.Add(row.FieldAsString(0), keyPath); } } - Dictionary componentWithChangedKeyPath = new Dictionary(); - Dictionary componentWithNonKeyPathChanged = new Dictionary(); + var componentWithChangedKeyPath = new Dictionary(); + var componentWithNonKeyPathChanged = new Dictionary(); // Verify changes in the file table, now that file diffing has occurred foreach (FileRow row in fileTable.Rows) { - string fileId = row.Fields[0].Data.ToString(); - string componentId = row.Fields[1].Data.ToString(); - if (RowOperation.Modify != row.Operation) { continue; } + var fileId = row.FieldAsString(0); + var componentId = row.FieldAsString(1); + // If this file is the keypath of a component if (componentKeyPath.ContainsValue(fileId)) { @@ -582,12 +594,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - foreach (KeyValuePair componentFile in componentWithNonKeyPathChanged) + foreach (var componentFile in componentWithNonKeyPathChanged) { // Make sure all changes to non keypath files also had a change in the keypath. - if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key)) + if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath)) { - this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key])); + this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath)); } } } @@ -614,3 +626,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } } + +#endif diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs index 19f7b9e5..f5ac00e6 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs @@ -33,7 +33,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false; var apiPatchingSymbolFlags = (PatchSymbolFlagsType)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0); -#if REVISIT_FOR_PATCHING +#if TODO_PATCHING foreach (FileFacade facade in this.FileFacades) { if (RowOperation.Modify == facade.File.Operation && diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs index 6b1dead5..f09a2e47 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs @@ -1,4 +1,4 @@ -// 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. +// 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.Bind { @@ -122,13 +122,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind tableString.Append(definition.Name); foreach (var column in definition.Columns) { - // conditionally keep columns added in a transform; otherwise, - // break because columns can only be added at the end + // Conditionally keep columns added in a transform; otherwise, + // break because columns can only be added at the end. if (column.Added && !keepAddedColumns) { break; } + if (column.Unreal) + { + continue; + } + if (!first) { columnString.Append('\t'); @@ -168,6 +173,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind break; } + if (field.Column.Unreal) + { + continue; + } + if (first) { first = false; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 31d0b3a6..5707f7ce 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -40,193 +40,193 @@ namespace WixToolset.Core.WindowsInstaller.Bind public void Execute() { - var output = new WindowsInstallerData(this.Section.Tuples.First().SourceLineNumbers); - output.Codepage = this.Section.Codepage; - output.Type = SectionTypeToOutputType(this.Section.Type); - - this.AddSectionToOutput(this.Section, output); + this.Output = new WindowsInstallerData(this.Section.Tuples.First().SourceLineNumbers) + { + Codepage = this.Section.Codepage, + Type = SectionTypeToOutputType(this.Section.Type) + }; - this.Output = output; + this.AddSectionToOutput(); } - private void AddSectionToOutput(IntermediateSection section, WindowsInstallerData output) + private void AddSectionToOutput() { - foreach (var tuple in section.Tuples) + foreach (var tuple in this.Section.Tuples) { switch (tuple.Definition.Type) { case TupleDefinitionType.AppSearch: - this.AddTupleDefaultly(tuple, output); - output.EnsureTable(this.TableDefinitions["Signature"]); + this.AddTupleDefaultly(tuple); + this.Output.EnsureTable(this.TableDefinitions["Signature"]); break; case TupleDefinitionType.Assembly: - this.AddAssemblyTuple((AssemblyTuple)tuple, output); + this.AddAssemblyTuple((AssemblyTuple)tuple); break; case TupleDefinitionType.Binary: - this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true); + this.AddTupleDefaultly(tuple, idIsPrimaryKey: true); break; case TupleDefinitionType.BBControl: - this.AddBBControlTuple((BBControlTuple)tuple, output); + this.AddBBControlTuple((BBControlTuple)tuple); break; case TupleDefinitionType.Class: - this.AddClassTuple((ClassTuple)tuple, output); + this.AddClassTuple((ClassTuple)tuple); break; case TupleDefinitionType.Control: - this.AddControlTuple((ControlTuple)tuple, output); + this.AddControlTuple((ControlTuple)tuple); break; case TupleDefinitionType.Component: - this.AddComponentTuple((ComponentTuple)tuple, output); + this.AddComponentTuple((ComponentTuple)tuple); break; case TupleDefinitionType.CustomAction: - this.AddCustomActionTuple((CustomActionTuple)tuple, output); + this.AddCustomActionTuple((CustomActionTuple)tuple); break; case TupleDefinitionType.Dialog: - this.AddDialogTuple((DialogTuple)tuple, output); + this.AddDialogTuple((DialogTuple)tuple); break; case TupleDefinitionType.Directory: - this.AddDirectoryTuple((DirectoryTuple)tuple, output); + this.AddDirectoryTuple((DirectoryTuple)tuple); break; case TupleDefinitionType.Environment: - this.AddEnvironmentTuple((EnvironmentTuple)tuple, output); + this.AddEnvironmentTuple((EnvironmentTuple)tuple); break; case TupleDefinitionType.Error: - this.AddErrorTuple((ErrorTuple)tuple, output); + this.AddErrorTuple((ErrorTuple)tuple); break; case TupleDefinitionType.Feature: - this.AddFeatureTuple((FeatureTuple)tuple, output); + this.AddFeatureTuple((FeatureTuple)tuple); break; case TupleDefinitionType.File: - this.AddFileTuple((FileTuple)tuple, output); + this.AddFileTuple((FileTuple)tuple); break; case TupleDefinitionType.Icon: - this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true); + this.AddTupleDefaultly(tuple, idIsPrimaryKey: true); break; case TupleDefinitionType.IniFile: - this.AddIniFileTuple((IniFileTuple)tuple, output); + this.AddIniFileTuple((IniFileTuple)tuple); break; case TupleDefinitionType.Media: - this.AddMediaTuple((MediaTuple)tuple, output); + this.AddMediaTuple((MediaTuple)tuple); break; case TupleDefinitionType.ModuleConfiguration: - this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple, output); + this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple); break; case TupleDefinitionType.MsiEmbeddedUI: - this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple, output); + this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple); break; case TupleDefinitionType.MsiFileHash: - this.AddMsiFileHashTuple((MsiFileHashTuple)tuple, output); + this.AddMsiFileHashTuple((MsiFileHashTuple)tuple); break; case TupleDefinitionType.MsiServiceConfig: - this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple, output); + this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple); break; case TupleDefinitionType.MsiServiceConfigFailureActions: - this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple, output); + this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple); break; case TupleDefinitionType.MsiShortcutProperty: - this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true); + this.AddTupleDefaultly(tuple, idIsPrimaryKey: true); break; case TupleDefinitionType.MoveFile: - this.AddMoveFileTuple((MoveFileTuple)tuple, output); + this.AddMoveFileTuple((MoveFileTuple)tuple); break; case TupleDefinitionType.ProgId: - this.AddTupleDefaultly(tuple, output); - output.EnsureTable(this.TableDefinitions["Extension"]); + this.AddTupleDefaultly(tuple); + this.Output.EnsureTable(this.TableDefinitions["Extension"]); break; case TupleDefinitionType.Property: - this.AddPropertyTuple((PropertyTuple)tuple, output); + this.AddPropertyTuple((PropertyTuple)tuple); break; case TupleDefinitionType.RemoveFile: - this.AddRemoveFileTuple((RemoveFileTuple)tuple, output); + this.AddRemoveFileTuple((RemoveFileTuple)tuple); break; case TupleDefinitionType.Registry: - this.AddRegistryTuple((RegistryTuple)tuple, output); + this.AddRegistryTuple((RegistryTuple)tuple); break; case TupleDefinitionType.RegLocator: - this.AddRegLocatorTuple((RegLocatorTuple)tuple, output); + this.AddRegLocatorTuple((RegLocatorTuple)tuple); break; case TupleDefinitionType.RemoveRegistry: - this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple, output); + this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple); break; case TupleDefinitionType.ReserveCost: - this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true); + this.AddTupleDefaultly(tuple, idIsPrimaryKey: true); break; case TupleDefinitionType.ServiceControl: - this.AddServiceControlTuple((ServiceControlTuple)tuple, output); + this.AddServiceControlTuple((ServiceControlTuple)tuple); break; case TupleDefinitionType.ServiceInstall: - this.AddServiceInstallTuple((ServiceInstallTuple)tuple, output); + this.AddServiceInstallTuple((ServiceInstallTuple)tuple); break; case TupleDefinitionType.Shortcut: - this.AddShortcutTuple((ShortcutTuple)tuple, output); + this.AddShortcutTuple((ShortcutTuple)tuple); break; case TupleDefinitionType.Signature: - this.AddTupleDefaultly(tuple, output, idIsPrimaryKey: true); + this.AddTupleDefaultly(tuple, idIsPrimaryKey: true); break; case TupleDefinitionType.SummaryInformation: - this.AddTupleDefaultly(tuple, output, tableName: "_SummaryInformation"); + this.AddTupleDefaultly(tuple, tableName: "_SummaryInformation"); break; case TupleDefinitionType.TextStyle: - this.AddTextStyleTuple((TextStyleTuple)tuple, output); + this.AddTextStyleTuple((TextStyleTuple)tuple); break; case TupleDefinitionType.Upgrade: - this.AddUpgradeTuple((UpgradeTuple)tuple, output); + this.AddUpgradeTuple((UpgradeTuple)tuple); break; case TupleDefinitionType.WixAction: - this.AddWixActionTuple((WixActionTuple)tuple, output); + this.AddWixActionTuple((WixActionTuple)tuple); break; case TupleDefinitionType.WixMediaTemplate: - this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple, output); + this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple); break; case TupleDefinitionType.MustBeFromAnExtension: - this.AddTupleFromExtension(tuple, output); + this.AddTupleFromExtension(tuple); break; case TupleDefinitionType.WixCustomRow: - this.AddWixCustomRowTuple((WixCustomRowTuple)tuple, output); + this.AddWixCustomRowTuple((WixCustomRowTuple)tuple); break; case TupleDefinitionType.WixEnsureTable: - this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple, output); + this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple); break; // ignored. @@ -234,25 +234,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind case TupleDefinitionType.WixComponentGroup: case TupleDefinitionType.WixDeltaPatchFile: case TupleDefinitionType.WixFeatureGroup: - break; + case TupleDefinitionType.WixPatchBaseline: + break; // Already processed. case TupleDefinitionType.WixCustomTable: break; default: - this.AddTupleDefaultly(tuple, output); + this.AddTupleDefaultly(tuple); break; } } } - private void AddAssemblyTuple(AssemblyTuple tuple, WindowsInstallerData output) + private void AddAssemblyTuple(AssemblyTuple tuple) { var attributes = tuple.Type == AssemblyType.Win32Assembly ? 1 : (int?)null; - var table = output.EnsureTable(this.TableDefinitions["MsiAssembly"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "MsiAssembly"); row[0] = tuple.ComponentRef; row[1] = tuple.FeatureRef; row[2] = tuple.ManifestFileRef; @@ -260,7 +260,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[4] = attributes; } - private void AddBBControlTuple(BBControlTuple tuple, WindowsInstallerData output) + private void AddBBControlTuple(BBControlTuple tuple) { var attributes = tuple.Attributes; attributes |= tuple.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; @@ -272,8 +272,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind attributes |= tuple.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; attributes |= tuple.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; - var table = output.EnsureTable(this.TableDefinitions["BBControl"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "BBControl"); row[0] = tuple.BillboardRef; row[1] = tuple.BBControl; row[2] = tuple.Type; @@ -285,10 +284,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[8] = tuple.Text; } - private void AddClassTuple(ClassTuple tuple, WindowsInstallerData output) + private void AddClassTuple(ClassTuple tuple) { - var table = output.EnsureTable(this.TableDefinitions["Class"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Class"); row[0] = tuple.CLSID; row[1] = tuple.Context; row[2] = tuple.ComponentRef; @@ -304,7 +302,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[12] = tuple.RelativePath ? (int?)1 : null; } - private void AddControlTuple(ControlTuple tuple, WindowsInstallerData output) + private void AddControlTuple(ControlTuple tuple) { var text = tuple.Text; var attributes = tuple.Attributes; @@ -329,8 +327,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind text = String.Concat(text, " "); } - var table = output.EnsureTable(this.TableDefinitions["Control"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Control"); row[0] = tuple.DialogRef; row[1] = tuple.Control; row[2] = tuple.Type; @@ -344,7 +341,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[10] = tuple.Help; } - private void AddComponentTuple(ComponentTuple tuple, WindowsInstallerData output) + private void AddComponentTuple(ComponentTuple tuple) { var attributes = ComponentLocation.Either == tuple.Location ? WindowsInstallerConstants.MsidbComponentAttributesOptional : 0; attributes |= ComponentLocation.SourceOnly == tuple.Location ? WindowsInstallerConstants.MsidbComponentAttributesSourceOnly : 0; @@ -359,8 +356,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind attributes |= tuple.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence : 0; attributes |= tuple.Win64 ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0; - var table = output.EnsureTable(this.TableDefinitions["Component"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Component"); row[0] = tuple.Id.Id; row[1] = tuple.ComponentId; row[2] = tuple.DirectoryRef; @@ -369,7 +365,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[5] = tuple.KeyPath; } - private void AddCustomActionTuple(CustomActionTuple tuple, WindowsInstallerData output) + private void AddCustomActionTuple(CustomActionTuple tuple) { var type = tuple.Win64 ? WindowsInstallerConstants.MsidbCustomActionType64BitScript : 0; type |= tuple.IgnoreResult ? WindowsInstallerConstants.MsidbCustomActionTypeContinue : 0; @@ -396,8 +392,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind type |= tuple.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0; } - var table = output.EnsureTable(this.TableDefinitions["CustomAction"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "CustomAction"); row[0] = tuple.Id.Id; row[1] = type; row[2] = tuple.Source; @@ -405,7 +400,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[4] = tuple.PatchUninstall ? (int?)WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall : null; } - private void AddDialogTuple(DialogTuple tuple, WindowsInstallerData output) + private void AddDialogTuple(DialogTuple tuple) { var attributes = tuple.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0; attributes|= tuple.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; @@ -419,8 +414,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind attributes|= tuple.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; attributes|= tuple.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; - var table = output.EnsureTable(this.TableDefinitions["Dialog"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Dialog"); row[0] = tuple.Id.Id; row[1] = tuple.HCentering; row[2] = tuple.VCentering; @@ -432,10 +426,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[8] = tuple.DefaultControlRef; row[9] = tuple.CancelControlRef; - output.EnsureTable(this.TableDefinitions["ListBox"]); + this.Output.EnsureTable(this.TableDefinitions["ListBox"]); } - private void AddDirectoryTuple(DirectoryTuple tuple, WindowsInstallerData output) + private void AddDirectoryTuple(DirectoryTuple tuple) { var sourceName = GetMsiFilenameValue(tuple.SourceShortName, tuple.SourceName); var targetName = GetMsiFilenameValue(tuple.ShortName, tuple.Name); @@ -447,14 +441,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName ; - var table = output.EnsureTable(this.TableDefinitions["Directory"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Directory"); row[0] = tuple.Id.Id; row[1] = tuple.ParentDirectoryRef; row[2] = defaultDir; } - private void AddEnvironmentTuple(EnvironmentTuple tuple, WindowsInstallerData output) + private void AddEnvironmentTuple(EnvironmentTuple tuple) { var action = String.Empty; var system = tuple.System ? "*" : String.Empty; @@ -484,23 +477,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind break; } - var table = output.EnsureTable(this.TableDefinitions["Environment"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Environment"); row[0] = tuple.Id.Id; row[1] = String.Concat(action, uninstall, system, tuple.Name); row[2] = value; row[3] = tuple.ComponentRef; } - private void AddErrorTuple(ErrorTuple tuple, WindowsInstallerData output) + private void AddErrorTuple(ErrorTuple tuple) { - var table = output.EnsureTable(this.TableDefinitions["Error"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Error"); row[0] = Convert.ToInt32(tuple.Id.Id); row[1] = tuple.Message; } - private void AddFeatureTuple(FeatureTuple tuple, WindowsInstallerData output) + private void AddFeatureTuple(FeatureTuple tuple) { var attributes = tuple.DisallowAbsent ? WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent : 0; attributes |= tuple.DisallowAdvertise ? WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise : 0; @@ -508,8 +499,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind attributes |= FeatureInstallDefault.Source == tuple.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorSource : 0; attributes |= FeatureTypicalDefault.Advertise == tuple.TypicalDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise : 0; - var table = output.EnsureTable(this.TableDefinitions["Feature"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Feature"); row[0] = tuple.Id.Id; row[1] = tuple.ParentFeatureRef; row[2] = tuple.Title; @@ -520,16 +510,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[7] = attributes; } - private void AddFileTuple(FileTuple tuple, WindowsInstallerData output) + private void AddFileTuple(FileTuple tuple) { - var table = output.EnsureTable(this.TableDefinitions["File"]); - var row = (FileRow)table.CreateRow(tuple.SourceLineNumbers); + var row = (FileRow)this.CreateRow(tuple, "File"); row.File = tuple.Id.Id; row.Component = tuple.ComponentRef; row.FileName = GetMsiFilenameValue(tuple.ShortName, tuple.Name); row.FileSize = tuple.FileSize; row.Version = tuple.Version; row.Language = tuple.Language; + row.DiskId = tuple.DiskId ?? 1; // TODO: is 0 the correct thing to default here + row.Source = tuple.Source.Path; var attributes = (tuple.Attributes & FileTupleAttributes.Checksum) == FileTupleAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0; attributes |= (tuple.Attributes & FileTupleAttributes.Compressed) == FileTupleAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0; @@ -542,19 +533,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (!String.IsNullOrEmpty(tuple.FontTitle)) { - var fontTable = output.EnsureTable(this.TableDefinitions["Font"]); - var fontRow = fontTable.CreateRow(tuple.SourceLineNumbers); + var fontRow = this.CreateRow(tuple, "Font"); fontRow[0] = tuple.Id.Id; fontRow[1] = tuple.FontTitle; } } - private void AddIniFileTuple(IniFileTuple tuple, WindowsInstallerData output) + private void AddIniFileTuple(IniFileTuple tuple) { var tableName = (InifFileActionType.AddLine == tuple.Action || InifFileActionType.AddTag == tuple.Action || InifFileActionType.CreateLine == tuple.Action) ? "IniFile" : "RemoveIniFile"; - var table = output.EnsureTable(this.TableDefinitions[tableName]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, tableName); row[0] = tuple.Id.Id; row[1] = tuple.FileName; row[2] = tuple.DirProperty; @@ -565,12 +554,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[7] = tuple.ComponentRef; } - private void AddMediaTuple(MediaTuple tuple, WindowsInstallerData output) + private void AddMediaTuple(MediaTuple tuple) { if (this.Section.Type != SectionType.Module) { - var table = output.EnsureTable(this.TableDefinitions["Media"]); - var row = (MediaRow)table.CreateRow(tuple.SourceLineNumbers); + var row = (MediaRow)this.CreateRow(tuple, "Media"); row.DiskId = tuple.DiskId; row.LastSequence = tuple.LastSequence ?? 0; row.DiskPrompt = tuple.DiskPrompt; @@ -580,10 +568,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - private void AddModuleConfigurationTuple(ModuleConfigurationTuple tuple, WindowsInstallerData output) + private void AddModuleConfigurationTuple(ModuleConfigurationTuple tuple) { - var table = output.EnsureTable(this.TableDefinitions["ModuleConfiguration"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "ModuleConfiguration"); row[0] = tuple.Id.Id; row[1] = tuple.Format; row[2] = tuple.Type; @@ -597,13 +584,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[9] = tuple.HelpKeyword; } - private void AddMsiEmbeddedUITuple(MsiEmbeddedUITuple tuple, WindowsInstallerData output) + private void AddMsiEmbeddedUITuple(MsiEmbeddedUITuple tuple) { var attributes = tuple.EntryPoint ? WindowsInstallerConstants.MsidbEmbeddedUI : 0; attributes |= tuple.SupportsBasicUI ? WindowsInstallerConstants.MsidbEmbeddedHandlesBasic : 0; - var table = output.EnsureTable(this.TableDefinitions["MsiEmbeddedUI"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "MsiEmbeddedUI"); row[0] = tuple.Id.Id; row[1] = tuple.FileName; row[2] = attributes; @@ -611,10 +597,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[4] = tuple.Source; } - private void AddMsiFileHashTuple(MsiFileHashTuple tuple, WindowsInstallerData output) + private void AddMsiFileHashTuple(MsiFileHashTuple tuple) { - var table = output.EnsureTable(this.TableDefinitions["MsiFileHash"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "MsiFileHash"); row[0] = tuple.Id.Id; row[1] = tuple.Options; row[2] = tuple.HashPart1; @@ -623,14 +608,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[5] = tuple.HashPart4; } - private void AddMsiServiceConfigTuple(MsiServiceConfigTuple tuple, WindowsInstallerData output) + private void AddMsiServiceConfigTuple(MsiServiceConfigTuple tuple) { var events = tuple.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; events |= tuple.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; events |= tuple.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; - var table = output.EnsureTable(this.TableDefinitions["MsiServiceConfigFailureActions"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "MsiServiceConfigFailureActions"); row[0] = tuple.Id.Id; row[1] = tuple.Name; row[2] = events; @@ -639,14 +623,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[5] = tuple.ComponentRef; } - private void AddMsiServiceConfigFailureActionsTuple(MsiServiceConfigFailureActionsTuple tuple, WindowsInstallerData output) + private void AddMsiServiceConfigFailureActionsTuple(MsiServiceConfigFailureActionsTuple tuple) { var events = tuple.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; events |= tuple.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; events |= tuple.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; - var table = output.EnsureTable(this.TableDefinitions["MsiServiceConfig"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "MsiServiceConfig"); row[0] = tuple.Id.Id; row[1] = tuple.Name; row[2] = events; @@ -658,10 +641,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[8] = tuple.ComponentRef; } - private void AddMoveFileTuple(MoveFileTuple tuple, WindowsInstallerData output) + private void AddMoveFileTuple(MoveFileTuple tuple) { - var table = output.EnsureTable(this.TableDefinitions["MoveFile"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "MoveFile"); row[0] = tuple.Id.Id; row[1] = tuple.ComponentRef; row[2] = tuple.SourceName; @@ -671,26 +653,24 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[6] = tuple.Delete ? WindowsInstallerConstants.MsidbMoveFileOptionsMove : 0; } - private void AddPropertyTuple(PropertyTuple tuple, WindowsInstallerData output) + private void AddPropertyTuple(PropertyTuple tuple) { if (String.IsNullOrEmpty(tuple.Value)) { return; } - var table = output.EnsureTable(this.TableDefinitions["Property"]); - var row = (PropertyRow)table.CreateRow(tuple.SourceLineNumbers); + var row = (PropertyRow)this.CreateRow(tuple, "Property"); row.Property = tuple.Id.Id; row.Value = tuple.Value; } - private void AddRemoveFileTuple(RemoveFileTuple tuple, WindowsInstallerData output) + private void AddRemoveFileTuple(RemoveFileTuple tuple) { var installMode = tuple.OnInstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall : 0; installMode |= tuple.OnUninstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove : 0; - var table = output.EnsureTable(this.TableDefinitions["RemoveFile"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "RemoveFile"); row[0] = tuple.Id.Id; row[1] = tuple.ComponentRef; row[2] = tuple.FileName; @@ -698,7 +678,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[4] = installMode; } - private void AddRegistryTuple(RegistryTuple tuple, WindowsInstallerData output) + private void AddRegistryTuple(RegistryTuple tuple) { var value = tuple.Value; @@ -740,8 +720,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind break; } - var table = output.EnsureTable(this.TableDefinitions["Registry"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Registry"); row[0] = tuple.Id.Id; row[1] = tuple.Root; row[2] = tuple.Key; @@ -750,13 +729,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[5] = tuple.ComponentRef; } - private void AddRegLocatorTuple(RegLocatorTuple tuple, WindowsInstallerData output) + private void AddRegLocatorTuple(RegLocatorTuple tuple) { var type = (int)tuple.Type; type |= tuple.Win64 ? WindowsInstallerConstants.MsidbLocatorType64bit : 0; - var table = output.EnsureTable(this.TableDefinitions["RegLocator"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "RegLocator"); row[0] = tuple.Id.Id; row[1] = tuple.Root; row[2] = tuple.Key; @@ -764,12 +742,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[4] = type; } - private void AddRemoveRegistryTuple(RemoveRegistryTuple tuple, WindowsInstallerData output) + private void AddRemoveRegistryTuple(RemoveRegistryTuple tuple) { if (tuple.Action == RemoveRegistryActionType.RemoveOnInstall) { - var table = output.EnsureTable(this.TableDefinitions["RemoveRegistry"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "RemoveRegistry"); row[0] = tuple.Id.Id; row[1] = tuple.Root; row[2] = tuple.Key; @@ -778,8 +755,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else // Registry table is used to remove registry keys on uninstall. { - var table = output.EnsureTable(this.TableDefinitions["Registry"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Registry"); row[0] = tuple.Id.Id; row[1] = tuple.Root; row[2] = tuple.Key; @@ -788,7 +764,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - private void AddServiceControlTuple(ServiceControlTuple tuple, WindowsInstallerData output) + private void AddServiceControlTuple(ServiceControlTuple tuple) { var events = tuple.InstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventDelete : 0; events |= tuple.UninstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete : 0; @@ -797,8 +773,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind events |= tuple.InstallStop ? WindowsInstallerConstants.MsidbServiceControlEventStop : 0; events |= tuple.UninstallStop ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStop : 0; - var table = output.EnsureTable(this.TableDefinitions["ServiceControl"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "ServiceControl"); row[0] = tuple.Id.Id; row[1] = tuple.Name; row[2] = events; @@ -810,7 +785,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[5] = tuple.ComponentRef; } - private void AddServiceInstallTuple(ServiceInstallTuple tuple, WindowsInstallerData output) + private void AddServiceInstallTuple(ServiceInstallTuple tuple) { var errorControl = (int)tuple.ErrorControl; errorControl |= tuple.Vital ? WindowsInstallerConstants.MsidbServiceInstallErrorControlVital : 0; @@ -818,8 +793,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var serviceType = (int)tuple.ServiceType; serviceType |= tuple.Interactive ? WindowsInstallerConstants.MsidbServiceInstallInteractive : 0; - var table = output.EnsureTable(this.TableDefinitions["ServiceInstall"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "ServiceInstall"); row[0] = tuple.Id.Id; row[1] = tuple.Name; row[2] = tuple.DisplayName; @@ -835,10 +809,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[12] = tuple.Description; } - private void AddShortcutTuple(ShortcutTuple tuple, WindowsInstallerData output) + private void AddShortcutTuple(ShortcutTuple tuple) { - var table = output.EnsureTable(this.TableDefinitions["Shortcut"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "Shortcut"); row[0] = tuple.Id.Id; row[1] = tuple.DirectoryRef; row[2] = GetMsiFilenameValue(tuple.ShortName, tuple.Name); @@ -857,7 +830,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[15] = tuple.DescriptionResourceId; } - private void AddTextStyleTuple(TextStyleTuple tuple, WindowsInstallerData output) + private void AddTextStyleTuple(TextStyleTuple tuple) { var styleBits = tuple.Bold ? WindowsInstallerConstants.MsidbTextStyleStyleBitsBold : 0; styleBits |= tuple.Italic ? WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic : 0; @@ -873,8 +846,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind color += (long)(tuple.Blue ?? 0) * 65536; } - var table = output.EnsureTable(this.TableDefinitions["TextStyle"]); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, "TextStyle"); row[0] = tuple.Id.Id; row[1] = tuple.FaceName; row[2] = tuple.Size; @@ -882,10 +854,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[4] = styleBits == 0 ? null : (int?)styleBits; } - private void AddUpgradeTuple(UpgradeTuple tuple, WindowsInstallerData output) + private void AddUpgradeTuple(UpgradeTuple tuple) { - var table = output.EnsureTable(this.TableDefinitions["Upgrade"]); - var row = (UpgradeRow)table.CreateRow(tuple.SourceLineNumbers); + var row = (UpgradeRow)this.CreateRow(tuple, "Upgrade"); row.UpgradeCode = tuple.UpgradeCode; row.VersionMin = tuple.VersionMin; row.VersionMax = tuple.VersionMax; @@ -902,72 +873,71 @@ namespace WixToolset.Core.WindowsInstaller.Bind row.Attributes = attributes; } - private void AddWixActionTuple(WixActionTuple tuple, WindowsInstallerData output) + private void AddWixActionTuple(WixActionTuple tuple) { // Get the table definition for the action (and ensure the proper table exists for a module). - TableDefinition sequenceTableDefinition = null; + string sequenceTableName = null; switch (tuple.SequenceTable) { case SequenceTable.AdminExecuteSequence: - if (OutputType.Module == output.Type) + if (OutputType.Module == this.Output.Type) { - output.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); - sequenceTableDefinition = this.TableDefinitions["ModuleAdminExecuteSequence"]; + this.Output.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); + sequenceTableName = "ModuleAdminExecuteSequence"; } else { - sequenceTableDefinition = this.TableDefinitions["AdminExecuteSequence"]; + sequenceTableName = "AdminExecuteSequence"; } break; case SequenceTable.AdminUISequence: - if (OutputType.Module == output.Type) + if (OutputType.Module == this.Output.Type) { - output.EnsureTable(this.TableDefinitions["AdminUISequence"]); - sequenceTableDefinition = this.TableDefinitions["ModuleAdminUISequence"]; + this.Output.EnsureTable(this.TableDefinitions["AdminUISequence"]); + sequenceTableName = "ModuleAdminUISequence"; } else { - sequenceTableDefinition = this.TableDefinitions["AdminUISequence"]; + sequenceTableName = "AdminUISequence"; } break; case SequenceTable.AdvertiseExecuteSequence: - if (OutputType.Module == output.Type) + if (OutputType.Module == this.Output.Type) { - output.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); - sequenceTableDefinition = this.TableDefinitions["ModuleAdvtExecuteSequence"]; + this.Output.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); + sequenceTableName = "ModuleAdvtExecuteSequence"; } else { - sequenceTableDefinition = this.TableDefinitions["AdvtExecuteSequence"]; + sequenceTableName = "AdvtExecuteSequence"; } break; case SequenceTable.InstallExecuteSequence: - if (OutputType.Module == output.Type) + if (OutputType.Module == this.Output.Type) { - output.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); - sequenceTableDefinition = this.TableDefinitions["ModuleInstallExecuteSequence"]; + this.Output.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); + sequenceTableName = "ModuleInstallExecuteSequence"; } else { - sequenceTableDefinition = this.TableDefinitions["InstallExecuteSequence"]; + sequenceTableName = "InstallExecuteSequence"; } break; case SequenceTable.InstallUISequence: - if (OutputType.Module == output.Type) + if (OutputType.Module == this.Output.Type) { - output.EnsureTable(this.TableDefinitions["InstallUISequence"]); - sequenceTableDefinition = this.TableDefinitions["ModuleInstallUISequence"]; + this.Output.EnsureTable(this.TableDefinitions["InstallUISequence"]); + sequenceTableName = "ModuleInstallUISequence"; } else { - sequenceTableDefinition = this.TableDefinitions["InstallUISequence"]; + sequenceTableName = "InstallUISequence"; } break; } // create the action sequence row in the output - var sequenceTable = output.EnsureTable(sequenceTableDefinition); - var row = sequenceTable.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, sequenceTableName); if (SectionType.Module == this.Section.Type) { @@ -992,7 +962,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - private void AddWixCustomRowTuple(WixCustomRowTuple tuple, WindowsInstallerData output) + private void AddWixCustomRowTuple(WixCustomRowTuple tuple) { var customTableDefinition = this.TableDefinitions[tuple.Table]; @@ -1002,8 +972,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind return; } - var customTable = output.EnsureTable(customTableDefinition); - var customRow = customTable.CreateRow(tuple.SourceLineNumbers); + var customRow = this.CreateRow(tuple, customTableDefinition); #if TODO // SectionId seems like a good thing to preserve. customRow.SectionId = tuple.SectionId; @@ -1073,16 +1042,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - private void AddWixEnsureTableTuple(WixEnsureTableTuple tuple, WindowsInstallerData output) + private void AddWixEnsureTableTuple(WixEnsureTableTuple tuple) { var tableDefinition = this.TableDefinitions[tuple.Table]; - output.EnsureTable(tableDefinition); + this.Output.EnsureTable(tableDefinition); } - private void AddWixMediaTemplateTuple(WixMediaTemplateTuple tuple, WindowsInstallerData output) + private void AddWixMediaTemplateTuple(WixMediaTemplateTuple tuple) { - var table = output.EnsureTable(this.TableDefinitions["WixMediaTemplate"]); - var row = (WixMediaTemplateRow)table.CreateRow(tuple.SourceLineNumbers); + var row = (WixMediaTemplateRow)this.CreateRow(tuple, "WixMediaTemplate"); row.CabinetTemplate = tuple.CabinetTemplate; row.CompressionLevel = tuple.CompressionLevel; row.DiskPrompt = tuple.DiskPrompt; @@ -1091,26 +1059,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind row.MaximumCabinetSizeForLargeFileSplitting = tuple.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting; } - private void AddTupleFromExtension(IntermediateTuple tuple, WindowsInstallerData output) + private void AddTupleFromExtension(IntermediateTuple tuple) { foreach (var extension in this.BackendExtensions) { - if (extension.TryAddTupleToOutput(tuple, output)) + if (extension.TryAddTupleToOutput(tuple, this.Output)) { break; } } } - private void AddTupleDefaultly(IntermediateTuple tuple, WindowsInstallerData output, bool idIsPrimaryKey = false, string tableName = null) + private void AddTupleDefaultly(IntermediateTuple tuple, bool idIsPrimaryKey = false, string tableName = null) { if (!this.TableDefinitions.TryGet(tableName ?? tuple.Definition.Name, out var tableDefinition)) { return; } - var table = output.EnsureTable(tableDefinition); - var row = table.CreateRow(tuple.SourceLineNumbers); + var row = this.CreateRow(tuple, tableDefinition); var rowOffset = 0; if (idIsPrimaryKey) @@ -1159,6 +1126,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } + private Row CreateRow(IntermediateTuple tuple, string tableDefinitionName) => this.CreateRow(tuple, this.TableDefinitions[tableDefinitionName]); + + private Row CreateRow(IntermediateTuple tuple, TableDefinition tableDefinition) + { + var table = this.Output.EnsureTable(tableDefinition); + + var row = table.CreateRow(tuple.SourceLineNumbers); + row.SectionId = this.Section.Id; + + return row; + } + private static string GetMsiFilenameValue(string shortName, string longName) { if (String.IsNullOrEmpty(shortName) || String.Equals(shortName, longName, StringComparison.OrdinalIgnoreCase)) diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs new file mode 100644 index 00000000..854d973e --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs @@ -0,0 +1,90 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Core.WindowsInstaller.Msi; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class CreatePatchTransformsCommand + { + public CreatePatchTransformsCommand(IMessaging messaging, Intermediate intermediate, string intermediateFolder) + { + this.Messaging = messaging; + this.Intermediate = intermediate; + this.IntermediateFolder = intermediateFolder; + } + + private IMessaging Messaging { get; } + + private Intermediate Intermediate { get; } + + private string IntermediateFolder { get; } + + public IEnumerable PatchTransforms { get; private set; } + + public IEnumerable Execute() + { + var patchTransforms = new List(); + + var tuples = this.Intermediate.Sections.SelectMany(s => s.Tuples).OfType(); + + foreach (var tuple in tuples) + { + WindowsInstallerData transform; + + if (tuple.TransformFile is null) + { + var baselineData = this.GetData(tuple.BaselineFile.Path); + var updateData = this.GetData(tuple.UpdateFile.Path); + + var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, false); + transform = command.Execute(); + } + else + { + var exportBasePath = Path.Combine(this.IntermediateFolder, "_trans"); // TODO: come up with a better path. + + var command = new UnbindTransformCommand(this.Messaging, tuple.TransformFile.Path, exportBasePath, this.IntermediateFolder); + transform = command.Execute(); + } + + patchTransforms.Add(new PatchTransform(tuple.Id.Id, transform)); + } + + this.PatchTransforms = patchTransforms; + + return this.PatchTransforms; + } + + private WindowsInstallerData GetData(string path) + { + var ext = Path.GetExtension(path); + + if (".msi".Equals(ext, StringComparison.OrdinalIgnoreCase)) + { + using (var database = new Database(path, OpenDatabase.ReadOnly)) + { + var exportBasePath = Path.Combine(this.IntermediateFolder, "_msi"); // TODO: come up with a better path. + + var isAdminImage = false; // TODO: need a better way to set this + + var command = new UnbindDatabaseCommand(this.Messaging, database, path, OutputType.Product, exportBasePath, this.IntermediateFolder, isAdminImage, suppressDemodularization: true, skipSummaryInfo: true); + return command.Execute(); + } + } + else // assume .wixpdb (or .wixout) + { + var data = WindowsInstallerData.Load(path, true); + return data; + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs index 5412c6f9..49b6a6f8 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs @@ -48,7 +48,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var mergeModulesFileFacades = new List(); - IMsmMerge2 merge = MsmInterop.GetMsmMerge(); + var merge = MsmInterop.GetMsmMerge(); // Index all of the file rows to be able to detect collisions with files in the Merge Modules. // It may seem a bit expensive to build up this index solely for the purpose of checking collisions @@ -57,11 +57,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let // this case be slightly more expensive because the cost of maintaining an indexed file row collection // is a lot more costly for the common cases. - var indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.Id.Id, StringComparer.Ordinal); + var indexedFileFacades = this.FileFacades.ToDictionary(f => f.Id, StringComparer.Ordinal); foreach (var wixMergeRow in this.WixMergeTuples) { - bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); + var containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); // If the module has files and creating layout if (containsFiles && !this.SuppressLayout) @@ -75,21 +75,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind private bool CreateFacadesForMergeModuleFiles(WixMergeTuple wixMergeRow, List mergeModulesFileFacades, Dictionary indexedFileFacades) { - bool containsFiles = false; + var containsFiles = false; try { // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. - using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) + using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) { if (db.TableExists("File") && db.TableExists("Component")) { - Dictionary uniqueModuleFileIdentifiers = new Dictionary(StringComparer.OrdinalIgnoreCase); + var uniqueModuleFileIdentifiers = new Dictionary(StringComparer.OrdinalIgnoreCase); - using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) + using (var view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) { // add each file row from the merge module into the file row collection (check for errors along the way) - foreach (Record record in view.Records) + foreach (var record in view.Records) { // NOTE: this is very tricky - the merge module file rows are not added to the // file table because they should not be created via idt import. Instead, these @@ -103,21 +103,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind var mergeModuleFileFacade = new FileFacade(true, fileTuple); // If case-sensitive collision with another merge module or a user-authored file identifier. - if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.Id.Id, out var collidingFacade)) + if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade)) { - this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.File.Id.Id)); + this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.Id)); } - else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.Id.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module + else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module { - this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.File.Id.Id, collidingFacade.File.Id.Id)); + this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.Id, collidingFacade.Id)); } else // no collision { mergeModulesFileFacades.Add(mergeModuleFileFacade); // Keep updating the indexes as new rows are added. - indexedFileFacades.Add(mergeModuleFileFacade.File.Id.Id, mergeModuleFileFacade); - uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.Id.Id, mergeModuleFileFacade); + indexedFileFacades.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); + uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); } containsFiles = true; @@ -126,13 +126,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind } // Get the summary information to detect the Schema - using (SummaryInformation summaryInformation = new SummaryInformation(db)) + using (var summaryInformation = new SummaryInformation(db)) { - string moduleInstallerVersionString = summaryInformation.GetProperty(14); + var moduleInstallerVersionString = summaryInformation.GetProperty(14); try { - int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); + var moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); if (moduleInstallerVersion > this.OutputInstallerVersion) { this.Messaging.Write(WarningMessages.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleInstallerVersion, this.OutputInstallerVersion)); @@ -159,7 +159,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeTuple wixMergeRow) { - bool moduleOpen = false; + var moduleOpen = false; short mergeLanguage; var mergeId = wixMergeRow.Id.Id; @@ -180,10 +180,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind moduleOpen = true; // extract the module cabinet, then explode all of the files to a temp directory - string moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab"); + var moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab"); merge.ExtractCAB(moduleCabPath); - string mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId); + var mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId); Directory.CreateDirectory(mergeIdPath); try diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs index 6b365ecd..ed3b6f01 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs @@ -5,367 +5,396 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System; using System.Collections.Generic; using System.ComponentModel; - using System.Globalization; using System.IO; using System.Text; + using WixToolset.Core.WindowsInstaller.Msi; using WixToolset.Data; - using WixToolset.Extensibility; using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; + using WixToolset.Extensibility; using WixToolset.Extensibility.Data; - using WixToolset.Core.WindowsInstaller.Msi; + using WixToolset.Extensibility.Services; - internal class GenerateDatabaseCommand + internal class GenerateDatabaseCommand { - public int Codepage { private get; set; } + public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable fileSystemExtensions, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, int codepage, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Extensions = fileSystemExtensions; + this.Data = data; + this.OutputPath = outputPath; + this.TableDefinitions = tableDefinitions; + this.IntermediateFolder = intermediateFolder; + this.Codepage = codepage; + this.KeepAddedColumns = keepAddedColumns; + this.SuppressAddingValidationRows = suppressAddingValidationRows; + this.UseSubDirectory = useSubdirectory; + } + + private int Codepage { get; } - public IBackendHelper BackendHelper { private get; set; } + private IBackendHelper BackendHelper { get; } - public IEnumerable Extensions { private get; set; } + private IEnumerable Extensions { get; } /// /// Whether to keep columns added in a transform. /// - public bool KeepAddedColumns { private get; set; } + private bool KeepAddedColumns { get; } - public IMessaging Messaging { private get; set; } + private IMessaging Messaging { get; } - public WindowsInstallerData Output { private get; set; } + private WindowsInstallerData Data { get; } - public string OutputPath { private get; set; } + private string OutputPath { get; } - public TableDefinitionCollection TableDefinitions { private get; set; } + private TableDefinitionCollection TableDefinitions { get; } - public string IntermediateFolder { private get; set; } + private string IntermediateFolder { get; } public List GeneratedTemporaryFiles { get; } = new List(); /// /// Whether to use a subdirectory based on the file name for intermediate files. /// - public bool SuppressAddingValidationRows { private get; set; } + private bool SuppressAddingValidationRows { get; } - public bool UseSubDirectory { private get; set; } + private bool UseSubDirectory { get; } public void Execute() { // Add the _Validation rows. if (!this.SuppressAddingValidationRows) { - var validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]); - - foreach (var table in this.Output.Tables) - { - if (!table.Definition.Unreal) - { - // Add the validation rows for this table. - foreach (ColumnDefinition columnDef in table.Definition.Columns) - { - var row = validationTable.CreateRow(null); - - row[0] = table.Name; - - row[1] = columnDef.Name; - - if (columnDef.Nullable) - { - row[2] = "Y"; - } - else - { - row[2] = "N"; - } - - if (columnDef.MinValue.HasValue) - { - row[3] = columnDef.MinValue.Value; - } - - if (columnDef.MaxValue.HasValue) - { - row[4] = columnDef.MaxValue.Value; - } - - row[5] = columnDef.KeyTable; - - if (columnDef.KeyColumn.HasValue) - { - row[6] = columnDef.KeyColumn.Value; - } - - if (ColumnCategory.Unknown != columnDef.Category) - { - row[7] = columnDef.Category.ToString(); - } - - row[8] = columnDef.Possibilities; - - row[9] = columnDef.Description; - } - } - } + this.AddValidationRows(); } - // Set the base directory. var baseDirectory = this.IntermediateFolder; if (this.UseSubDirectory) { - string filename = Path.GetFileNameWithoutExtension(this.OutputPath); + var filename = Path.GetFileNameWithoutExtension(this.OutputPath); baseDirectory = Path.Combine(baseDirectory, filename); - - // make sure the directory exists - Directory.CreateDirectory(baseDirectory); } - var idtDirectory = Path.Combine(baseDirectory, "_idts"); - Directory.CreateDirectory(idtDirectory); + var idtFolder = Path.Combine(baseDirectory, "_idts"); - try + var type = OpenDatabase.CreateDirect; + + if (OutputType.Patch == this.Data.Type) { - OpenDatabase type = OpenDatabase.CreateDirect; + type |= OpenDatabase.OpenPatchFile; + } - // set special flag for patch files - if (OutputType.Patch == this.Output.Type) - { - type |= OpenDatabase.OpenPatchFile; - } + // Localize the codepage if a value was specified directly. + if (-1 != this.Codepage) + { + this.Data.Codepage = this.Codepage; + } + try + { #if DEBUG Console.WriteLine("Opening database at: {0}", this.OutputPath); #endif - // Localize the codepage if a value was specified directly. - if (-1 != this.Codepage) - { - this.Output.Codepage = this.Codepage; - } - Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); - using (Database db = new Database(this.OutputPath, type)) + Directory.CreateDirectory(idtFolder); + + using (var db = new Database(this.OutputPath, type)) { - // if we're not using the default codepage, import a new one into our + // 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 != this.Output.Codepage) + if (0 != this.Data.Codepage) { - this.SetDatabaseCodepage(db, this.Output.Codepage, idtDirectory); + this.SetDatabaseCodepage(db, this.Data.Codepage, idtFolder); } - foreach (Table table in this.Output.Tables) + this.ImportTables(db, idtFolder); + + // Insert substorages (usually transforms inside a patch or instance transforms in a package). + this.ImportSubStorages(db); + + // We're good, commit the changes to the new database. + db.Commit(); + } + } + catch (IOException e) + { + // TODO: this error message doesn't seem specific enough + throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e); + } + } + + private void AddValidationRows() + { + var validationTable = this.Data.EnsureTable(this.TableDefinitions["_Validation"]); + + foreach (var table in this.Data.Tables) + { + if (!table.Definition.Unreal) + { + // Add the validation rows for this table. + foreach (var columnDef in table.Definition.Columns) { - Table importTable = table; - bool hasBinaryColumn = false; + var row = validationTable.CreateRow(null); + + row[0] = table.Name; - // Skip all unreal tables other than _Streams. - if (table.Definition.Unreal && "_Streams" != table.Name) + row[1] = columnDef.Name; + + if (columnDef.Nullable) { - continue; + row[2] = "Y"; + } + else + { + row[2] = "N"; } - // Do not put the _Validation table in patches, it is not needed. - if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name) + if (columnDef.MinValue.HasValue) { - continue; + row[3] = columnDef.MinValue.Value; } - // 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 (columnDef.MaxValue.HasValue) { - if (ColumnType.Object == columnDefinition.Type) - { - importTable = new Table(table.Definition); - hasBinaryColumn = true; - break; - } + row[4] = columnDef.MaxValue.Value; } - // Create the table via IDT import. - if ("_Streams" != importTable.Name) + row[5] = columnDef.KeyTable; + + if (columnDef.KeyColumn.HasValue) { - try - { - var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Output.Codepage, idtDirectory, this.KeepAddedColumns); - command.Execute(); + row[6] = columnDef.KeyColumn.Value; + } - var buildOutput = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary); - this.GeneratedTemporaryFiles.Add(buildOutput); + if (ColumnCategory.Unknown != columnDef.Category) + { + row[7] = columnDef.Category.ToString(); + } - db.Import(command.IdtPath); - } - catch (WixInvalidIdtException) - { - // If ValidateRows finds anything it doesn't like, it throws - importTable.ValidateRows(); + row[8] = columnDef.Possibilities; - // Otherwise we rethrow the InvalidIdt - throw; - } + row[9] = columnDef.Description; + } + } + } + } + + private void ImportTables(Database db, string idtDirectory) + { + foreach (var table in this.Data.Tables) + { + var importTable = table; + var hasBinaryColumn = false; + + // Skip all unreal tables other than _Streams. + if (table.Definition.Unreal && "_Streams" != table.Name) + { + continue; + } + + // Do not put the _Validation table in patches, it is not needed. + if (OutputType.Patch == this.Data.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 (var columnDefinition in table.Definition.Columns) + { + if (ColumnType.Object == columnDefinition.Type) + { + importTable = new Table(table.Definition); + hasBinaryColumn = true; + break; + } + } + + // Create the table via IDT import. + if ("_Streams" != importTable.Name) + { + try + { + var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Data.Codepage, idtDirectory, this.KeepAddedColumns); + command.Execute(); + + var buildOutput = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary); + this.GeneratedTemporaryFiles.Add(buildOutput); + + db.Import(command.IdtPath); + } + 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) + { + var query = new StringBuilder("SELECT "); + + // Build the query for the view. + var firstColumn = true; + foreach (var columnDefinition in table.Definition.Columns) + { + if (columnDefinition.Unreal) + { + continue; } - // insert the rows via SQL query if this table contains object fields - if (hasBinaryColumn) + if (!firstColumn) { - StringBuilder query = new StringBuilder("SELECT "); + query.Append(","); + } - // Build the query for the view. - bool firstColumn = true; - foreach (ColumnDefinition columnDefinition in table.Definition.Columns) + query.AppendFormat(" `{0}`", columnDefinition.Name); + firstColumn = false; + } + query.AppendFormat(" FROM `{0}`", table.Name); + + using (var tableView = db.OpenExecuteView(query.ToString())) + { + // Import each row containing a stream + foreach (var row in table.Rows) + { + using (var record = new Record(table.Definition.Columns.Length)) { - if (!firstColumn) + // Stream names are created by concatenating the name of the table with the values + // of the primary key (delimited by periods). + var streamName = new StringBuilder(); + + // the _Streams table doesn't prepend the table name (or a period) + if ("_Streams" != table.Name) { - query.Append(","); + streamName.Append(table.Name); } - query.AppendFormat(" `{0}`", columnDefinition.Name); - firstColumn = false; - } - query.AppendFormat(" FROM `{0}`", table.Name); + var needStream = false; - using (View tableView = db.OpenExecuteView(query.ToString())) - { - // Import each row containing a stream - foreach (Row row in table.Rows) + for (var i = 0; i < table.Definition.Columns.Length; i++) { - using (Record record = new Record(table.Definition.Columns.Length)) + var columnDefinition = table.Definition.Columns[i]; + + if (columnDefinition.Unreal) { - StringBuilder streamName = new StringBuilder(); - bool needStream = false; + continue; + } + + switch (columnDefinition.Type) + { + case ColumnType.Localized: + case ColumnType.Preserved: + case ColumnType.String: + var str = row.FieldAsString(i); - // the _Streams table doesn't prepend the table name (or a period) - if ("_Streams" != table.Name) - { - streamName.Append(table.Name); - } + if (columnDefinition.PrimaryKey) + { + if (0 < streamName.Length) + { + streamName.Append("."); + } + + streamName.Append(str); + } - for (int i = 0; i < table.Definition.Columns.Length; i++) - { - ColumnDefinition columnDefinition = table.Definition.Columns[i]; + record.SetString(i + 1, str); + break; + case ColumnType.Number: + record.SetInteger(i + 1, row.FieldAsInteger(i)); + break; - switch (columnDefinition.Type) + case ColumnType.Object: + if (null != row[i]) { - case ColumnType.Localized: - case ColumnType.Preserved: - case ColumnType.String: - if (columnDefinition.PrimaryKey) + needStream = true; + try + { + record.SetStream(i + 1, row.FieldAsString(i)); + } + catch (Win32Exception e) + { + if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME { - if (0 < streamName.Length) - { - streamName.Append("."); - } - streamName.Append((string)row[i]); + throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, row.FieldAsString(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]) + else { - needStream = true; - try - { - record.SetStream(i + 1, (string)row[i]); - } - catch (Win32Exception e) - { - if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME - { - throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, (string)row[i])); - } - else - { - throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); - } - } + throw new WixException(ErrorMessages.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.Messaging.Write(ErrorMessages.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); - } - else // add the row to the database - { - tableView.Modify(ModifyView.Assign, record); - } + break; } } - } - - // Remove rows from the _Streams table for wixpdbs. - if ("_Streams" == table.Name) - { - table.Rows.Clear(); - } - } - } - // Insert substorages (usually transforms inside a patch or instance transforms in a package). - if (0 < this.Output.SubStorages.Count) - { - using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) - { - foreach (SubStorage subStorage in this.Output.SubStorages) - { - string transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst")); - - // Bind the transform. - this.BindTransform(subStorage.Data, transformFile); - - if (this.Messaging.EncounteredError) + // check for a stream name that is more than 62 characters long (the maximum allowed length) + if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length) { - continue; + this.Messaging.Write(ErrorMessages.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); } - - // add the storage - using (Record record = new Record(2)) + else // add the row to the database { - record.SetString(1, subStorage.Name); - record.SetStream(2, transformFile); - storagesView.Modify(ModifyView.Assign, record); + tableView.Modify(ModifyView.Assign, record); } } } } - // We're good, commit the changes to the new database. - db.Commit(); + // Remove rows from the _Streams table for wixpdbs. + if ("_Streams" == table.Name) + { + table.Rows.Clear(); + } } } - catch (IOException e) - { - // TODO: this error message doesn't seem specific enough - throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e); - } } - private void BindTransform(WindowsInstallerData transform, string outputPath) + private void ImportSubStorages(Database db) { - var command = new BindTransformCommand(); - command.Messaging = this.Messaging; - command.Extensions = this.Extensions; - command.TempFilesLocation = this.IntermediateFolder; - command.Transform = transform; - command.OutputPath = outputPath; - command.TableDefinitions = this.TableDefinitions; - command.Execute(); + if (0 < this.Data.SubStorages.Count) + { + using (var storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) + { + foreach (var subStorage in this.Data.SubStorages) + { + var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst")); + + // Bind the transform. + var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.Extensions, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions); + command.Execute(); + + if (this.Messaging.EncounteredError) + { + continue; + } + + // Add the storage to the database. + using (var record = new Record(2)) + { + record.SetString(1, subStorage.Name); + record.SetStream(2, transformFile); + storagesView.Modify(ModifyView.Assign, record); + } + } + } + } } - private void SetDatabaseCodepage(Database db, int codepage, string idtDirectory) + private void SetDatabaseCodepage(Database db, int codepage, string idtFolder) { - // write out the _ForceCodepage IDT file - var idtPath = Path.Combine(idtDirectory, "_ForceCodepage.idt"); + // Write out the _ForceCodepage IDT file. + var idtPath = Path.Combine(idtFolder, "_ForceCodepage.idt"); using (var idtFile = new StreamWriter(idtPath, false, Encoding.ASCII)) { idtFile.WriteLine(); // dummy column name record @@ -377,14 +406,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind var trackId = this.BackendHelper.TrackFile(idtPath, TrackedFileType.Temporary); this.GeneratedTemporaryFiles.Add(trackId); - // try to import the table into the MSI + // Try to import the table into the MSI. try { db.Import(idtPath); } catch (WixInvalidIdtException) { - // the IDT should be valid, so an invalid code page was given + // The IDT should be valid, so an invalid code page was given. throw new WixException(ErrorMessages.IllegalCodepage(codepage)); } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs new file mode 100644 index 00000000..8a7dd702 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs @@ -0,0 +1,588 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using WixToolset.Core.WindowsInstaller.Msi; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + /// + /// Creates a transform by diffing two outputs. + /// + public sealed class GenerateTransformCommand + { + private const char sectionDelimiter = '/'; + private readonly IMessaging messaging; + private SummaryInformationStreams transformSummaryInfo; + + /// + /// Instantiates a new Differ class. + /// + public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool showPedanticMessages) + { + this.messaging = messaging; + this.TargetOutput = targetOutput; + this.UpdatedOutput = updatedOutput; + this.ShowPedanticMessages = showPedanticMessages; + } + + private WindowsInstallerData TargetOutput { get; } + + private WindowsInstallerData UpdatedOutput { get; } + + private TransformFlags ValidationFlags { get; } + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + private bool ShowPedanticMessages { get; } + + /// + /// Gets or sets the option to suppress keeping special rows. + /// + /// The option to suppress keeping special rows. + private bool SuppressKeepingSpecialRows { get; } + + /// + /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. + /// + /// The option to keep all rows including unchanged rows. + private bool PreserveUnchangedRows { get; } + + public WindowsInstallerData Transform { get; private set; } + + /// + /// Creates a transform by diffing two outputs. + /// + /// The target output. + /// The updated output. + /// + /// The transform. + public WindowsInstallerData Execute() + { + var targetOutput = this.TargetOutput; + var updatedOutput = this.UpdatedOutput; + var validationFlags = this.ValidationFlags; + + var transform = new WindowsInstallerData(null) + { + Type = OutputType.Transform, + Codepage = updatedOutput.Codepage + }; + + this.transformSummaryInfo = new SummaryInformationStreams(); + + // compare the codepages + if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) + { + this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); + if (null != updatedOutput.SourceLineNumbers) + { + this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); + } + } + + // compare the output types + if (targetOutput.Type != updatedOutput.Type) + { + throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); + } + + // compare the contents of the tables + foreach (var targetTable in targetOutput.Tables) + { + var updatedTable = updatedOutput.Tables[targetTable.Name]; + var operation = TableOperation.None; + + var rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); + + if (TableOperation.Drop == operation) + { + var droppedTable = transform.EnsureTable(targetTable.Definition); + droppedTable.Operation = TableOperation.Drop; + } + else if (TableOperation.None == operation) + { + var modified = transform.EnsureTable(updatedTable.Definition); + foreach (var row in rows) + { + modified.Rows.Add(row); + } + } + } + + // added tables + foreach (var updatedTable in updatedOutput.Tables) + { + if (null == targetOutput.Tables[updatedTable.Name]) + { + var addedTable = transform.EnsureTable(updatedTable.Definition); + addedTable.Operation = TableOperation.Add; + + foreach (var updatedRow in updatedTable.Rows) + { + updatedRow.Operation = RowOperation.Add; + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + addedTable.Rows.Add(updatedRow); + } + } + } + + // set summary information properties + if (!this.SuppressKeepingSpecialRows) + { + var summaryInfoTable = transform.Tables["_SummaryInformation"]; + this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); + } + + this.Transform = transform; + return this.Transform; + } + + /// + /// Add a row to the using the primary key. + /// + /// The indexed rows. + /// The row to index. + private void AddIndexedRow(Dictionary index, Row row) + { + var primaryKey = row.GetPrimaryKey(); + + if (null != primaryKey) + { + if (index.TryGetValue(primaryKey, out var collisionRow)) + { + // Overriding WixActionRows have a primary key defined and take precedence in the index. + if (row is WixActionRow actionRow) + { + // If the current row is not overridable, see if the indexed row is. + if (!actionRow.Overridable) + { + if (collisionRow is WixActionRow indexedRow && indexedRow.Overridable) + { + // The indexed key is overridable and should be replaced. + index[primaryKey] = actionRow; + } + } + + // If we got this far, the row does not need to be indexed. + return; + } + + if (this.ShowPedanticMessages) + { + this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); + } + } + else + { + index.Add(primaryKey, row); + } + } + else // use the string representation of the row as its primary key (it may not be unique) + { + // this is provided for compatibility with unreal tables with no primary key + // all real tables must specify at least one column as the primary key + primaryKey = row.ToString(); + index[primaryKey] = row; + } + } + + private bool CompareRows(Table targetTable, Row targetRow, Row updatedRow, out Row comparedRow) + { + comparedRow = null; + + var keepRow = false; + + if (null == targetRow ^ null == updatedRow) + { + if (null == targetRow) + { + updatedRow.Operation = RowOperation.Add; + comparedRow = updatedRow; + } + else if (null == updatedRow) + { + targetRow.Operation = RowOperation.Delete; + targetRow.SectionId += sectionDelimiter; + + comparedRow = targetRow; + keepRow = true; + } + } + else // possibly modified + { + updatedRow.Operation = RowOperation.None; + if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) + { + // ignore rows that shouldn't be in a transform + if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) + { + updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + comparedRow = updatedRow; + keepRow = true; + } + } + else + { + if (this.PreserveUnchangedRows) + { + keepRow = true; + } + + for (var i = 0; i < updatedRow.Fields.Length; i++) + { + var columnDefinition = updatedRow.Fields[i].Column; + + if (columnDefinition.Unreal) + { + } + else if (!columnDefinition.PrimaryKey) + { + var modified = false; + + if (i >= targetRow.Fields.Length) + { + columnDefinition.Added = true; + modified = true; + } + else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) + { + if (null == targetRow[i] ^ null == updatedRow[i]) + { + modified = true; + } + else if (null != targetRow[i] && null != updatedRow[i]) + { + modified = (targetRow.FieldAsInteger(i) != updatedRow.FieldAsInteger(i)); + } + } + else if (ColumnType.Preserved == columnDefinition.Type) + { + updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); + + // keep rows containing preserved fields so the historical data is available to the binder + keepRow = !this.SuppressKeepingSpecialRows; + } + else if (ColumnType.Object == columnDefinition.Type) + { + var targetObjectField = (ObjectField)targetRow.Fields[i]; + var updatedObjectField = (ObjectField)updatedRow.Fields[i]; + + updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex; + updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri; + + // always keep a copy of the previous data even if they are identical + // This makes diff.wixmst clean and easier to control patch logic + updatedObjectField.PreviousData = (string)targetObjectField.Data; + + // always remember the unresolved data for target build + updatedObjectField.UnresolvedPreviousData = targetObjectField.UnresolvedData; + + // keep rows containing object fields so the files can be compared in the binder + keepRow = !this.SuppressKeepingSpecialRows; + } + else + { + modified = (targetRow.FieldAsString(i) != updatedRow.FieldAsString(i)); + } + + if (modified) + { + if (null != updatedRow.Fields[i].PreviousData) + { + updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); + } + + updatedRow.Fields[i].Modified = true; + updatedRow.Operation = RowOperation.Modify; + keepRow = true; + } + } + } + + if (keepRow) + { + comparedRow = updatedRow; + comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + } + } + } + + return keepRow; + } + + private List CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) + { + var rows = new List(); + operation = TableOperation.None; + + // dropped tables + if (null == updatedTable ^ null == targetTable) + { + if (null == targetTable) + { + operation = TableOperation.Add; + rows.AddRange(updatedTable.Rows); + } + else if (null == updatedTable) + { + operation = TableOperation.Drop; + } + } + else // possibly modified tables + { + var updatedPrimaryKeys = new Dictionary(); + var targetPrimaryKeys = new Dictionary(); + + // compare the table definitions + if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) + { + // continue to the next table; may be more mismatches + this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); + } + else + { + this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); + + // diff the target and updated rows + foreach (var targetPrimaryKeyEntry in targetPrimaryKeys) + { + var targetPrimaryKey = targetPrimaryKeyEntry.Key; + var targetRow = targetPrimaryKeyEntry.Value; + updatedPrimaryKeys.TryGetValue(targetPrimaryKey, out var updatedRow); + + var keepRow = this.CompareRows(targetTable, targetRow, updatedRow, out var compared); + + if (keepRow) + { + rows.Add(compared); + } + } + + // find the inserted rows + foreach (var updatedPrimaryKeyEntry in updatedPrimaryKeys) + { + var updatedPrimaryKey = updatedPrimaryKeyEntry.Key; + + if (!targetPrimaryKeys.ContainsKey(updatedPrimaryKey)) + { + var updatedRow = updatedPrimaryKeyEntry.Value; + + updatedRow.Operation = RowOperation.Add; + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + rows.Add(updatedRow); + } + } + } + } + + return rows; + } + + private void IndexPrimaryKeys(Table targetTable, Dictionary targetPrimaryKeys, Table updatedTable, Dictionary updatedPrimaryKeys) + { + // index the target rows + foreach (var row in targetTable.Rows) + { + this.AddIndexedRow(targetPrimaryKeys, row); + + if ("Property" == targetTable.Name) + { + var id = row.FieldAsString(0); + + if ("ProductCode" == id) + { + this.transformSummaryInfo.TargetProductCode = row.FieldAsString(1); + + if ("*" == this.transformSummaryInfo.TargetProductCode) + { + this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); + } + } + else if ("ProductVersion" == id) + { + this.transformSummaryInfo.TargetProductVersion = row.FieldAsString(1); + } + else if ("UpgradeCode" == id) + { + this.transformSummaryInfo.TargetUpgradeCode = row.FieldAsString(1); + } + } + else if ("_SummaryInformation" == targetTable.Name) + { + var id = row.FieldAsInteger(0); + + if (1 == id) // PID_CODEPAGE + { + this.transformSummaryInfo.TargetSummaryInfoCodepage = row.FieldAsString(1); + } + else if (7 == id) // PID_TEMPLATE + { + this.transformSummaryInfo.TargetPlatformAndLanguage = row.FieldAsString(1); + } + else if (14 == id) // PID_PAGECOUNT + { + this.transformSummaryInfo.TargetMinimumVersion = row.FieldAsString(1); + } + } + } + + // index the updated rows + foreach (var row in updatedTable.Rows) + { + this.AddIndexedRow(updatedPrimaryKeys, row); + + if ("Property" == updatedTable.Name) + { + var id = row.FieldAsString(0); + + if ("ProductCode" == id) + { + this.transformSummaryInfo.UpdatedProductCode = row.FieldAsString(1); + + if ("*" == this.transformSummaryInfo.UpdatedProductCode) + { + this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); + } + } + else if ("ProductVersion" == id) + { + this.transformSummaryInfo.UpdatedProductVersion = row.FieldAsString(1); + } + } + else if ("_SummaryInformation" == updatedTable.Name) + { + var id = row.FieldAsInteger(0); + + if (1 == id) // PID_CODEPAGE + { + this.transformSummaryInfo.UpdatedSummaryInfoCodepage = row.FieldAsString(1); + } + else if (7 == id) // PID_TEMPLATE + { + this.transformSummaryInfo.UpdatedPlatformAndLanguage = row.FieldAsString(1); + } + else if (14 == id) // PID_PAGECOUNT + { + this.transformSummaryInfo.UpdatedMinimumVersion = row.FieldAsString(1); + } + } + } + } + + private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) + { + // calculate the minimum version of MSI required to process the transform + var minimumVersion = 100; + + if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out var targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out var updatedMin)) + { + minimumVersion = Math.Max(targetMin, updatedMin); + } + + var summaryRows = new Dictionary(summaryInfoTable.Rows.Count); + + foreach (var row in summaryInfoTable.Rows) + { + var id = row.FieldAsInteger(0); + + summaryRows[id] = row; + + if ((int)SummaryInformation.Transform.CodePage == id) + { + row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; + row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; + } + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == id) + { + row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == id) + { + row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.ProductCodes == id) + { + row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); + } + else if ((int)SummaryInformation.Transform.InstallerRequirement == id) + { + row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); + } + else if ((int)SummaryInformation.Transform.Security == id) + { + row[1] = "4"; + } + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; + summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; + summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; + summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.InstallerRequirement)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; + summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.Security; + summaryRow[1] = "4"; + } + } + + private class SummaryInformationStreams + { + public string TargetSummaryInfoCodepage { get; set; } + + public string TargetPlatformAndLanguage { get; set; } + + public string TargetProductCode { get; set; } + + public string TargetProductVersion { get; set; } + + public string TargetUpgradeCode { get; set; } + + public string TargetMinimumVersion { get; set; } + + public string UpdatedSummaryInfoCodepage { get; set; } + + public string UpdatedPlatformAndLanguage { get; set; } + + public string UpdatedProductCode { get; set; } + + public string UpdatedProductVersion { get; set; } + + public string UpdatedMinimumVersion { get; set; } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs index 0da6a6b0..2844f797 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs @@ -132,7 +132,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind file.SymbolPaths = String.Concat(file.SymbolPaths, ";", row.SymbolPaths); } -#if REVISIT_FOR_PATCHING +#if TODO_PATCHING Field field = row.Fields[2]; if (null != field.PreviousData) { diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs new file mode 100644 index 00000000..9818f01a --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs @@ -0,0 +1,585 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class GetFileFacadesFromTransforms + { + public GetFileFacadesFromTransforms(IMessaging messaging, IEnumerable subStorages, TableDefinitionCollection tableDefinitions) + { + this.Messaging = messaging; + this.SubStorages = subStorages; + this.TableDefinitions = tableDefinitions; + } + + public IEnumerable Extensions { get; } + + private IMessaging Messaging { get; } + + public IEnumerable SubStorages { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + public IEnumerable FileFacades { get; private set; } + + public void Execute() + { + var allFileRows = new List(); + var copyToPatch = (allFileRows != null); +#if TODO_PATCHING + var patchMediaRows = new RowDictionary(); + + var patchMediaFileRows = new Dictionary>(); + + var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); + var patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]); + + if (copyFromPatch) + { + // index patch files by diskId+fileId + foreach (WixFileRow patchFileRow in patchFileTable.Rows) + { + int diskId = patchFileRow.DiskId; + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) + { + mediaFileRows = new RowDictionary(); + patchMediaFileRows.Add(diskId, mediaFileRows); + } + + mediaFileRows.Add(patchFileRow); + } + + var patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]); + patchMediaRows = new RowDictionary(patchMediaTable); + } + + // Index paired transforms by name without their "#" prefix. + var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name.Substring(1), s => s.Data); + + foreach (var substorage in this.SubStorages) + { + if (substorage.Name.StartsWith("#")) + { + // Skip paired transforms. + continue; + } + + var mainTransform = substorage.Data; + var mainWixFiles = new RowDictionary(mainTransform.Tables["WixFile"]); + var mainMsiFileHashIndex = new RowDictionary(mainTransform.Tables["MsiFileHash"]); + + var mainFileTable = mainTransform.Tables["File"]; + var pairedTransform = pairedTransforms[substorage.Name]; + + // copy Media.LastSequence and index the MsiFileHash table if it exists. + if (copyFromPatch) + { + var pairedMediaTable = pairedTransform.Tables["Media"]; + foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) + { + var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); + pairedMediaRow.Fields[1] = patchMediaRow.Fields[1]; + } + + if (null != mainMsiFileHashTable) + { + mainMsiFileHashIndex = new RowDictionary(mainMsiFileHashTable); + } + + // Validate file row changes for keypath-related issues + this.ValidateFileRowChanges(mainTransform); + } + + if (null == mainFileTable) + { + continue; + } + + // Index File table of pairedTransform + var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); + + foreach (FileRow mainFileRow in mainFileTable.Rows) + { + if (RowOperation.Delete == mainFileRow.Operation) + { + continue; + } + else if (RowOperation.None == mainFileRow.Operation) + { + continue; + } + + var mainWixFileRow = mainWixFiles.Get(mainFileRow.File); + + if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes. + { + var objectField = (ObjectField)mainWixFileRow.Fields[6]; + var pairedFileRow = pairedFileRows.Get(mainFileRow.File); + + // If the file is new, we always need to add it to the patch. + if (mainFileRow.Operation != RowOperation.Add) + { + // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. + if (null == objectField.PreviousData) + { + if (mainFileRow.Operation == RowOperation.None) + { + continue; + } + } + else + { + // TODO: should this entire condition be placed in the binder file manager? + if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) && + !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString())) + { + // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. + mainFileRow.Operation = RowOperation.Modify; + if (null != pairedFileRow) + { + // Always patch-added, but never non-compressed. + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = RowOperation.Modify; + } + } + else + { + // The File is same. We need mark all the attributes as unchanged. + mainFileRow.Operation = RowOperation.None; + foreach (var field in mainFileRow.Fields) + { + field.Modified = false; + } + + if (null != pairedFileRow) + { + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Fields[6].Modified = false; + pairedFileRow.Operation = RowOperation.None; + } + continue; + } + } + } + else if (null != pairedFileRow) // RowOperation.Add + { + // Always patch-added, but never non-compressed. + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = RowOperation.Add; + } + } + + // index patch files by diskId+fileId + int diskId = mainWixFileRow.DiskId; + + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) + { + mediaFileRows = new RowDictionary(); + patchMediaFileRows.Add(diskId, mediaFileRows); + } + + var fileId = mainFileRow.File; + var patchFileRow = mediaFileRows.Get(fileId); + if (copyToPatch) + { + if (null == patchFileRow) + { + var patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); + patchActualFileRow.CopyFrom(mainFileRow); + + patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); + patchFileRow.CopyFrom(mainWixFileRow); + + mediaFileRows.Add(patchFileRow); + + allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right? + } + else + { + // TODO: confirm the rest of data is identical? + + // make sure Source is same. Otherwise we are silently ignoring a file. + if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source)); + } + + // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. + patchFileRow.AppendPreviousDataFrom(mainWixFileRow); + } + } + //else + //{ + // // copy data from the patch back to the transform + // if (null != patchFileRow) + // { + // var pairedFileRow = pairedFileRows.Get(fileId); + // for (var i = 0; i < patchFileRow.Fields.Length; i++) + // { + // var patchValue = patchFileRow[i] == null ? String.Empty : patchFileRow.FieldAsString(i); + // var mainValue = mainFileRow[i] == null ? String.Empty : mainFileRow.FieldAsString(i); + + // if (1 == i) + // { + // // File.Component_ changes should not come from the shared file rows + // // that contain the file information as each individual transform might + // // have different changes (or no changes at all). + // } + // // File.Attributes should not changed for binary deltas + // else if (6 == i) + // { + // if (null != patchFileRow.Patch) + // { + // // File.Attribute should not change for binary deltas + // pairedFileRow.Attributes = mainFileRow.Attributes; + // mainFileRow.Fields[i].Modified = false; + // } + // } + // // File.Sequence is updated in pairedTransform, not mainTransform + // else if (7 == i) + // { + // // file sequence is updated in Patch table instead of File table for delta patches + // if (null != patchFileRow.Patch) + // { + // pairedFileRow.Fields[i].Modified = false; + // } + // else + // { + // pairedFileRow[i] = patchFileRow[i]; + // pairedFileRow.Fields[i].Modified = true; + // } + // mainFileRow.Fields[i].Modified = false; + // } + // else if (patchValue != mainValue) + // { + // mainFileRow[i] = patchFileRow[i]; + // mainFileRow.Fields[i].Modified = true; + // if (mainFileRow.Operation == RowOperation.None) + // { + // mainFileRow.Operation = RowOperation.Modify; + // } + // } + // } + + // // copy MsiFileHash row for this File + // if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) + // { + // patchHashRow = patchFileRow.Hash; + // } + + // if (null != patchHashRow) + // { + // var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); + // var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); + // for (var i = 0; i < patchHashRow.Fields.Length; i++) + // { + // mainHashRow[i] = patchHashRow[i]; + // if (i > 1) + // { + // // assume all hash fields have been modified + // mainHashRow.Fields[i].Modified = true; + // } + // } + + // // assume the MsiFileHash operation follows the File one + // mainHashRow.Operation = mainFileRow.Operation; + // } + + // // copy MsiAssemblyName rows for this File + // List patchAssemblyNameRows = patchFileRow.AssemblyNames; + // if (null != patchAssemblyNameRows) + // { + // var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); + // foreach (var patchAssemblyNameRow in patchAssemblyNameRows) + // { + // // Copy if there isn't an identical modified/added row already in the transform. + // var foundMatchingModifiedRow = false; + // foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows) + // { + // if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) + // { + // foundMatchingModifiedRow = true; + // break; + // } + // } + + // if (!foundMatchingModifiedRow) + // { + // var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); + // for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++) + // { + // mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; + // } + + // // assume value field has been modified + // mainAssemblyNameRow.Fields[2].Modified = true; + // mainAssemblyNameRow.Operation = mainFileRow.Operation; + // } + // } + // } + + // // Add patch header for this file + // if (null != patchFileRow.Patch) + // { + // // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. + // this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); + // this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); + + // // Add to Patch table + // var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); + // if (0 == patchTable.Rows.Count) + // { + // patchTable.Operation = TableOperation.Add; + // } + + // var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); + // patchRow[0] = patchFileRow.File; + // patchRow[1] = patchFileRow.Sequence; + + // var patchFile = new FileInfo(patchFileRow.Source); + // patchRow[2] = (int)patchFile.Length; + // patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; + + // var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; + // if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) + // { + // streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); + + // var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); + // if (0 == patchHeadersTable.Rows.Count) + // { + // patchHeadersTable.Operation = TableOperation.Add; + // } + + // var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); + // patchHeadersRow[0] = streamName; + // patchHeadersRow[1] = patchFileRow.Patch; + // patchRow[5] = streamName; + // patchHeadersRow.Operation = RowOperation.Add; + // } + // else + // { + // patchRow[4] = patchFileRow.Patch; + // } + // patchRow.Operation = RowOperation.Add; + // } + // } + // else + // { + // // TODO: throw because all transform rows should have made it into the patch + // } + //} + } + } +#endif + this.FileFacades = allFileRows; + } + + /// + /// Adds the PatchFiles action to the sequence table if it does not already exist. + /// + /// The sequence table to check or modify. + /// The primary authoring transform. + /// The secondary patch transform. + /// The file row that contains information about the patched file. + private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow) + { + var tableName = table.ToString(); + + // Find/add PatchFiles action (also determine sequence for it). + // Search mainTransform first, then pairedTransform (pairedTransform overrides). + var hasPatchFilesAction = false; + var installFilesSequence = 0; + var duplicateFilesSequence = 0; + + TestSequenceTableForPatchFilesAction( + mainTransform.Tables[tableName], + ref hasPatchFilesAction, + ref installFilesSequence, + ref duplicateFilesSequence); + TestSequenceTableForPatchFilesAction( + pairedTransform.Tables[tableName], + ref hasPatchFilesAction, + ref installFilesSequence, + ref duplicateFilesSequence); + if (!hasPatchFilesAction) + { + WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionTuple); + + var sequence = patchFilesActionTuple.Sequence; + + // Test for default sequence value's appropriateness + if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence)) + { + if (0 != duplicateFilesSequence) + { + if (duplicateFilesSequence < installFilesSequence) + { + throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action)); + } + else + { + sequence = (duplicateFilesSequence + installFilesSequence) / 2; + if (installFilesSequence == sequence || duplicateFilesSequence == sequence) + { + throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action)); + } + } + } + else + { + sequence = installFilesSequence + 1; + } + } + + var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); + if (0 == sequenceTable.Rows.Count) + { + sequenceTable.Operation = TableOperation.Add; + } + + var patchAction = sequenceTable.CreateRow(null); + patchAction[0] = patchFilesActionTuple.Action; + patchAction[1] = patchFilesActionTuple.Condition; + patchAction[2] = sequence; + patchAction.Operation = RowOperation.Add; + } + } + + /// + /// Tests sequence table for PatchFiles and associated actions + /// + /// The table to test. + /// Set to true if PatchFiles action is found. Left unchanged otherwise. + /// Set to sequence value of InstallFiles action if found. Left unchanged otherwise. + /// Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise. + private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence) + { + if (null != sequenceTable) + { + foreach (var row in sequenceTable.Rows) + { + var actionName = row.FieldAsString(0); + switch (actionName) + { + case "PatchFiles": + hasPatchFilesAction = true; + break; + + case "InstallFiles": + installFilesSequence = row.FieldAsInteger(2); + break; + + case "DuplicateFiles": + duplicateFilesSequence = row.FieldAsInteger(2); + break; + } + } + } + } + + /// + /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. + /// + /// The output to validate. + private void ValidateFileRowChanges(WindowsInstallerData transform) + { + var componentTable = transform.Tables["Component"]; + var fileTable = transform.Tables["File"]; + + // There's no sense validating keypaths if the transform has no component or file table + if (componentTable == null || fileTable == null) + { + return; + } + + var componentKeyPath = new Dictionary(componentTable.Rows.Count); + + // Index the Component table for non-directory & non-registry key paths. + foreach (var row in componentTable.Rows) + { + var keyPath = row.FieldAsString(5); + if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) + { + componentKeyPath.Add(row.FieldAsString(0), keyPath); + } + } + + var componentWithChangedKeyPath = new Dictionary(); + var componentWithNonKeyPathChanged = new Dictionary(); + // Verify changes in the file table, now that file diffing has occurred + foreach (FileRow row in fileTable.Rows) + { + if (RowOperation.Modify != row.Operation) + { + continue; + } + + var fileId = row.FieldAsString(0); + var componentId = row.FieldAsString(1); + + // If this file is the keypath of a component + if (componentKeyPath.ContainsValue(fileId)) + { + if (!componentWithChangedKeyPath.ContainsKey(componentId)) + { + componentWithChangedKeyPath.Add(componentId, fileId); + } + } + else + { + if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) + { + componentWithNonKeyPathChanged.Add(componentId, fileId); + } + } + } + + foreach (var componentFile in componentWithNonKeyPathChanged) + { + // Make sure all changes to non keypath files also had a change in the keypath. + if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath)) + { + this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath)); + } + } + } + + private bool CompareFiles(string targetFile, string updatedFile) + { + bool? compared = null; + foreach (var extension in this.Extensions) + { + compared = extension.CompareFiles(targetFile, updatedFile); + + if (compared.HasValue) + { + break; + } + } + + if (!compared.HasValue) + { + throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. + } + + return compared.Value; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs index 92ddad6f..1aa4065e 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs @@ -9,15 +9,22 @@ namespace WixToolset.Core.WindowsInstaller.Bind using WixToolset.Data; using WixToolset.Data.Tuples; using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; internal class LoadTableDefinitionsCommand { - public LoadTableDefinitionsCommand(IntermediateSection section) => this.Section = section; - - public TableDefinitionCollection TableDefinitions { get; private set; } + public LoadTableDefinitionsCommand(IntermediateSection section, IEnumerable backendExtensions) + { + this.Section = section; + this.BackendExtensions = backendExtensions; + } private IntermediateSection Section { get; } + private IEnumerable BackendExtensions { get; } + + public TableDefinitionCollection TableDefinitions { get; private set; } + public TableDefinitionCollection Execute() { var tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandardInternal.GetTableDefinitions()); @@ -28,6 +35,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind tableDefinitions.Add(customTableDefinition); } + foreach (var backendExtension in this.BackendExtensions) + { + foreach (var tableDefinition in backendExtension.TableDefinitions) + { + tableDefinitions.Add(tableDefinition); + } + } + this.TableDefinitions = tableDefinitions; return this.TableDefinitions; } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs index 8c11555e..b90aecd1 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -277,7 +277,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind using (Record record = new Record(1)) { - record.SetString(1, file.File.Id.Id); + record.SetString(1, file.Id); view.Execute(record); } @@ -288,7 +288,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind 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); + recordUpdate.SetInteger(1, file.Sequence); // Update the file attributes to match the compression specified // on the Merge element or on the Package element. @@ -300,12 +300,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind attributes = recordUpdate.GetInteger(2); } - if ((file.File.Attributes & FileTupleAttributes.Compressed) == FileTupleAttributes.Compressed) + if (file.Compressed) { attributes |= WindowsInstallerConstants.MsidbFileAttributesCompressed; attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; } - else if ((file.File.Attributes & FileTupleAttributes.Uncompressed) == FileTupleAttributes.Uncompressed) + else if (file.Uncompressed) { attributes |= WindowsInstallerConstants.MsidbFileAttributesNoncompressed; attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs b/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs new file mode 100644 index 00000000..5ada29de --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs @@ -0,0 +1,246 @@ +// 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.Bind +{ + using System; + using System.Collections; + using System.Globalization; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + + internal class PatchTransform + { + public PatchTransform(string baseline, WindowsInstallerData transform) + { + this.Baseline = baseline; + this.Transform = transform; + } + + public string Baseline { get; } + + public WindowsInstallerData Transform { get; } + + /// + /// Validates that the differences in the transform are valid for patch transforms. + /// + public void Validate() + { +#if TODO_PATCHING + // Changing the ProdocutCode in a patch transform is not recommended. + Table propertyTable = this.Transform.Tables["Property"]; + if (null != propertyTable) + { + foreach (Row row in propertyTable.Rows) + { + // Only interested in modified rows; fast check. + if (RowOperation.Modify == row.Operation) + { + if (0 == String.CompareOrdinal("ProductCode", (string)row[0])) + { + this.OnMessage(WixWarnings.MajorUpgradePatchNotRecommended()); + } + } + } + } + + // If there is nothing in the component table we can return early because the remaining checks are component based. + Table componentTable = this.Transform.Tables["Component"]; + if (null == componentTable) + { + return; + } + + // Index Feature table row operations + Table featureTable = this.Transform.Tables["Feature"]; + Table featureComponentsTable = this.Transform.Tables["FeatureComponents"]; + Hashtable featureOps = null; + if (null != featureTable) + { + int capacity = featureTable.Rows.Count; + featureOps = new Hashtable(capacity); + + foreach (Row row in featureTable.Rows) + { + featureOps[(string)row[0]] = row.Operation; + } + } + else + { + featureOps = new Hashtable(); + } + + // Index Component table and check for keypath modifications + Hashtable deletedComponent = new Hashtable(); + Hashtable componentKeyPath = new Hashtable(); + foreach (Row row in componentTable.Rows) + { + string id = row.Fields[0].Data.ToString(); + string keypath = (null == row.Fields[5].Data) ? String.Empty : row.Fields[5].Data.ToString(); + + componentKeyPath.Add(id, keypath); + if (RowOperation.Delete == row.Operation) + { + deletedComponent.Add(id, row); + } + else if (RowOperation.Modify == row.Operation) + { + if (row.Fields[1].Modified) + { + // Changing the guid of a component is equal to deleting the old one and adding a new one. + deletedComponent.Add(id, row); + } + + // If the keypath is modified its an error + if (row.Fields[5].Modified) + { + this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, id, this.transformPath)); + } + } + } + + // Verify changes in the file table + Table fileTable = this.Transform.Tables["File"]; + if (null != fileTable) + { + Hashtable componentWithChangedKeyPath = new Hashtable(); + foreach (Row row in fileTable.Rows) + { + if (RowOperation.None != row.Operation) + { + string fileId = row.Fields[0].Data.ToString(); + string componentId = row.Fields[1].Data.ToString(); + + // If this file is the keypath of a component + if (String.Equals((string)componentKeyPath[componentId], fileId, StringComparison.Ordinal)) + { + if (row.Fields[2].Modified) + { + // You cant change the filename of a file that is the keypath of a component. + this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, componentId, this.transformPath)); + } + + if (!componentWithChangedKeyPath.ContainsKey(componentId)) + { + componentWithChangedKeyPath.Add(componentId, fileId); + } + } + + if (RowOperation.Delete == row.Operation) + { + // If the file is removed from a component that is not deleted. + if (!deletedComponent.ContainsKey(componentId)) + { + bool foundRemoveFileEntry = false; + string filename = Common.GetName((string)row[2], false, true); + + Table removeFileTable = this.Transform.Tables["RemoveFile"]; + if (null != removeFileTable) + { + foreach (Row removeFileRow in removeFileTable.Rows) + { + if (RowOperation.Delete == removeFileRow.Operation) + { + continue; + } + + if (componentId == (string)removeFileRow[1]) + { + // Check if there is a RemoveFile entry for this file + if (null != removeFileRow[2]) + { + string removeFileName = Common.GetName((string)removeFileRow[2], false, true); + + // Convert the MSI format for a wildcard string to Regex format. + removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); + + Regex regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + if (regex.IsMatch(filename)) + { + foundRemoveFileEntry = true; + break; + } + } + } + } + } + + if (!foundRemoveFileEntry) + { + this.OnMessage(WixWarnings.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId)); + } + } + } + } + } + } + + if (0 < deletedComponent.Count) + { + // Index FeatureComponents table. + Hashtable featureComponents = new Hashtable(); + + if (null != featureComponentsTable) + { + foreach (Row row in featureComponentsTable.Rows) + { + ArrayList features; + string componentId = row.Fields[1].Data.ToString(); + + if (featureComponents.Contains(componentId)) + { + features = (ArrayList)featureComponents[componentId]; + } + else + { + features = new ArrayList(); + featureComponents.Add(componentId, features); + } + features.Add(row.Fields[0].Data.ToString()); + } + } + + // Check to make sure if a component was deleted, the feature was too. + foreach (DictionaryEntry entry in deletedComponent) + { + if (featureComponents.Contains(entry.Key.ToString())) + { + ArrayList features = (ArrayList)featureComponents[entry.Key.ToString()]; + foreach (string featureId in features) + { + if (!featureOps.ContainsKey(featureId) || RowOperation.Delete != (RowOperation)featureOps[featureId]) + { + // The feature was not deleted. + this.OnMessage(WixErrors.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, this.transformPath)); + } + } + } + } + } + + // Warn if new components are added to existing features + if (null != featureComponentsTable) + { + foreach (Row row in featureComponentsTable.Rows) + { + if (RowOperation.Add == row.Operation) + { + // Check if the feature is in the Feature table + string feature_ = (string)row[0]; + string component_ = (string)row[1]; + + // Features may not be present if not referenced + if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_]) + { + this.OnMessage(WixWarnings.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, this.transformPath)); + } + } + } + } +#endif + throw new NotImplementedException(); + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs index 373ada38..13d47215 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs @@ -86,14 +86,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind // setup up the query record and find the appropriate file in the // previously executed file view - fileQueryRecord[1] = facade.File.Id.Id; + fileQueryRecord[1] = facade.Id; fileView.Execute(fileQueryRecord); using (Record fileRecord = fileView.Fetch()) { if (null == fileRecord) { - throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.Id.Id)); + throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.SourceLineNumber, facade.Id)); } relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); @@ -102,7 +102,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // finally put together the base media layout path and the relative file layout path var fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); - var transfer = this.BackendHelper.CreateFileTransfer(facade.File.Source.Path, fileLayoutPath, false, facade.File.SourceLineNumbers); + var transfer = this.BackendHelper.CreateFileTransfer(facade.SourcePath, fileLayoutPath, false, facade.SourceLineNumber); fileTransfers.Add(transfer); // Track the location where the cabinet will be placed. If the transfer is @@ -110,7 +110,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // because if the source and destination of the transfer is the same, we // don't want to clean the file because we'd be deleting the original // (and that would be bad). - var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.File.SourceLineNumbers); + var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.SourceLineNumber); tracked.Clean = !transfer.Redundant; trackedFiles.Add(tracked); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs index e9b0d612..749f9ac0 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs @@ -11,24 +11,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility.Services; + /// + /// Set sequence numbers for all the actions and create tuples in the output object. + /// internal class SequenceActionsCommand { - public SequenceActionsCommand(IntermediateSection section) + public SequenceActionsCommand(IMessaging messaging, IntermediateSection section) { + this.Messaging = messaging; this.Section = section; this.RelativeActionsForActions = new Dictionary(); } + private IMessaging Messaging { get; } + private IntermediateSection Section { get; } private Dictionary RelativeActionsForActions { get; } - public IMessaging Messaging { private get; set; } - - /// - /// Set sequence numbers for all the actions and create tuples in the output object. - /// public void Execute() { var requiredActionTuples = new Dictionary(); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs index 1f2a22d9..81d46b41 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -59,27 +59,27 @@ namespace WixToolset.Core.WindowsInstaller.Bind FileInfo fileInfo = null; try { - fileInfo = new FileInfo(facade.File.Source.Path); + fileInfo = new FileInfo(facade.SourcePath); } catch (ArgumentException) { - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.File.SourceLineNumbers, facade.File.Source.Path)); + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); return; } catch (PathTooLongException) { - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.File.SourceLineNumbers, facade.File.Source.Path)); + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); return; } catch (NotSupportedException) { - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.File.SourceLineNumbers, facade.File.Source.Path)); + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); return; } if (!fileInfo.Exists) { - this.Messaging.Write(ErrorMessages.CannotFindFile(facade.File.SourceLineNumbers, facade.File.Id.Id, facade.File.Name, facade.File.Source.Path)); + this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath)); return; } @@ -87,10 +87,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind { if (Int32.MaxValue < fileStream.Length) { - throw new WixException(ErrorMessages.FileTooLarge(facade.File.SourceLineNumbers, facade.File.Source.Path)); + throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath)); } - facade.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); + facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); } string version = null; @@ -103,7 +103,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND { - throw new WixException(ErrorMessages.FileNotFound(facade.File.SourceLineNumbers, fileInfo.FullName)); + throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); } else { @@ -118,7 +118,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { // not overwriting hash, so don't do the rest of these options. } - else if (null != facade.File.Version) + else if (null != facade.Version) { // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up. @@ -127,16 +127,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind // // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. - if (!this.FileFacades.Any(r => facade.File.Version.Equals(r.File.Id.Id, StringComparison.Ordinal))) + if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) { - this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.File.SourceLineNumbers, facade.File.Version, facade.File.Id.Id)); + this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); } } else { - if (null != facade.File.Language) + if (null != facade.Language) { - this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.File.SourceLineNumbers, facade.File.Language, facade.File.Id.Id)); + this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); } int[] hash; @@ -148,7 +148,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND { - throw new WixException(ErrorMessages.FileNotFound(facade.File.SourceLineNumbers, fileInfo.FullName)); + throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); } else { @@ -158,7 +158,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (null == facade.Hash) { - facade.Hash = new MsiFileHashTuple(facade.File.SourceLineNumbers, facade.File.Id); + facade.Hash = new MsiFileHashTuple(facade.SourceLineNumber, facade.Identifier); this.Section.Tuples.Add(facade.Hash); } @@ -173,11 +173,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind { // If no version was provided by the user, use the version from the file itself. // This is the most common case. - if (String.IsNullOrEmpty(facade.File.Version)) + if (String.IsNullOrEmpty(facade.Version)) { - facade.File.Version = version; + facade.Version = version; } - else if (!this.FileFacades.Any(r => facade.File.Version.Equals(r.File.Id.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. + else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. { // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching // the version value). We didn't find it so, we will override the default version they provided with the actual @@ -188,41 +188,41 @@ namespace WixToolset.Core.WindowsInstaller.Bind // // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. // That's typically even more rare than companion files so again, no index, just search. - facade.File.Version = version; + facade.Version = version; } - if (!String.IsNullOrEmpty(facade.File.Language) && String.IsNullOrEmpty(language)) + if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language)) { - this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.File.SourceLineNumbers, facade.File.Language, facade.File.Id.Id)); + this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); } else // override the default provided by the user (usually nothing) with the actual language from the file itself. { - facade.File.Language = language; + facade.Language = language; } // Populate the binder variables for this file information if requested. if (null != this.VariableCache) { - if (!String.IsNullOrEmpty(facade.File.Version)) + if (!String.IsNullOrEmpty(facade.Version)) { - var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.File.Id.Id); - this.VariableCache[key] = facade.File.Version; + var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.Id); + this.VariableCache[key] = facade.Version; } - if (!String.IsNullOrEmpty(facade.File.Language)) + if (!String.IsNullOrEmpty(facade.Language)) { - var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.File.Id.Id); - this.VariableCache[key] = facade.File.Language; + var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.Id); + this.VariableCache[key] = facade.Language; } } } // If this is a CLR assembly, load the assembly and get the assembly name information - if (AssemblyType.DotNetAssembly == facade.Assembly?.Type) + if (AssemblyType.DotNetAssembly == facade.AssemblyType) { try { - var assemblyName = AssemblyNameReader.ReadAssembly(facade.File.SourceLineNumbers, fileInfo.FullName, version); + var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version); this.SetMsiAssemblyName(assemblyNameTuples, facade, "name", assemblyName.Name); this.SetMsiAssemblyName(assemblyNameTuples, facade, "culture", assemblyName.Culture); @@ -242,9 +242,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind { this.SetMsiAssemblyName(assemblyNameTuples, facade, "publicKeyToken", assemblyName.PublicKeyToken); } - else if (facade.Assembly.ApplicationFileRef == null) + else if (facade.AssemblyApplicationFileRef == null) { - throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.File.SourceLineNumbers, fileInfo.FullName, facade.File.ComponentRef)); + throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); } if (!String.IsNullOrEmpty(assemblyName.FileVersion)) @@ -255,7 +255,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // add the assembly name to the information cache if (null != this.VariableCache) { - this.VariableCache[$"assemblyfullname.{facade.File.Id.Id}"] = assemblyName.GetFullName(); + this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); } } catch (WixException e) @@ -263,20 +263,20 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.Messaging.Write(e.Error); } } - else if (AssemblyType.Win32Assembly == facade.Assembly?.Type) + else if (AssemblyType.Win32Assembly == facade.AssemblyType) { // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through // all files like this. Even though this is a rare case it looks like we might be able to index the // file earlier. - var fileManifest = this.FileFacades.FirstOrDefault(r => r.File.Id.Id.Equals(facade.Assembly.ManifestFileRef, StringComparison.Ordinal)); + var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal)); if (null == fileManifest) { - this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.File.SourceLineNumbers, facade.File.Id.Id, facade.Assembly.ManifestFileRef)); + this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef)); } try { - var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.File.SourceLineNumbers, fileManifest.File.Source.Path); + var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); if (!String.IsNullOrEmpty(assemblyName.Name)) { @@ -315,41 +315,41 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// create a new row. /// /// MsiAssemblyName table. - /// FileFacade containing the assembly read for the MsiAssemblyName row. + /// FileFacade containing the assembly read for the MsiAssemblyName row. /// MsiAssemblyName name. /// MsiAssemblyName value. - private void SetMsiAssemblyName(Dictionary assemblyNameTuples, FileFacade file, string name, string value) + private void SetMsiAssemblyName(Dictionary assemblyNameTuples, FileFacade facade, string name, string value) { // check for null value (this can occur when grabbing the file version from an assembly without one) if (String.IsNullOrEmpty(value)) { - this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.ComponentRef, name)); + this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name)); } else { // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail. - if ("name" == name && AssemblyType.DotNetAssembly == file.Assembly.Type && - String.IsNullOrEmpty(file.Assembly.ApplicationFileRef) && - !String.Equals(Path.GetFileNameWithoutExtension(file.File.Name), value, StringComparison.OrdinalIgnoreCase)) + if ("name" == name && AssemblyType.DotNetAssembly == facade.AssemblyType && + String.IsNullOrEmpty(facade.AssemblyApplicationFileRef) && + !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase)) { - this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.Name), value)); + this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value)); } // override directly authored value - var lookup = String.Concat(file.File.ComponentRef, "/", name); + var lookup = String.Concat(facade.ComponentRef, "/", name); if (!assemblyNameTuples.TryGetValue(lookup, out var assemblyNameRow)) { - assemblyNameRow = new MsiAssemblyNameTuple(file.File.SourceLineNumbers); - assemblyNameRow.ComponentRef = file.File.ComponentRef; + assemblyNameRow = new MsiAssemblyNameTuple(facade.SourceLineNumber); + assemblyNameRow.ComponentRef = facade.ComponentRef; assemblyNameRow.Name = name; assemblyNameRow.Value = value; - if (null == file.AssemblyNames) + if (null == facade.AssemblyNames) { - file.AssemblyNames = new List(); + facade.AssemblyNames = new List(); } - file.AssemblyNames.Add(assemblyNameRow); + facade.AssemblyNames.Add(assemblyNameRow); this.Section.Tuples.Add(assemblyNameRow); } @@ -357,7 +357,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (this.VariableCache != null) { - var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, file.File.Id.Id).ToLowerInvariant(); + var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant(); this.VariableCache[key] = value; } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs index f9e3bd5a..ae872f45 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs @@ -33,7 +33,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Order by Component to group the files by directory. var optimized = this.OptimizedFileFacades(); - foreach (var fileId in optimized.Select(f => f.File.Id.Id)) + foreach (var fileId in optimized.Select(f => f.Id)) { var fileRow = fileRows.Get(fileId); fileRow.Sequence = ++lastSequence; @@ -41,13 +41,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else { - int lastSequence = 0; + var lastSequence = 0; MediaRow mediaRow = null; - Dictionary> patchGroups = new Dictionary>(); + var patchGroups = new Dictionary>(); // sequence the non-patch-added files var optimized = this.OptimizedFileFacades(); - foreach (FileFacade facade in optimized) + foreach (var facade in optimized) { if (null == mediaRow) { @@ -64,19 +64,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind mediaRow = mediaRows.Get(facade.DiskId); } - if (facade.File.PatchGroup.HasValue) + if (facade.PatchGroup.HasValue) { - if (patchGroups.TryGetValue(facade.File.PatchGroup.Value, out var patchGroup)) + if (patchGroups.TryGetValue(facade.PatchGroup.Value, out var patchGroup)) { patchGroup = new List(); - patchGroups.Add(facade.File.PatchGroup.Value, patchGroup); + patchGroups.Add(facade.PatchGroup.Value, patchGroup); } patchGroup.Add(facade); } else { - var fileRow = fileRows.Get(facade.File.Id.Id); + var fileRow = fileRows.Get(facade.Id); fileRow.Sequence = ++lastSequence; } } @@ -102,7 +102,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind mediaRow = mediaRows.Get(facade.DiskId); } - var fileRow = fileRows.Get(facade.File.Id.Id); + var fileRow = fileRows.Get(facade.Id); fileRow.Sequence = ++lastSequence; } } @@ -119,7 +119,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // TODO: Sort these facades even smarter by directory path and component id // and maybe file size or file extension and other creative ideas to // get optimal install speed out of MSI. - return this.FileFacades.OrderBy(f => f.File.ComponentRef); + return this.FileFacades.OrderBy(f => f.ComponentRef); } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Data/tables.xml b/src/WixToolset.Core.WindowsInstaller/Data/tables.xml index e4b5e954..7cd1767b 100644 --- a/src/WixToolset.Core.WindowsInstaller/Data/tables.xml +++ b/src/WixToolset.Core.WindowsInstaller/Data/tables.xml @@ -156,6 +156,10 @@ minValue="0" maxValue="32767" description="Integer containing bit flags representing file attributes (with the decimal value of each bit position in parentheses)"/> + + certificates = new Dictionary(); + var certificates = new Dictionary(); // Reset the in-memory tables for this new database - Table digitalSignatureTable = new Table(this.TableDefinitions["MsiDigitalSignature"]); - Table digitalCertificateTable = new Table(this.TableDefinitions["MsiDigitalCertificate"]); + var digitalSignatureTable = new Table(this.TableDefinitions["MsiDigitalSignature"]); + var digitalCertificateTable = new Table(this.TableDefinitions["MsiDigitalCertificate"]); // If any digital signature records exist that are not of the media type, preserve them if (database.TableExists("MsiDigitalSignature")) { - using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) + using (var digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) { - foreach (Record digitalSignatureRecord in digitalSignatureView.Records) + foreach (var digitalSignatureRecord in digitalSignatureView.Records) { Row digitalSignatureRow = null; digitalSignatureRow = digitalSignatureTable.CreateRow(null); - string table = digitalSignatureRecord.GetString(0); - string signObject = digitalSignatureRecord.GetString(1); + var table = digitalSignatureRecord.GetString(0); + var signObject = digitalSignatureRecord.GetString(1); digitalSignatureRow[0] = table; digitalSignatureRow[1] = signObject; @@ -75,16 +75,16 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe if (false == digitalSignatureRecord.IsNull(3)) { // Export to a file, because the MSI API's require us to provide a file path on disk - string hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature"); - string hashFileName = string.Concat(table, ".", signObject, ".bin"); + var hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature"); + var hashFileName = String.Concat(table, ".", signObject, ".bin"); Directory.CreateDirectory(hashPath); hashPath = Path.Combine(hashPath, hashFileName); - using (FileStream fs = File.Create(hashPath)) + using (var fs = File.Create(hashPath)) { int bytesRead; - byte[] buffer = new byte[1024 * 4]; + var buffer = new byte[1024 * 4]; while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) { @@ -101,21 +101,21 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe // If any digital certificates exist, extract and preserve them if (database.TableExists("MsiDigitalCertificate")) { - using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) + using (var digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) { - foreach (Record digitalCertificateRecord in digitalCertificateView.Records) + foreach (var digitalCertificateRecord in digitalCertificateView.Records) { - string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate + var certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate // Export to a file, because the MSI API's require us to provide a file path on disk - string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); + var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); Directory.CreateDirectory(certPath); - certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer")); + certPath = Path.Combine(certPath, String.Concat(certificateId, ".cer")); - using (FileStream fs = File.Create(certPath)) + using (var fs = File.Create(certPath)) { int bytesRead; - byte[] buffer = new byte[1024 * 4]; + var buffer = new byte[1024 * 4]; while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) { @@ -124,37 +124,37 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe } // Add it to our "add to MsiDigitalCertificate" table dictionary - Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); + var digitalCertificateRow = digitalCertificateTable.CreateRow(null); digitalCertificateRow[0] = certificateId; // Now set the file path on disk where this binary stream will be picked up at import time - digitalCertificateRow[1] = string.Concat(certificateId, ".cer"); + digitalCertificateRow[1] = String.Concat(certificateId, ".cer"); // Load the cert to get it's thumbprint - X509Certificate cert = X509Certificate.CreateFromCertFile(certPath); - X509Certificate2 cert2 = new X509Certificate2(cert); + var cert = X509Certificate.CreateFromCertFile(certPath); + var cert2 = new X509Certificate2(cert); certificates.Add(cert2.Thumbprint, certificateId); } } } - using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) + using (var mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) { - foreach (Record mediaRecord in mediaView.Records) + foreach (var mediaRecord in mediaView.Records) { X509Certificate2 cert2 = null; Row digitalSignatureRow = null; - string cabName = mediaRecord.GetString(4); // get the name of the cab + var cabName = mediaRecord.GetString(4); // get the name of the cab // If there is no cabinet or it's an internal cab, skip it. if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) { continue; } - string cabId = mediaRecord.GetString(1); // get the ID of the cab - string cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName); + var cabId = mediaRecord.GetString(1); // get the ID of the cab + var cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName); // If the cabs aren't there, throw an error but continue to catch the other errors if (!File.Exists(cabPath)) @@ -166,12 +166,12 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe try { // Get the certificate from the cab - X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); + var signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); cert2 = new X509Certificate2(signedFileCert); } catch (System.Security.Cryptography.CryptographicException e) { - uint HResult = unchecked((uint)Marshal.GetHRForException(e)); + var HResult = unchecked((uint)Marshal.GetHRForException(e)); // If the file has no cert, continue, but flag that we found at least one so we can later give a warning if (0x80092009 == HResult) // CRYPT_E_NO_MATCH @@ -197,26 +197,26 @@ namespace WixToolset.Core.WindowsInstaller.Inscribe if (!certificates.ContainsKey(cert2.Thumbprint)) { // generate a stable identifier - string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint); + var certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint); // Add it to our "add to MsiDigitalCertificate" table dictionary - Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); + var digitalCertificateRow = digitalCertificateTable.CreateRow(null); digitalCertificateRow[0] = certificateGeneratedId; // Export to a file, because the MSI API's require us to provide a file path on disk - string certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); + var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); Directory.CreateDirectory(certPath); - certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer")); + certPath = Path.Combine(certPath, String.Concat(cert2.Thumbprint, ".cer")); File.Delete(certPath); - using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) + using (var writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) { writer.Write(cert2.RawData); writer.Close(); } // Now set the file path on disk where this binary stream will be picked up at import time - digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer"); + digitalCertificateRow[1] = String.Concat(cert2.Thumbprint, ".cer"); certificates.Add(cert2.Thumbprint, certificateGeneratedId); } diff --git a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs index b6e72e11..8aa450bf 100644 --- a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs @@ -3,36 +3,74 @@ namespace WixToolset.Core.WindowsInstaller { using System; - using System.ComponentModel; + using System.Collections.Generic; using System.IO; - using WixToolset.Core.Native; + using System.Linq; + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Core.WindowsInstaller.Msi; using WixToolset.Core.WindowsInstaller.Unbind; using WixToolset.Data; - using WixToolset.Data.Bind; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; - using WixToolset.Ole32; + using WixToolset.Extensibility.Services; internal class MspBackend : IBackend { public IBindResult Bind(IBindContext context) { - throw new NotImplementedException(); - } + var messaging = context.ServiceProvider.GetService(); - public IDecompileResult Decompile(IDecompileContext context) - { - throw new NotImplementedException(); - } + var extensionManager = context.ServiceProvider.GetService(); - public bool Inscribe(IInscribeContext context) - { - throw new NotImplementedException(); + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + + // Create transforms named in patch transforms. + IEnumerable patchTransforms; + { + var command = new CreatePatchTransformsCommand(messaging, context.IntermediateRepresentation, context.IntermediateFolder); + patchTransforms = command.Execute(); + } + + // Enhance the intermediate by attaching the created patch transforms. + IEnumerable subStorages; + { + var command = new AttachPatchTransformsCommand(messaging, context.IntermediateRepresentation, patchTransforms); + subStorages = command.Execute(); + } + + // Create WindowsInstallerData with patch metdata and transforms as sub-storages + // Create MSP from WindowsInstallerData + using (var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null)) + { + command.Execute(); + + var result = context.ServiceProvider.GetService(); + result.FileTransfers = command.FileTransfers; + result.TrackedFiles = command.TrackedFiles; + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result, command.Wixout); + } + + return result; + } } + public IDecompileResult Decompile(IDecompileContext context) => throw new NotImplementedException(); + + public bool Inscribe(IInscribeContext context) => throw new NotImplementedException(); + public Intermediate Unbind(IUnbindContext context) { -#if REVISIT_FOR_PATCHING +#if TODO_PATCHING Output patch; // patch files are essentially database files (use a special flag to let the API know its a patch file) @@ -116,4 +154,4 @@ namespace WixToolset.Core.WindowsInstaller throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/WixToolset.Core.WindowsInstaller/MstBackend.cs b/src/WixToolset.Core.WindowsInstaller/MstBackend.cs index b64f417a..a6d86c10 100644 --- a/src/WixToolset.Core.WindowsInstaller/MstBackend.cs +++ b/src/WixToolset.Core.WindowsInstaller/MstBackend.cs @@ -12,7 +12,7 @@ namespace WixToolset.Core.WindowsInstaller { public IBindResult Bind(IBindContext context) { -#if REVISIT_FOR_PATCHING +#if TODO_PATCHING var command = new BindTransformCommand(); command.Extensions = context.Extensions; command.TempFilesLocation = context.IntermediateFolder; diff --git a/src/WixToolset.Core.WindowsInstaller/Ole32/Storage.cs b/src/WixToolset.Core.WindowsInstaller/Ole32/Storage.cs index c6a43bc4..541d899a 100644 --- a/src/WixToolset.Core.WindowsInstaller/Ole32/Storage.cs +++ b/src/WixToolset.Core.WindowsInstaller/Ole32/Storage.cs @@ -63,8 +63,8 @@ namespace WixToolset.Ole32 /// internal sealed class Storage : IDisposable { + private readonly IStorage storage; private bool disposed; - private IStorage storage; /// /// Instantiate a new Storage. diff --git a/src/WixToolset.Core.WindowsInstaller/Patch.cs b/src/WixToolset.Core.WindowsInstaller/Patch.cs index 6549e830..42cd7152 100644 --- a/src/WixToolset.Core.WindowsInstaller/Patch.cs +++ b/src/WixToolset.Core.WindowsInstaller/Patch.cs @@ -1,5 +1,7 @@ // 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. +#if DELETE + namespace WixToolset.Data { using System; @@ -50,7 +52,6 @@ namespace WixToolset.Data [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] public void AttachTransforms(List transforms) { -#if REVISIT_FOR_PATCHING // Track if at least one transform gets attached. bool attachedTransform = false; @@ -1229,8 +1230,10 @@ namespace WixToolset.Data } return pairedTransform; -#endif + throw new NotImplementedException(); } } } + +#endif diff --git a/src/WixToolset.Core.WindowsInstaller/PatchTransform.cs b/src/WixToolset.Core.WindowsInstaller/PatchTransform.cs index 0dc1e874..f58ca53f 100644 --- a/src/WixToolset.Core.WindowsInstaller/PatchTransform.cs +++ b/src/WixToolset.Core.WindowsInstaller/PatchTransform.cs @@ -1,5 +1,7 @@ // 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. +#if DELETE + namespace WixToolset { using System; @@ -50,7 +52,6 @@ namespace WixToolset /// public void Validate() { -#if REVISIT_FOR_PATCHING // Changing the ProdocutCode in a patch transform is not recommended. Table propertyTable = this.Transform.Tables["Property"]; if (null != propertyTable) @@ -261,8 +262,10 @@ namespace WixToolset } } } -#endif + throw new NotImplementedException(); } } } + +#endif \ No newline at end of file diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs index eca51caf..eea0fe23 100644 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs @@ -19,7 +19,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind public Intermediate Execute() { -#if REVISIT_FOR_PATCHING +#if TODO_PATCHING Output output; try diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs index bdf8d542..9261fda0 100644 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs @@ -4,6 +4,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind { using System; using System.Collections; + using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.IO; @@ -12,7 +13,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind using WixToolset.Core.WindowsInstaller.Msi; using WixToolset.Data; using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; using WixToolset.Extensibility.Services; internal class UnbindTransformCommand @@ -41,21 +41,21 @@ namespace WixToolset.Core.WindowsInstaller.Unbind public WindowsInstallerData Execute() { - WindowsInstallerData transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile)); + var transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile)); transform.Type = OutputType.Transform; // get the summary information table - using (SummaryInformation summaryInformation = new SummaryInformation(this.TransformFile)) + using (var summaryInformation = new SummaryInformation(this.TransformFile)) { - Table table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + var table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - for (int i = 1; 19 >= i; i++) + for (var i = 1; 19 >= i; i++) { - string value = summaryInformation.GetProperty(i); + var value = summaryInformation.GetProperty(i); if (0 < value.Length) { - Row row = table.CreateRow(transform.SourceLineNumbers); + var row = table.CreateRow(transform.SourceLineNumbers); row[0] = i; row[1] = value; } @@ -63,9 +63,9 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } // create a schema msi which hopefully matches the table schemas in the transform - WindowsInstallerData schemaOutput = new WindowsInstallerData(null); - string msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi"); - foreach (TableDefinition tableDefinition in this.TableDefinitions) + var schemaOutput = new WindowsInstallerData(null); + var msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi"); + foreach (var tableDefinition in this.TableDefinitions) { // skip unreal tables and the Patch table if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name) @@ -74,40 +74,40 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } } - Hashtable addedRows = new Hashtable(); + var addedRows = new Dictionary(); Table transformViewTable; // Bind the schema msi. this.GenerateDatabase(schemaOutput, msiDatabaseFile); // apply the transform to the database and retrieve the modifications - using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) + using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) { // apply the transform with the ViewTransform option to collect all the modifications msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); // unbind the database var unbindCommand = new UnbindDatabaseCommand(this.Messaging, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); - WindowsInstallerData transformViewOutput = unbindCommand.Execute(); + var transformViewOutput = unbindCommand.Execute(); // index the added and possibly modified rows (added rows may also appears as modified rows) transformViewTable = transformViewOutput.Tables["_TransformView"]; - Hashtable modifiedRows = new Hashtable(); - foreach (Row row in transformViewTable.Rows) + var modifiedRows = new Hashtable(); + foreach (var row in transformViewTable.Rows) { - string tableName = (string)row[0]; - string columnName = (string)row[1]; - string primaryKeys = (string)row[2]; + var tableName = (string)row[0]; + var columnName = (string)row[1]; + var primaryKeys = (string)row[2]; if ("INSERT" == columnName) { - string index = String.Concat(tableName, ':', primaryKeys); + var index = String.Concat(tableName, ':', primaryKeys); addedRows.Add(index, null); } else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row { - string index = String.Concat(tableName, ':', primaryKeys); + var index = String.Concat(tableName, ':', primaryKeys); modifiedRows[index] = row; } @@ -116,16 +116,16 @@ namespace WixToolset.Core.WindowsInstaller.Unbind // create placeholder rows for modified rows to make the transform insert the updated values when its applied foreach (Row row in modifiedRows.Values) { - string tableName = (string)row[0]; - string columnName = (string)row[1]; - string primaryKeys = (string)row[2]; + var tableName = (string)row[0]; + var columnName = (string)row[1]; + var primaryKeys = (string)row[2]; - string index = String.Concat(tableName, ':', primaryKeys); + var index = String.Concat(tableName, ':', primaryKeys); // ignore information for added rows - if (!addedRows.Contains(index)) + if (!addedRows.ContainsKey(index)) { - Table table = schemaOutput.Tables[tableName]; + var table = schemaOutput.Tables[tableName]; this.CreateRow(table, primaryKeys, true); } } @@ -135,7 +135,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind this.GenerateDatabase(schemaOutput, msiDatabaseFile); // apply the transform to the database and retrieve the modifications - using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) + using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) { try { @@ -158,26 +158,26 @@ namespace WixToolset.Core.WindowsInstaller.Unbind // unbind the database var unbindCommand = new UnbindDatabaseCommand(this.Messaging, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); - WindowsInstallerData output = unbindCommand.Execute(); + var output = unbindCommand.Execute(); // index all the rows to easily find modified rows - Hashtable rows = new Hashtable(); - foreach (Table table in output.Tables) + var rows = new Dictionary(); + foreach (var table in output.Tables) { - foreach (Row row in table.Rows) + foreach (var row in table.Rows) { rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); } } // process the _TransformView rows into transform rows - foreach (Row row in transformViewTable.Rows) + foreach (var row in transformViewTable.Rows) { - string tableName = (string)row[0]; - string columnName = (string)row[1]; - string primaryKeys = (string)row[2]; + var tableName = (string)row[0]; + var columnName = (string)row[1]; + var primaryKeys = (string)row[2]; - Table table = transform.EnsureTable(this.TableDefinitions[tableName]); + var table = transform.EnsureTable(this.TableDefinitions[tableName]); if ("CREATE" == columnName) // added table { @@ -185,7 +185,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } else if ("DELETE" == columnName) // deleted row { - Row deletedRow = this.CreateRow(table, primaryKeys, false); + var deletedRow = this.CreateRow(table, primaryKeys, false); deletedRow.Operation = RowOperation.Delete; } else if ("DROP" == columnName) // dropped table @@ -194,24 +194,24 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } else if ("INSERT" == columnName) // added row { - string index = String.Concat(tableName, ':', primaryKeys); - Row addedRow = (Row)rows[index]; + var index = String.Concat(tableName, ':', primaryKeys); + var addedRow = rows[index]; addedRow.Operation = RowOperation.Add; table.Rows.Add(addedRow); } else if (null != primaryKeys) // modified row { - string index = String.Concat(tableName, ':', primaryKeys); + var index = String.Concat(tableName, ':', primaryKeys); // the _TransformView table includes information for added rows // that looks like modified rows so it sometimes needs to be ignored - if (!addedRows.Contains(index)) + if (!addedRows.ContainsKey(index)) { - Row modifiedRow = (Row)rows[index]; + var modifiedRow = rows[index]; // mark the field as modified - int indexOfModifiedValue = -1; - for (int i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++i) + var indexOfModifiedValue = -1; + for (var i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++i) { if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal)) { @@ -231,7 +231,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind } else // added column { - ColumnDefinition column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal)); + var column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal)); column.Added = true; } } @@ -240,21 +240,6 @@ namespace WixToolset.Core.WindowsInstaller.Unbind return transform; } - private void GenerateDatabase(WindowsInstallerData output, string databaseFile) - { - var command = new GenerateDatabaseCommand(); - command.Extensions = Array.Empty(); - command.Output = output; - command.OutputPath = databaseFile; - command.KeepAddedColumns = true; - command.UseSubDirectory = false; - command.SuppressAddingValidationRows = true; - command.TableDefinitions = this.TableDefinitions; - command.IntermediateFolder = this.IntermediateFolder; - command.Codepage = -1; - command.Execute(); - } - /// /// Create a deleted or modified row. /// @@ -264,14 +249,14 @@ namespace WixToolset.Core.WindowsInstaller.Unbind /// The new row. private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields) { - Row row = table.CreateRow(null); + var row = table.CreateRow(null); - string[] primaryKeyParts = primaryKeys.Split('\t'); - int primaryKeyPartIndex = 0; + var primaryKeyParts = primaryKeys.Split('\t'); + var primaryKeyPartIndex = 0; - for (int i = 0; i < table.Definition.Columns.Length; i++) + for (var i = 0; i < table.Definition.Columns.Length; i++) { - ColumnDefinition columnDefinition = table.Definition.Columns[i]; + var columnDefinition = table.Definition.Columns[i]; if (columnDefinition.PrimaryKey) { @@ -294,8 +279,8 @@ namespace WixToolset.Core.WindowsInstaller.Unbind { if (null == this.EmptyFile) { - this.EmptyFile = Path.GetTempFileName() + ".empty"; - using (FileStream fileStream = File.Create(this.EmptyFile)) + this.EmptyFile = Path.Combine(this.IntermediateFolder, ".empty"); + using (var fileStream = File.Create(this.EmptyFile)) { } } @@ -311,5 +296,11 @@ namespace WixToolset.Core.WindowsInstaller.Unbind return row; } + + private void GenerateDatabase(WindowsInstallerData output, string databaseFile) + { + var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); + command.Execute(); + } } } diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs index 173404d7..f9cf4492 100644 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs +++ b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs @@ -1,11 +1,10 @@ -// 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. +// 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 { using System; using System.IO; using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; internal class WindowsInstallerBackendFactory : IBackendFactory { @@ -16,7 +15,7 @@ namespace WixToolset.Core.WindowsInstaller outputType = Path.GetExtension(outputFile); } - switch (outputType.ToLowerInvariant()) + switch (outputType?.ToLowerInvariant()) { case "module": case ".msm": diff --git a/src/WixToolset.Core/Bind/FileFacade.cs b/src/WixToolset.Core/Bind/FileFacade.cs index d631a3b5..7bfdb9bb 100644 --- a/src/WixToolset.Core/Bind/FileFacade.cs +++ b/src/WixToolset.Core/Bind/FileFacade.cs @@ -2,32 +2,146 @@ namespace WixToolset.Core.Bind { + using System; using System.Collections.Generic; + using WixToolset.Data; using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; public class FileFacade { public FileFacade(FileTuple file, AssemblyTuple assembly) { - this.File = file; - this.Assembly = assembly; + this.FileTuple = file; + this.AssemblyTuple = assembly; } public FileFacade(bool fromModule, FileTuple file) { this.FromModule = fromModule; - this.File = file; + this.FileTuple = file; + } + + internal FileFacade(FileRow row) + { + this.FromTransform = true; + this.FileRow = row; } public bool FromModule { get; } - public FileTuple File { get; } + public bool FromTransform { get; } + + private FileRow FileRow { get; } + + private FileTuple FileTuple { get; } + + private AssemblyTuple AssemblyTuple { get; } + + public string Id => this.FileRow == null ? this.FileTuple.Id.Id : this.FileRow.File; + + public Identifier Identifier => this.FileRow == null ? this.FileTuple.Id : throw new NotImplementedException(); + + public string ComponentRef => this.FileRow == null ? this.FileTuple.ComponentRef : this.FileRow.Component; + + public int DiskId + { + get => this.FileRow == null ? this.FileTuple.DiskId ?? 0 : this.FileRow.DiskId; + set + { + if (this.FileRow == null) + { + this.FileTuple.DiskId = value; + } + else + { + this.FileRow.DiskId = value; + } + } + } + + public string FileName => this.FileRow == null ? this.FileTuple.Name : this.FileRow.FileName; + + public int FileSize + { + get => this.FileRow == null ? this.FileTuple.FileSize : this.FileRow.FileSize; + set + { + if (this.FileRow == null) + { + this.FileTuple.FileSize = value; + } + else + { + this.FileRow.FileSize = value; + } + } + } + + public string Language + { + get => this.FileRow == null ? this.FileTuple.Language : this.FileRow.Language; + set + { + if (this.FileRow == null) + { + this.FileTuple.Language = value; + } + else + { + this.FileRow.Language = value; + } + } + } + + public int? PatchGroup => this.FileRow == null ? this.FileTuple.PatchGroup : null; + + public int Sequence + { + get => this.FileRow == null ? this.FileTuple.Sequence : this.FileRow.Sequence; + set + { + if (this.FileRow == null) + { + this.FileTuple.Sequence = value; + } + else + { + this.FileRow.Sequence = value; + } + } + } + + public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileTuple.SourceLineNumbers : this.FileRow.SourceLineNumbers; + + public string SourcePath => this.FileRow == null ? this.FileTuple.Source.Path : this.FileRow.Source; + + public bool Compressed => this.FileRow == null ? (this.FileTuple.Attributes & FileTupleAttributes.Compressed) == FileTupleAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; + + public bool Uncompressed => this.FileRow == null ? (this.FileTuple.Attributes & FileTupleAttributes.Uncompressed) == FileTupleAttributes.Uncompressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + + public string Version + { + get => this.FileRow == null ? this.FileTuple.Version : this.FileRow.Version; + set + { + if (this.FileRow == null) + { + this.FileTuple.Version = value; + } + else + { + this.FileRow.Version = value; + } + } + } - public AssemblyTuple Assembly { get; } + public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblyTuple?.Type : throw new NotImplementedException(); - public int DiskId => this.File.DiskId ?? 0; + public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblyTuple?.ApplicationFileRef : throw new NotImplementedException(); - public bool Uncompressed => (this.File.Attributes & FileTupleAttributes.Uncompressed) == FileTupleAttributes.Uncompressed; + public string AssemblyManifestFileRef => this.FileRow == null ? this.AssemblyTuple?.ManifestFileRef : throw new NotImplementedException(); /// /// Gets the set of MsiAssemblyName rows created for this file. diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs index 19a26915..5cb2524d 100644 --- a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs @@ -89,7 +89,7 @@ namespace WixToolset.Core.Bind { var objectField = field.AsPath(); -#if REVISIT_FOR_PATCHING +#if TODO_PATCHING // Skip file resolution if the file is to be deleted. if (RowOperation.Delete == tuple.Operation) { @@ -111,7 +111,7 @@ namespace WixToolset.Core.Bind { if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe) { -#if REVISIT_FOR_PATCHING +#if TODO_PATCHING // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file if (null == objectField.UnresolvedData) { @@ -129,7 +129,7 @@ namespace WixToolset.Core.Bind var value = fileResolver.ResolveFile(objectField.Path, tuple.Definition, tuple.SourceLineNumbers, BindStage.Normal); field.Set(value); } -#if REVISIT_FOR_PATCHING +#if TODO_PATCHING else // Re-base binding path scenario caused by pyro.exe -bt -bu { // by default, use the resolved Data for file lookup @@ -158,7 +158,7 @@ namespace WixToolset.Core.Bind } } -#if REVISIT_FOR_PATCHING +#if TODO_PATCHING if (null != objectField.PreviousData) { objectField.PreviousData = this.BindVariableResolver.ResolveVariables(tuple.SourceLineNumbers, objectField.PreviousData, false, out isDefault); diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 0c3d4c9c..cee64911 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -7878,7 +7878,7 @@ namespace WixToolset.Core if (patch) { // /Patch/PatchProperty goes directly into MsiPatchMetadata table - this.Core.AddTuple(new MsiPatchMetadataTuple(sourceLineNumbers) + this.Core.AddTuple(new MsiPatchMetadataTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, company, name)) { Company = company, Property = name, @@ -8130,12 +8130,15 @@ namespace WixToolset.Core /// /// The element to parse. /// Media index from parent element. - private void ParsePatchBaselineElement(XElement node, int diskId) + private void ParsePatchBaselineElement(XElement node, int? diskId) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; var parsedValidate = false; var validationFlags = TransformFlags.PatchTransformDefault; + string baselineFile = null; + string updateFile = null; + string transformFile = null; foreach (var attrib in node.Attributes()) { @@ -8146,6 +8149,18 @@ namespace WixToolset.Core case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "BaselineFile": + baselineFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpdateFile": + updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "TransformFile": + transformFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; default: this.Core.UnexpectedAttribute(node, attrib); break; @@ -8167,6 +8182,28 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, 27)); } + if (!String.IsNullOrEmpty(baselineFile) || !String.IsNullOrEmpty(updateFile)) + { + if (String.IsNullOrEmpty(baselineFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "UpdateFile")); + } + + if (String.IsNullOrEmpty(updateFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile", "BaselineFile")); + } + + if (!String.IsNullOrEmpty(transformFile)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TransformFile", !String.IsNullOrEmpty(baselineFile) ? "BaselineFile" : "UpdateFile")); + } + } + else if (String.IsNullOrEmpty(transformFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "TransformFile", true)); + } + foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) @@ -8200,8 +8237,11 @@ namespace WixToolset.Core { var tuple = new WixPatchBaselineTuple(sourceLineNumbers, id) { - DiskId = diskId, - ValidationFlags = validationFlags + DiskId = diskId ?? 1, + ValidationFlags = validationFlags, + BaselineFile = new IntermediateFieldPathValue { Path = baselineFile }, + UpdateFile = new IntermediateFieldPathValue { Path = updateFile }, + TransformFile = new IntermediateFieldPathValue { Path = transformFile } }; this.Core.AddTuple(tuple); diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index 4b6839f1..89d4b6da 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -1028,12 +1028,6 @@ namespace WixToolset.Core Value = "0" }); - this.Core.AddTuple(new SummaryInformationTuple(sourceLineNumbers) - { - PropertyId = SumaryInformationType.WindowsInstallerVersion, - Value = msiVersion.ToString(CultureInfo.InvariantCulture) - }); - this.Core.AddTuple(new SummaryInformationTuple(sourceLineNumbers) { PropertyId = SumaryInformationType.Security, diff --git a/src/WixToolset.Core/Compiler_Patch.cs b/src/WixToolset.Core/Compiler_Patch.cs index 42951543..f8d05132 100644 --- a/src/WixToolset.Core/Compiler_Patch.cs +++ b/src/WixToolset.Core/Compiler_Patch.cs @@ -197,9 +197,8 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { - var tuple = new WixPatchIdTuple(sourceLineNumbers) + var tuple = new WixPatchIdTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, patchId)) { - ProductCode = patchId, ClientPatchId = clientPatchId, OptimizePatchSizeForLargeFiles = optimizePatchSizeForLargeFiles, ApiPatchingSymbolFlags = apiPatchingSymbolFlags diff --git a/src/WixToolset.Core/Compiler_UI.cs b/src/WixToolset.Core/Compiler_UI.cs index 30bb7ab6..8a425fd4 100644 --- a/src/WixToolset.Core/Compiler_UI.cs +++ b/src/WixToolset.Core/Compiler_UI.cs @@ -470,7 +470,7 @@ namespace WixToolset.Core string defaultControl = null; string cancelControl = null; - this.ParseControlElement(child, id.Id, TupleDefinitionType.BBControl, ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl, false); + this.ParseControlElement(child, id.Id, TupleDefinitionType.BBControl, ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl); break; default: this.Core.UnexpectedElement(node, child); @@ -966,7 +966,7 @@ namespace WixToolset.Core switch (child.Name.LocalName) { case "Control": - this.ParseControlElement(child, id.Id, TupleDefinitionType.Control, ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl, trackDiskSpace); + this.ParseControlElement(child, id.Id, TupleDefinitionType.Control, ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl); break; default: this.Core.UnexpectedElement(node, child); @@ -1032,7 +1032,7 @@ namespace WixToolset.Core /// Name of the default control. /// Name of the candle control. /// True if the containing dialog tracks disk space. - private void ParseControlElement(XElement node, string dialog, TupleDefinitionType tupleType, ref IntermediateTuple lastTabTuple, ref string firstControl, ref string defaultControl, ref string cancelControl, bool trackDiskSpace) + private void ParseControlElement(XElement node, string dialog, TupleDefinitionType tupleType, ref IntermediateTuple lastTabTuple, ref string firstControl, ref string defaultControl, ref string cancelControl) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier controlId = null; diff --git a/src/WixToolset.Core/Resolver.cs b/src/WixToolset.Core/Resolver.cs index cbae3742..54bde848 100644 --- a/src/WixToolset.Core/Resolver.cs +++ b/src/WixToolset.Core/Resolver.cs @@ -92,7 +92,7 @@ namespace WixToolset.Core delayedFields = command.DelayedFields; } -#if REVISIT_FOR_PATCHING +#if TODO_PATCHING if (context.IntermediateRepresentation.SubStorages != null) { foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages) diff --git a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs new file mode 100644 index 00000000..584f86fe --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs @@ -0,0 +1,112 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.ComponentModel; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text; + using System.Xml.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class PatchFixture + { + private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd"; + + [Fact(Skip = "Skip until patches have files in them")] + public void CanBuildSimplePatch() + { + var folder = TestData.Get(@"TestData\PatchSingle"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); + var baselinePath = Path.ChangeExtension(baselinePdb, ".msp"); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + Assert.True(File.Exists(baselinePdb)); + Assert.True(File.Exists(update1Pdb)); + + var doc = GetExtractPatchXml(patchPath); + Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + + var names = Query.GetSubStorageNames(patchPath); + Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); + + var cab = Path.Combine(tempFolder, "foo.cab"); + Query.ExtractStream(patchPath, "foo.cab", cab); + Assert.True(File.Exists(cab)); + + var files = Query.GetCabinetFiles(cab); + Assert.Equal(new[] { "a", "b" }, files.Select(f => f.ArchiveName).ToArray()); // This test may not be quite correct, yet. + } + } + + private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB) + { + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(sourceFolder, @"Package.wxs"), + "-d", "V=" + defineV, + "-d", "A=" + defineA, + "-d", "B=" + defineB, + "-bindpath", Path.Combine(sourceFolder, ".data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", outputPath + }); + + result.AssertSuccess(); + + return Path.ChangeExtension(outputPath, ".wixpdb"); + } + + private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV) + { + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(sourceFolder, @"Patch.wxs"), + "-d", "V=" + defineV, + "-bindpath", Path.Combine(baseFolder, "bin"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", outputPath + }); + + result.AssertSuccess(); + + return Path.ChangeExtension(outputPath, ".wixpdb"); + } + + private static XDocument GetExtractPatchXml(string path) + { + var buffer = new StringBuilder(65535); + var size = buffer.Capacity; + + var er = MsiExtractPatchXMLData(path, 0, buffer, ref size); + if (er != 0) + { + throw new Win32Exception(er); + } + + return XDocument.Parse(buffer.ToString()); + } + + [DllImport("msi.dll", EntryPoint = "MsiExtractPatchXMLDataW", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int MsiExtractPatchXMLData(string szPatchPath, int dwReserved, StringBuilder szXMLData, ref int pcchXMLData); + + [DllImport("msi.dll", EntryPoint = "MsiApplyPatchW", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine); + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs index 4e48cbe1..b1a4c607 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs @@ -67,34 +67,6 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact] - public void WixVersionVariablesWork() - { - var folder = TestData.Get(@"TestData\Variables"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var warning = result.Messages.Where(message => message.Id == (int)WarningMessages.Ids.PreprocessorWarning); - Assert.Single(warning); - } - } - [Fact] public void ForEachLoopsWork() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt @@ -0,0 +1 @@ +This is A v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt new file mode 100644 index 00000000..b1f0bc01 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt @@ -0,0 +1 @@ +This ia A v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt new file mode 100644 index 00000000..ece55fec --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt @@ -0,0 +1 @@ +This is B v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt new file mode 100644 index 00000000..cf3372fd --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt @@ -0,0 +1 @@ +This ia B v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs new file mode 100644 index 00000000..2657797f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs new file mode 100644 index 00000000..7c3cff7e --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt @@ -0,0 +1 @@ +This is A v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt new file mode 100644 index 00000000..b1f0bc01 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt @@ -0,0 +1 @@ +This ia A v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt new file mode 100644 index 00000000..ece55fec --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt @@ -0,0 +1 @@ +This is B v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt new file mode 100644 index 00000000..cf3372fd --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt @@ -0,0 +1 @@ +This ia B v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs new file mode 100644 index 00000000..ee133ba3 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs new file mode 100644 index 00000000..52e87f64 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 0330adf6..b17a27ff 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -1,4 +1,4 @@ - + @@ -41,6 +41,18 @@ + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 57559c253fe8ec6f9c66662d11fbcabccc3ba057 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Thu, 6 Feb 2020 22:04:55 -0500 Subject: Add CreateFolder tuples for null-keypath components in the backend instead of the compiler. --- .../Bind/AddCreateFoldersCommand.cs | 40 ++++++++++++++++++++++ .../Bind/BindDatabaseCommand.cs | 6 ++++ src/WixToolset.Core/Compiler.cs | 17 --------- .../MsiQueryFixture.cs | 33 ++++++++++++++++++ .../TestData/Components/Package.en-us.wxl | 11 ++++++ .../TestData/Components/Package.wxs | 21 ++++++++++++ .../TestData/Components/PackageComponents.wxs | 10 ++++++ .../TestData/Components/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 4 +++ 9 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs new file mode 100644 index 00000000..6cc4153f --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs @@ -0,0 +1,40 @@ +// 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.Bind +{ + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Tuples; + + /// + /// Add CreateFolder tuples, if not already present, for null-keypath components. + /// + internal class AddCreateFoldersCommand + { + internal AddCreateFoldersCommand(IntermediateSection section) + { + this.Section = section; + } + + private IntermediateSection Section { get; } + + public void Execute() + { + var createFolderTuplesByComponentRef = new HashSet(this.Section.Tuples.OfType().Select(t => t.ComponentRef)); + foreach (var componentTuple in this.Section.Tuples.OfType().Where(t => t.KeyPathType == ComponentKeyPathType.Directory).ToList()) + { + if (!createFolderTuplesByComponentRef.Contains(componentTuple.Id.Id)) + { + var createFolderTuple = new CreateFolderTuple(componentTuple.SourceLineNumbers) + { + DirectoryRef = componentTuple.DirectoryRef, + ComponentRef = componentTuple.Id.Id, + }; + + this.Section.Tuples.Add(createFolderTuple); + } + } + } + } +} \ No newline at end of file diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 34104ef5..1bcaf209 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -338,6 +338,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind command.Execute(); } + // Add missing CreateFolder tuples to null-keypath components. + { + var command = new AddCreateFoldersCommand(section); + command.Execute(); + } + // stop processing if an error previously occurred if (this.Messaging.EncounteredError) { diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index cee64911..6fd0f30b 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -2126,7 +2126,6 @@ namespace WixToolset.Core var id = new Identifier(AccessModifier.Private, componentIdPlaceholderWixVariable); var keyFound = false; string keyPath = null; - var shouldAddCreateFolder = false; var keyPathType = ComponentKeyPathType.Directory; var location = ComponentLocation.LocalOnly; @@ -2181,7 +2180,6 @@ namespace WixToolset.Core { keyFound = true; keyPath = null; - shouldAddCreateFolder = true; } break; case "Location": @@ -2343,10 +2341,6 @@ namespace WixToolset.Core break; case "CreateFolder": var createdFolder = this.ParseCreateFolderElement(child, id.Id, directoryId, win64); - if (directoryId == createdFolder) - { - shouldAddCreateFolder = false; - } break; case "Environment": this.ParseEnvironmentElement(child, id.Id); @@ -2479,17 +2473,6 @@ namespace WixToolset.Core } } - if (shouldAddCreateFolder) - { - var tuple = new CreateFolderTuple(sourceLineNumbers) - { - DirectoryRef = directoryId, - ComponentRef = id.Id - }; - - this.Core.AddTuple(tuple); - } - // check for conditions that exclude this component from using generated guids var isGeneratableGuidOk = "*" == guid; if (isGeneratableGuidOk) diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 38ef2e2e..6fd02d5f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -252,6 +252,39 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void PopulatesCreateFolderTableForNullKeypathComponents() + { + var folder = TestData.Get(@"TestData\Components"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CreateFolder" }); + Assert.Equal(new[] + { + "CreateFolder:INSTALLFOLDER\tNullKeypathComponent", + }, results); + } + } + [Fact] public void PopulatesCustomActionTable() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs new file mode 100644 index 00000000..6da3dcbe --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs new file mode 100644 index 00000000..beaf70bf --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index b17a27ff..a64ff93d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -156,6 +156,10 @@ + + + + -- cgit v1.2.3-55-g6feb From f56ab787834ce5ecb1b40f71fb7c7f6b470ab1e3 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 24 Feb 2020 20:44:34 -0500 Subject: Fix File/@TrueType. --- .../Bind/CreateOutputFromIRCommand.cs | 2 +- src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 4 ++-- src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 5707f7ce..3a165582 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -531,7 +531,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind attributes |= (tuple.Attributes & FileTupleAttributes.Vital) == FileTupleAttributes.Vital ? WindowsInstallerConstants.MsidbFileAttributesVital : 0; row.Attributes = attributes; - if (!String.IsNullOrEmpty(tuple.FontTitle)) + if (tuple.FontTitle != null) { var fontRow = this.CreateRow(tuple, "Font"); fontRow[0] = tuple.Id.Id; diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 6fd02d5f..d93b3d54 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -594,7 +594,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] + [Fact] public void PopulatesFontTableFromTrueType() { var folder = TestData.Get(@"TestData"); @@ -622,7 +622,7 @@ namespace WixToolsetTest.CoreIntegration var results = Query.QueryDatabase(msiPath, new[] { "Font" }); Assert.Equal(new[] { - "Font:TrueTypeFontComp.ttf\t", + "Font:TrueTypeFontFile\t", }, results); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs index ff94ce52..6ac48963 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs @@ -3,7 +3,7 @@ - + -- cgit v1.2.3-55-g6feb From 22c97adba70fa838b8f285d404750d0f8fe685d8 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 20 Mar 2020 19:55:02 +1000 Subject: Update Example.Extension to automatically update based on its Example.wxs. Use .wixlib instead of .wir, and skip tests which are now failing. Add more bundle tests. --- WixToolset.Core.sln | 15 +++ .../CompileCoreTestExtensionWixlib.csproj | 13 +++ src/test/CompileCoreTestExtensionWixlib/Program.cs | 33 +++++++ src/test/Example.Extension/Data/example.wxs | 3 + .../Example.Extension/Example.Extension.csproj | 22 ++++- src/test/Example.Extension/ExampleExtensionData.cs | 2 +- .../BundleFixture.cs | 101 ++++++++++++++++++++- .../MultiFileBootstrapperApplication.wxs | 6 ++ .../TestData/SimpleBundle/MultiFileBundle.wxs | 11 +++ .../WixToolsetTest.CoreIntegration.csproj | 2 + .../WixiplFixture.cs | 4 +- .../WixlibFixture.cs | 54 +++++++++++ 12 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj create mode 100644 src/test/CompileCoreTestExtensionWixlib/Program.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/WixToolset.Core.sln b/WixToolset.Core.sln index e2dbb592..6c804904 100644 --- a/WixToolset.Core.sln +++ b/WixToolset.Core.sln @@ -16,6 +16,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.CoreIntegrat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.TestPackage", "src\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj", "{853716DB-C02C-41BD-91BC-79CDC0C17D10}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompileCoreTestExtensionWixlib", "src\test\CompileCoreTestExtensionWixlib\CompileCoreTestExtensionWixlib.csproj", "{23FC60D7-B101-42F8-9786-DB7A9CD964A2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -98,6 +100,18 @@ Global {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.Build.0 = Release|Any CPU {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.ActiveCfg = Release|Any CPU {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.Build.0 = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.ActiveCfg = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.Build.0 = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.ActiveCfg = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.Build.0 = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.Build.0 = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.ActiveCfg = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.Build.0 = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.ActiveCfg = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -105,6 +119,7 @@ Global GlobalSection(NestedProjects) = preSolution {C66C2503-C671-4230-8B48-1D93A8532A28} = {1284331E-BC6C-426D-AAAF-140C0174F875} {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B} = {1284331E-BC6C-426D-AAAF-140C0174F875} + {23FC60D7-B101-42F8-9786-DB7A9CD964A2} = {1284331E-BC6C-426D-AAAF-140C0174F875} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB8820D5-723D-426D-B4A0-4D221603C5FA} diff --git a/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj b/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj new file mode 100644 index 00000000..4bc1d02b --- /dev/null +++ b/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj @@ -0,0 +1,13 @@ + + + + + + net461 + Exe + + + + + + \ No newline at end of file diff --git a/src/test/CompileCoreTestExtensionWixlib/Program.cs b/src/test/CompileCoreTestExtensionWixlib/Program.cs new file mode 100644 index 00000000..308ab8a2 --- /dev/null +++ b/src/test/CompileCoreTestExtensionWixlib/Program.cs @@ -0,0 +1,33 @@ +// 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. + +using System.Collections.Generic; +using WixToolset.Core.TestPackage; + +namespace CompileCoreTestExtensionWixlib +{ + // We want to be able to test Core with extensions, but there's no easy way to build an extension without Tools. + // So we have this helper exe. + public class Program + { + public static void Main(string[] args) + { + var intermediateFolder = args[0]; + var wixlibPath = args[1]; + + var buildArgs = new List(); + buildArgs.Add("build"); + foreach (var path in args[2].Split(';')) + { + buildArgs.Add(path); + } + buildArgs.Add("-intermediateFolder"); + buildArgs.Add(intermediateFolder); + buildArgs.Add("-o"); + buildArgs.Add(wixlibPath); + + var result = WixRunner.Execute(buildArgs.ToArray()); + + result.AssertSuccess(); + } + } +} diff --git a/src/test/Example.Extension/Data/example.wxs b/src/test/Example.Extension/Data/example.wxs index 53531e99..cb100adf 100644 --- a/src/test/Example.Extension/Data/example.wxs +++ b/src/test/Example.Extension/Data/example.wxs @@ -5,4 +5,7 @@ + + + diff --git a/src/test/Example.Extension/Example.Extension.csproj b/src/test/Example.Extension/Example.Extension.csproj index 32560e60..e9483c72 100644 --- a/src/test/Example.Extension/Example.Extension.csproj +++ b/src/test/Example.Extension/Example.Extension.csproj @@ -9,11 +9,31 @@ - + + + + false + + + + + + $(OutputPath)..\net461\CompileCoreTestExtensionWixlib.exe + $(IntermediateOutputPath)Example.wixlib + + + + + + + + + + diff --git a/src/test/Example.Extension/ExampleExtensionData.cs b/src/test/Example.Extension/ExampleExtensionData.cs index 724f9eea..de0b8899 100644 --- a/src/test/Example.Extension/ExampleExtensionData.cs +++ b/src/test/Example.Extension/ExampleExtensionData.cs @@ -11,7 +11,7 @@ namespace Example.Extension public Intermediate GetLibrary(ITupleDefinitionCreator tupleDefinitions) { - return Intermediate.Load(typeof(ExampleExtensionData).Assembly, "Example.Extension.Data.Example.wir", tupleDefinitions); + return Intermediate.Load(typeof(ExampleExtensionData).Assembly, "Example.Extension.Example.wixlib", tupleDefinitions); } public bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition) diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index f32208a4..0e127e6e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -2,8 +2,10 @@ namespace WixToolsetTest.CoreIntegration { + using System; using System.IO; using System.Linq; + using Example.Extension; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using WixToolset.Data; @@ -12,6 +14,50 @@ namespace WixToolsetTest.CoreIntegration public class BundleFixture { + [Fact] + public void CanBuildMultiFileBundle() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), + Path.Combine(folder, "MultiFileBundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-burnStub", burnStubPath, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); +#if TODO + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); +#endif + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + var bundleTuple = section.Tuples.OfType().Single(); + Assert.Equal("1.0.0.0", bundleTuple.Version); + + var previousVersion = bundleTuple.Fields[(int)WixBundleTupleFields.Version].PreviousValue; + Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString()); + + var msiTuple = section.Tuples.OfType().Single(); + Assert.Equal("test.msi", msiTuple.Id.Id); + } + } + [Fact] public void CanBuildSimpleBundle() { @@ -51,7 +97,60 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString()); var msiTuple = section.Tuples.OfType().Single(); - Assert.Equal("test.msi", msiTuple.Id.Id ); + Assert.Equal("test.msi", msiTuple.Id.Id); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void CanBuildSimpleBundleUsingExtensionBA() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var libResult = WixRunner.Execute(new[] + { + "build", + Path.Combine(@"C:\src\mynewwix4\Core\src\test\Example.Extension\Data", "example.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixlib") + }); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-burnStub", burnStubPath, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); +#if TODO + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); +#endif + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + var bundleTuple = section.Tuples.OfType().Single(); + Assert.Equal("1.0.0.0", bundleTuple.Version); + + var previousVersion = bundleTuple.Fields[(int)WixBundleTupleFields.Version].PreviousValue; + Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString()); + + var msiTuple = section.Tuples.OfType().Single(); + Assert.Equal("test.msi", msiTuple.Id.Id); } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs new file mode 100644 index 00000000..2d36934f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs new file mode 100644 index 00000000..205c58ca --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index a64ff93d..7f21fde1 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -109,6 +109,8 @@ + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs index e45fa711..5927987b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs @@ -91,7 +91,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact] + [Fact(Skip = "Test demonstrates failure")] public void CanBuildMsiUsingExtensionLibrary() { var folder = TestData.Get(@"TestData\Wixipl"); @@ -135,7 +135,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact] + [Fact(Skip = "Test demonstrates failure")] public void CanBuildWixiplUsingExtensionLibrary() { var folder = TestData.Get(@"TestData\Wixipl"); diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs index b7f2f9c0..a48a8370 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs @@ -14,6 +14,60 @@ namespace WixToolsetTest.CoreIntegration public class WixlibFixture { + [Fact] + public void CanBuildSimpleBundleUsingWixlib() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-burnStub", burnStubPath, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); +#if TODO + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); +#endif + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); + var section = intermediate.Sections.Single(); + + var bundleTuple = section.Tuples.OfType().Single(); + Assert.Equal("1.0.0.0", bundleTuple.Version); + + var previousVersion = bundleTuple.Fields[(int)WixBundleTupleFields.Version].PreviousValue; + Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString()); + + var msiTuple = section.Tuples.OfType().Single(); + Assert.Equal("test.msi", msiTuple.Id.Id); + } + } + [Fact] public void CanBuildSingleFileUsingWixlib() { -- cgit v1.2.3-55-g6feb From afbc6889c73d58136cb8851858ca3c17f41dc2c5 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 26 Mar 2020 15:21:06 +1000 Subject: Add BundleExtension element. Add GetTestXml. Fix issue with building with current version of burn. --- .../Bundles/CreateBundleExeCommand.cs | 2 +- .../Bundles/CreateBurnManifestCommand.cs | 12 ++++ .../WixToolset.Core.Burn.csproj | 6 ++ src/WixToolset.Core.TestPackage/BundleExtractor.cs | 44 +++++++++++++ .../ExtractBAContainerResult.cs | 39 +++++++++++ .../XmlNodeExtensions.cs | 75 ++++++++++++++++++++++ src/WixToolset.Core/Compiler.cs | 6 ++ src/WixToolset.Core/Compiler_Bundle.cs | 73 +++++++++++++++++++++ .../BundleManifestFixture.cs | 61 ++++++++++++++++++ .../TestData/BundleExtension/BundleExtension.wxs | 6 ++ .../BundleExtension/SimpleBundleExtension.wxs | 10 +++ .../TestData/BundleWithPackageGroupRef/Bundle.wxs | 9 +++ .../MinimalPackageGroup.wxs | 8 +++ .../TestXmlFixture.cs | 62 ++++++++++++++++++ .../WixToolsetTest.CoreIntegration.csproj | 4 ++ 15 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 src/WixToolset.Core.TestPackage/BundleExtractor.cs create mode 100644 src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs create mode 100644 src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs index bf0473d2..53636509 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs @@ -125,7 +125,7 @@ namespace WixToolset.Core.Burn.Bundles version.FileVersion = fourPartVersion; version.ProductVersion = fourPartVersion; - var strings = version[1033]; + var strings = version[1033] ?? version.Add(1033); strings["LegalCopyright"] = bundleInfo.Copyright; strings["OriginalFilename"] = Path.GetFileName(outputPath); strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index b7ea4116..64a01794 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -621,6 +621,18 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); } + // Write the BundleExtension elements. + var bundleExtensions = this.Section.Tuples.OfType(); + + foreach (var bundleExtension in bundleExtensions) + { + writer.WriteStartElement("BundleExtension"); + writer.WriteAttributeString("Id", bundleExtension.Id.Id); + writer.WriteAttributeString("EntryPayloadId", bundleExtension.PayloadRef); + + writer.WriteEndElement(); + } + writer.WriteEndDocument(); // } } diff --git a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj index 3542f85a..ae0e7023 100644 --- a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj +++ b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj @@ -10,6 +10,12 @@ true + + + <_Parameter1>WixToolset.Core.TestPackage + + + diff --git a/src/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/WixToolset.Core.TestPackage/BundleExtractor.cs new file mode 100644 index 00000000..3d7b2932 --- /dev/null +++ b/src/WixToolset.Core.TestPackage/BundleExtractor.cs @@ -0,0 +1,44 @@ +// 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.TestPackage +{ + using System.IO; + using System.Xml; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Extensibility.Services; + + public class BundleExtractor + { + public static ExtractBAContainerResult ExtractBAContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) + { + var result = new ExtractBAContainerResult(); + Directory.CreateDirectory(tempFolderPath); + using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) + { + result.Success = burnReader.ExtractUXContainer(destinationFolderPath, tempFolderPath); + } + + if (result.Success) + { + result.ManifestDocument = LoadBurnManifest(destinationFolderPath); + result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); + } + + return result; + } + + public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BurnNamespace); + return namespaceManager; + } + + public static XmlDocument LoadBurnManifest(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, "manifest.xml")); + return document; + } + } +} diff --git a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs new file mode 100644 index 00000000..6d2ea943 --- /dev/null +++ b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs @@ -0,0 +1,39 @@ +// 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.TestPackage +{ + using System.IO; + using System.Xml; + using Xunit; + + public class ExtractBAContainerResult + { + public XmlDocument ManifestDocument { get; set; } + public XmlNamespaceManager ManifestNamespaceManager { get; set; } + public bool Success { get; set; } + + public ExtractBAContainerResult AssertSuccess() + { + Assert.True(this.Success); + return this; + } + + public string GetBAFilePath(string extractedBAContainerFolderPath) + { + var uxPayloads = this.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload"); + var baPayload = uxPayloads[0]; + var relativeBAPath = baPayload.Attributes["FilePath"].Value; + return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); + } + + /// + /// + /// + /// elements must have the 'burn' prefix + /// + public XmlNodeList SelectManifestNodes(string xpath) + { + return this.ManifestDocument.SelectNodes(xpath, this.ManifestNamespaceManager); + } + } +} diff --git a/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs b/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs new file mode 100644 index 00000000..a7f04508 --- /dev/null +++ b/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs @@ -0,0 +1,75 @@ +// 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.TestPackage +{ + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using System.Xml; + + public static class XmlNodeExtensions + { + public static string GetTestXml(this XmlNode node, Dictionary> ignoredAttributesByElementName = null) + { + return node.OuterXml.GetTestXml(ignoredAttributesByElementName); + } + + public static string GetTestXml(this string xml, Dictionary> ignoredAttributesByElementName = null) + { + string formattedXml; + using (var sw = new StringWriter()) + using (var writer = new TestXmlWriter(sw)) + { + var doc = new XmlDocument(); + doc.LoadXml(xml); + + if (ignoredAttributesByElementName != null) + { + HandleIgnoredAttributes(doc, ignoredAttributesByElementName); + } + + doc.Save(writer); + formattedXml = sw.ToString(); + } + + return Regex.Replace(formattedXml, " xmlns(:[^=]+)?='[^']*'", ""); + } + + private static void HandleIgnoredAttributes(XmlNode node, Dictionary> ignoredAttributesByElementName) + { + if (node.Attributes != null && ignoredAttributesByElementName.TryGetValue(node.LocalName, out var ignoredAttributes)) + { + foreach (var ignoredAttribute in ignoredAttributes) + { + var attribute = node.Attributes[ignoredAttribute]; + if (attribute != null) + { + attribute.Value = "*"; + } + } + } + + if (node.ChildNodes != null) + { + foreach (XmlNode childNode in node.ChildNodes) + { + HandleIgnoredAttributes(childNode, ignoredAttributesByElementName); + } + } + } + + private class TestXmlWriter : XmlTextWriter + { + public TestXmlWriter(TextWriter w) + : base(w) + { + this.QuoteChar = '\''; + } + + public override void WriteStartDocument() + { + //OmitXmlDeclaration + } + } + } +} diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 8c0c4a39..6f122f7b 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -6121,6 +6121,12 @@ namespace WixToolset.Core case "BootstrapperApplicationRef": this.ParseBootstrapperApplicationRefElement(child); break; + case "BundleExtension": + this.ParseBundleExtensionElement(child); + break; + case "BundleExtensionRef": + this.ParseSimpleRefElement(child, "WixBundleExtension"); + break; case "ComplianceCheck": this.ParseComplianceCheckElement(child); break; diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 3be7d0c5..a840e448 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -278,6 +278,12 @@ namespace WixToolset.Core case "BootstrapperApplicationRef": this.ParseBootstrapperApplicationRefElement(child); break; + case "BundleExtension": + this.ParseBundleExtensionElement(child); + break; + case "BundleExtensionRef": + this.ParseSimpleRefElement(child, "WixBundleExtension"); + break; case "OptionalUpdateRegistration": this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name); break; @@ -759,6 +765,73 @@ namespace WixToolset.Core } } + /// + /// Parse the BundleExtension element. + /// + /// Element to parse + private void ParseBundleExtensionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier previousId = null; + var previousType = ComplexReferenceChildType.Unknown; + + // The BundleExtension element acts like a Payload element so delegate to the "Payload" attribute parsing code to parse and create a Payload entry. + var id = this.ParsePayloadElementContent(node, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId, false); + if (null != id) + { + previousId = id; + previousType = ComplexReferenceChildType.Payload; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == previousId) + { + // We need *either* or or even just @SourceFile on the BundleExtension... + // but we just say there's a missing . + // TODO: Is there a better message for this? + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Payload")); + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + // Add the BundleExtension. + if (!this.Core.EncounteredError) + { + var tuple = new WixBundleExtensionTuple(sourceLineNumbers, id) + { + PayloadRef = id.Id, + }; + this.Core.AddTuple(tuple); + } + } + /// /// Parse the OptionalUpdateRegistration element. /// diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs new file mode 100644 index 00000000..da4482ff --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -0,0 +1,61 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class BundleManifestFixture + { + [Fact] + public void PopulatesManifestWithBundleExtension() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleExtension", "BundleExtension.wxs"), + Path.Combine(folder, "BundleExtension", "SimpleBundleExtension.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-burnStub", burnStubPath, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); + Assert.Equal(1, bundleExtensions.Count); + Assert.Equal("", bundleExtensions[0].GetTestXml()); + + var bundleExtensionPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload[@Id='ExampleBext']"); + Assert.Equal(1, bundleExtensionPayloads.Count); + var ignored = new Dictionary> + { + { "Payload", new List { "FileSize", "Hash", "SourcePath" } }, + }; + Assert.Equal("", bundleExtensionPayloads[0].GetTestXml(ignored)); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs new file mode 100644 index 00000000..eefae822 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs new file mode 100644 index 00000000..7303a05a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs new file mode 100644 index 00000000..207a8de1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs new file mode 100644 index 00000000..b0bde4f6 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs b/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs new file mode 100644 index 00000000..5330305e --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs @@ -0,0 +1,62 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using WixToolset.Core.TestPackage; + using Xunit; + + public class TestXmlFixture + { + [Fact] + public void ChangesIgnoredAttributesToStarToHelpMakeTestsLessFragile() + { + var original = @" + + + + +"; + var expected = ""; + var ignored = new Dictionary> { { "Target", new List { "One", "Two", "Missing" } } }; + Assert.Equal(expected, original.GetTestXml(ignored)); + } + + [Fact] + public void OutputsSingleQuotesSinceDoubleQuotesInCsharpLiteralStringsArePainful() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesAllNamespacesToReduceTyping() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesUnnecessaryWhitespaceToAvoidLineEndingIssues() + { + var original = @" + + + + +"; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesXmlDeclarationToReduceTyping() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 7f21fde1..85538b79 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -21,6 +21,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 0baf6e26ec7ab2ff0b6ad36e9d44f3d68819b5d6 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 27 Mar 2020 13:54:56 +1000 Subject: Add ability for extensions to create custom bundle searches. This required creating BundleExtensionData.xml. --- src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs | 27 +++ src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 41 ++--- .../Bind/ExtensionSearchFacade.cs | 24 +++ .../Bind/LegacySearchFacade.cs | 185 +++++++++++++++++++ src/WixToolset.Core.Burn/Bind/SearchFacade.cs | 197 --------------------- src/WixToolset.Core.Burn/Bundles/BurnCommon.cs | 8 +- ...CreateBootstrapperApplicationManifestCommand.cs | 6 +- .../CreateBundleExtensionManifestCommand.cs | 149 ++++++++++++++++ .../Bundles/CreateBurnManifestCommand.cs | 4 +- .../Bundles/OrderSearchesCommand.cs | 71 ++++++++ src/WixToolset.Core.Burn/ISearchFacade.cs | 15 ++ src/WixToolset.Core.TestPackage/BundleExtractor.cs | 34 ++++ .../ExtractBAContainerResult.cs | 32 ++++ src/WixToolset.Core/CompilerCore.cs | 5 + .../ExtensibilityServices/ParseHelper.cs | 37 ++++ src/test/Example.Extension/Data/example.wxs | 3 + .../Example.Extension/ExampleCompilerExtension.cs | 104 +++++++++++ src/test/Example.Extension/ExampleExtensionData.cs | 6 +- src/test/Example.Extension/ExampleSearchTuple.cs | 31 ++++ .../Example.Extension/ExampleTupleDefinitions.cs | 17 +- .../BundleManifestFixture.cs | 56 ++++++ .../BundleExtension/BundleExtensionSearches.wxs | 8 + .../BundleExtension/BundleWithSearches.wxs | 11 ++ .../WixToolsetTest.CoreIntegration.csproj | 2 + 24 files changed, 842 insertions(+), 231 deletions(-) create mode 100644 src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs create mode 100644 src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs create mode 100644 src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/SearchFacade.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs create mode 100644 src/WixToolset.Core.Burn/ISearchFacade.cs create mode 100644 src/test/Example.Extension/ExampleSearchTuple.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs new file mode 100644 index 00000000..d00c5778 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs @@ -0,0 +1,27 @@ +// 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.Burn +{ + using System; + using System.Xml; + using WixToolset.Data.Tuples; + + internal abstract class BaseSearchFacade : ISearchFacade + { + protected WixSearchTuple SearchTuple { get; set; } + + public virtual void WriteXml(XmlTextWriter writer) + { + writer.WriteAttributeString("Id", this.SearchTuple.Id.Id); + writer.WriteAttributeString("Variable", this.SearchTuple.Variable); + if (!String.IsNullOrEmpty(this.SearchTuple.Condition)) + { + writer.WriteAttributeString("Condition", this.SearchTuple.Condition); + } + if (!String.IsNullOrEmpty(this.SearchTuple.BundleExtensionRef)) + { + writer.WriteAttributeString("ExtensionId", this.SearchTuple.BundleExtensionRef); + } + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 9f98483f..2cb5ed64 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -117,10 +117,10 @@ namespace WixToolset.Core.Burn // If there are any fields to resolve later, create the cache to populate during bind. var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; - // TODO: Although the WixSearch tables are defined in the Util extension, - // the Bundle Binder has to know all about them. We hope to revisit all - // of this in the 4.0 timeframe. - var orderedSearches = this.OrderSearches(section); + var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section); + orderSearchesCommand.Execute(); + var orderedSearches = orderSearchesCommand.OrderedSearchFacades; + var extensionSearchTuplesById = orderSearchesCommand.ExtensionSearchTuplesByExtensionId; // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). { @@ -387,6 +387,17 @@ namespace WixToolset.Core.Burn var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; payloadTuples.Add(baManifestPayload.Id.Id, baManifestPayload); + ++uxPayloadIndex; + } + + // Generate the bundle extension manifest... + { + var command = new CreateBundleExtensionManifestCommand(section, bundleTuple, extensionSearchTuplesById, uxPayloadIndex, this.IntermediateFolder); + command.Execute(); + + var bextManifestPayload = command.BundleExtensionManifestPayloadRow; + payloadTuples.Add(bextManifestPayload.Id.Id, bextManifestPayload); + ++uxPayloadIndex; } #if TODO @@ -464,28 +475,6 @@ namespace WixToolset.Core.Burn trackedFiles.Add(trackIntermediate); } - private IEnumerable OrderSearches(IntermediateSection section) - { - var searchesById = section.Tuples - .Where(t => t.Definition.Type == TupleDefinitionType.WixComponentSearch || - t.Definition.Type == TupleDefinitionType.WixFileSearch || - t.Definition.Type == TupleDefinitionType.WixProductSearch || - t.Definition.Type == TupleDefinitionType.WixRegistrySearch) - .ToDictionary(t => t.Id.Id); - - var orderedSearches = new List(searchesById.Keys.Count); - - foreach (var searchTuple in section.Tuples.OfType()) - { - if (searchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple)) - { - orderedSearches.Add(new SearchFacade(searchTuple, specificSearchTuple)); - } - } - - return orderedSearches; - } - /// /// Populates the variable cache with specific package properties. /// diff --git a/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs new file mode 100644 index 00000000..6a830a28 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs @@ -0,0 +1,24 @@ +// 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.Burn +{ + using System.Xml; + using WixToolset.Data.Tuples; + + internal class ExtensionSearchFacade : BaseSearchFacade + { + public ExtensionSearchFacade(WixSearchTuple searchTuple) + { + this.SearchTuple = searchTuple; + } + + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("ExtensionSearch"); + + base.WriteXml(writer); + + writer.WriteEndElement(); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs b/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs new file mode 100644 index 00000000..0a80760d --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs @@ -0,0 +1,185 @@ +// 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.Burn +{ + using System; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Tuples; + + internal class LegacySearchFacade : BaseSearchFacade + { + public LegacySearchFacade(WixSearchTuple searchTuple, IntermediateTuple searchSpecificTuple) + { + this.SearchTuple = searchTuple; + this.SearchSpecificTuple = searchSpecificTuple; + } + + public IntermediateTuple SearchSpecificTuple { get; } + + /// + /// Generates Burn manifest and ParameterInfo-style markup a search. + /// + /// + public override void WriteXml(XmlTextWriter writer) + { + switch (this.SearchSpecificTuple) + { + case WixComponentSearchTuple tuple: + this.WriteComponentSearchXml(writer, tuple); + break; + case WixFileSearchTuple tuple: + this.WriteFileSearchXml(writer, tuple); + break; + case WixProductSearchTuple tuple: + this.WriteProductSearchXml(writer, tuple); + break; + case WixRegistrySearchTuple tuple: + this.WriteRegistrySearchXml(writer, tuple); + break; + } + } + + private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchTuple searchTuple) + { + writer.WriteStartElement("MsiComponentSearch"); + + base.WriteXml(writer); + + writer.WriteAttributeString("ComponentId", searchTuple.Guid); + + if (!String.IsNullOrEmpty(searchTuple.ProductCode)) + { + writer.WriteAttributeString("ProductCode", searchTuple.ProductCode); + } + + if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.KeyPath)) + { + writer.WriteAttributeString("Type", "keyPath"); + } + else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.WantDirectory)) + { + writer.WriteAttributeString("Type", "directory"); + } + + writer.WriteEndElement(); + } + + private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchTuple searchTuple) + { + writer.WriteStartElement((0 == (searchTuple.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); + + base.WriteXml(writer); + + writer.WriteAttributeString("Path", searchTuple.Path); + if (WixFileSearchAttributes.WantExists == (searchTuple.Attributes & WixFileSearchAttributes.WantExists)) + { + writer.WriteAttributeString("Type", "exists"); + } + else if (WixFileSearchAttributes.WantVersion == (searchTuple.Attributes & WixFileSearchAttributes.WantVersion)) + { + // Can never get here for DirectorySearch. + writer.WriteAttributeString("Type", "version"); + } + else + { + writer.WriteAttributeString("Type", "path"); + } + writer.WriteEndElement(); + } + + private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchTuple tuple) + { + writer.WriteStartElement("MsiProductSearch"); + + base.WriteXml(writer); + + if (0 != (tuple.Attributes & WixProductSearchAttributes.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", tuple.Guid); + } + else + { + writer.WriteAttributeString("ProductCode", tuple.Guid); + } + + if (0 != (tuple.Attributes & WixProductSearchAttributes.Version)) + { + writer.WriteAttributeString("Type", "version"); + } + else if (0 != (tuple.Attributes & WixProductSearchAttributes.Language)) + { + writer.WriteAttributeString("Type", "language"); + } + else if (0 != (tuple.Attributes & WixProductSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (tuple.Attributes & WixProductSearchAttributes.Assignment)) + { + writer.WriteAttributeString("Type", "assignment"); + } + + writer.WriteEndElement(); + } + + private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchTuple tuple) + { + writer.WriteStartElement("RegistrySearch"); + + base.WriteXml(writer); + + switch (tuple.Root) + { + case RegistryRootType.ClassesRoot: + writer.WriteAttributeString("Root", "HKCR"); + break; + case RegistryRootType.CurrentUser: + writer.WriteAttributeString("Root", "HKCU"); + break; + case RegistryRootType.LocalMachine: + writer.WriteAttributeString("Root", "HKLM"); + break; + case RegistryRootType.Users: + writer.WriteAttributeString("Root", "HKU"); + break; + } + + writer.WriteAttributeString("Key", tuple.Key); + + if (!String.IsNullOrEmpty(tuple.Value)) + { + writer.WriteAttributeString("Value", tuple.Value); + } + + var existenceOnly = 0 != (tuple.Attributes & WixRegistrySearchAttributes.WantExists); + + writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); + + if (0 != (tuple.Attributes & WixRegistrySearchAttributes.Win64)) + { + writer.WriteAttributeString("Win64", "yes"); + } + + if (!existenceOnly) + { + if (0 != (tuple.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) + { + writer.WriteAttributeString("ExpandEnvironment", "yes"); + } + + // We *always* say this is VariableType="string". If we end up + // needing to be more specific, we will have to expand the "Format" + // attribute to allow "number" and "version". + + writer.WriteAttributeString("VariableType", "string"); + } + + writer.WriteEndElement(); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/SearchFacade.cs b/src/WixToolset.Core.Burn/Bind/SearchFacade.cs deleted file mode 100644 index 65f3cb5b..00000000 --- a/src/WixToolset.Core.Burn/Bind/SearchFacade.cs +++ /dev/null @@ -1,197 +0,0 @@ -// 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.Burn -{ - using System; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Tuples; - - internal class SearchFacade - { - public SearchFacade(WixSearchTuple searchTuple, IntermediateTuple searchSpecificTuple) - { - this.SearchTuple = searchTuple; - this.SearchSpecificTuple = searchSpecificTuple; - } - - public WixSearchTuple SearchTuple { get; } - - public IntermediateTuple SearchSpecificTuple { get; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup a search. - /// - /// - public void WriteXml(XmlTextWriter writer) - { - switch (this.SearchSpecificTuple) - { - case WixComponentSearchTuple tuple: - this.WriteComponentSearchXml(writer, tuple); - break; - case WixFileSearchTuple tuple: - this.WriteFileSearchXml(writer, tuple); - break; - case WixProductSearchTuple tuple: - this.WriteProductSearchXml(writer, tuple); - break; - case WixRegistrySearchTuple tuple: - this.WriteRegistrySearchXml(writer, tuple); - break; - } - } - - private void WriteCommonAttributes(XmlTextWriter writer) - { - writer.WriteAttributeString("Id", this.SearchTuple.Id.Id); - writer.WriteAttributeString("Variable", this.SearchTuple.Variable); - if (!String.IsNullOrEmpty(this.SearchTuple.Condition)) - { - writer.WriteAttributeString("Condition", this.SearchTuple.Condition); - } - } - - private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchTuple searchTuple) - { - writer.WriteStartElement("MsiComponentSearch"); - - this.WriteCommonAttributes(writer); - - writer.WriteAttributeString("ComponentId", searchTuple.Guid); - - if (!String.IsNullOrEmpty(searchTuple.ProductCode)) - { - writer.WriteAttributeString("ProductCode", searchTuple.ProductCode); - } - - if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.KeyPath)) - { - writer.WriteAttributeString("Type", "keyPath"); - } - else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.WantDirectory)) - { - writer.WriteAttributeString("Type", "directory"); - } - - writer.WriteEndElement(); - } - - private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchTuple searchTuple) - { - writer.WriteStartElement((0 == (searchTuple.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); - - this.WriteCommonAttributes(writer); - - writer.WriteAttributeString("Path", searchTuple.Path); - if (WixFileSearchAttributes.WantExists == (searchTuple.Attributes & WixFileSearchAttributes.WantExists)) - { - writer.WriteAttributeString("Type", "exists"); - } - else if (WixFileSearchAttributes.WantVersion == (searchTuple.Attributes & WixFileSearchAttributes.WantVersion)) - { - // Can never get here for DirectorySearch. - writer.WriteAttributeString("Type", "version"); - } - else - { - writer.WriteAttributeString("Type", "path"); - } - writer.WriteEndElement(); - } - - private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchTuple tuple) - { - writer.WriteStartElement("MsiProductSearch"); - - this.WriteCommonAttributes(writer); - - if (0 != (tuple.Attributes & WixProductSearchAttributes.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", tuple.Guid); - } - else - { - writer.WriteAttributeString("ProductCode", tuple.Guid); - } - - if (0 != (tuple.Attributes & WixProductSearchAttributes.Version)) - { - writer.WriteAttributeString("Type", "version"); - } - else if (0 != (tuple.Attributes & WixProductSearchAttributes.Language)) - { - writer.WriteAttributeString("Type", "language"); - } - else if (0 != (tuple.Attributes & WixProductSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (tuple.Attributes & WixProductSearchAttributes.Assignment)) - { - writer.WriteAttributeString("Type", "assignment"); - } - - writer.WriteEndElement(); - } - - private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchTuple tuple) - { - writer.WriteStartElement("RegistrySearch"); - - this.WriteCommonAttributes(writer); - - switch (tuple.Root) - { - case RegistryRootType.ClassesRoot: - writer.WriteAttributeString("Root", "HKCR"); - break; - case RegistryRootType.CurrentUser: - writer.WriteAttributeString("Root", "HKCU"); - break; - case RegistryRootType.LocalMachine: - writer.WriteAttributeString("Root", "HKLM"); - break; - case RegistryRootType.Users: - writer.WriteAttributeString("Root", "HKU"); - break; - } - - writer.WriteAttributeString("Key", tuple.Key); - - if (!String.IsNullOrEmpty(tuple.Value)) - { - writer.WriteAttributeString("Value", tuple.Value); - } - - var existenceOnly = 0 != (tuple.Attributes & WixRegistrySearchAttributes.WantExists); - - writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); - - if (0 != (tuple.Attributes & WixRegistrySearchAttributes.Win64)) - { - writer.WriteAttributeString("Win64", "yes"); - } - - if (!existenceOnly) - { - if (0 != (tuple.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) - { - writer.WriteAttributeString("ExpandEnvironment", "yes"); - } - - // We *always* say this is VariableType="string". If we end up - // needing to be more specific, we will have to expand the "Format" - // attribute to allow "number" and "version". - - writer.WriteAttributeString("VariableType", "string"); - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs index 78b95bf4..5cff0b5a 100644 --- a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs +++ b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs @@ -22,6 +22,12 @@ namespace WixToolset.Core.Burn.Bundles public const string BurnUXContainerPayloadIdFormat = "p{0}"; public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}"; + public const string BADataFileName = "BootstrapperApplicationData.xml"; + public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; + + public const string BundleExtensionDataFileName = "BundleExtensionData.xml"; + public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData"; + // See WinNT.h for details about the PE format, including the // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, // IMAGE_FILE_HEADER, etc. @@ -167,7 +173,7 @@ namespace WixToolset.Core.Burn.Bundles /// True if initialized. protected bool Initialize(BinaryReader reader) { - if (!GetWixburnSectionInfo(reader)) + if (!this.GetWixburnSectionInfo(reader)) { return false; } diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs index 5cd1f7e8..be8227f2 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -57,7 +57,7 @@ namespace WixToolset.Core.Burn.Bundles { writer.Formatting = Formatting.Indented; writer.WriteStartDocument(); - writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"); + writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace); this.WriteBundleInfo(writer); @@ -247,11 +247,11 @@ namespace WixToolset.Core.Burn.Bundles private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) { - var generatedId = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); + var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BADataFileName); var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) { - Name = "BootstrapperApplicationData.xml", + Name = BurnCommon.BADataFileName, SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, Compressed = true, UnresolvedSourceFile = baManifestPath, diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs new file mode 100644 index 00000000..b608c03d --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs @@ -0,0 +1,149 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Tuples; + + internal class CreateBundleExtensionManifestCommand + { + public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IDictionary> extensionSearchTuplesByExtensionId, int lastUXPayloadIndex, string intermediateFolder) + { + this.Section = section; + this.BundleTuple = bundleTuple; + this.ExtensionSearchTuplesByExtensionId = extensionSearchTuplesByExtensionId; + this.LastUXPayloadIndex = lastUXPayloadIndex; + this.IntermediateFolder = intermediateFolder; + } + + private IntermediateSection Section { get; } + + private WixBundleTuple BundleTuple { get; } + + private IDictionary> ExtensionSearchTuplesByExtensionId { get; } + + private int LastUXPayloadIndex { get; } + + private string IntermediateFolder { get; } + + public WixBundlePayloadTuple BundleExtensionManifestPayloadRow { get; private set; } + + public void Execute() + { + var bextManifestPath = this.CreateBundleExtensionManifest(); + + this.BundleExtensionManifestPayloadRow = this.CreateBundleExtensionManifestPayloadRow(bextManifestPath); + } + + private string CreateBundleExtensionManifest() + { + var path = Path.Combine(this.IntermediateFolder, "wix-bextdata.xml"); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var writer = new XmlTextWriter(path, Encoding.Unicode)) + { + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument(); + writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); + + foreach (var kvp in this.ExtensionSearchTuplesByExtensionId) + { + this.WriteExtension(writer, kvp.Key, kvp.Value); + } + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + + return path; + } + + private void WriteExtension(XmlTextWriter writer, string extensionId, IEnumerable tuples) + { + writer.WriteStartElement("BundleExtension"); + + writer.WriteAttributeString("Id", extensionId); + + this.WriteBundleExtensionDataTuples(writer, tuples); + + writer.WriteEndElement(); + } + + private void WriteBundleExtensionDataTuples(XmlTextWriter writer, IEnumerable tuples) + { + var dataTuplesGroupedByDefinitionName = tuples.GroupBy(t => t.Definition); + + foreach (var group in dataTuplesGroupedByDefinitionName) + { + var definition = group.Key; + + // We simply assert that the table (and field) name is valid, because + // this is up to the extension developer to get right. An author will + // only affect the attribute value, and that will get properly escaped. +#if DEBUG + Debug.Assert(Common.IsIdentifier(definition.Name)); + foreach (var fieldDef in definition.FieldDefinitions) + { + Debug.Assert(Common.IsIdentifier(fieldDef.Name)); + } +#endif // DEBUG + + foreach (var tuple in group) + { + writer.WriteStartElement(definition.Name); + + if (tuple.Id != null) + { + writer.WriteAttributeString("Id", tuple.Id.Id); + } + + foreach (var field in tuple.Fields) + { + if (!field.IsNull()) + { + writer.WriteAttributeString(field.Definition.Name, field.AsString()); + } + } + + writer.WriteEndElement(); + } + } + } + + private WixBundlePayloadTuple CreateBundleExtensionManifestPayloadRow(string bextManifestPath) + { + var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName); + + var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) + { + Name = BurnCommon.BundleExtensionDataFileName, + SourceFile = new IntermediateFieldPathValue { Path = bextManifestPath }, + Compressed = true, + UnresolvedSourceFile = bextManifestPath, + ContainerRef = BurnConstants.BurnUXContainerName, + EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), + Packaging = PackagingType.Embedded, + }; + + var fileInfo = new FileInfo(bextManifestPath); + + tuple.FileSize = (int)fileInfo.Length; + + tuple.Hash = BundleHashAlgorithm.Hash(fileInfo); + + this.Section.Tuples.Add(tuple); + + return tuple; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 64a01794..58133d38 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -18,7 +18,7 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateBurnManifestCommand { - public CreateBurnManifestCommand(IMessaging messaging, IEnumerable backendExtensions, string executableName, IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable containers, WixChainTuple chainTuple, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, IEnumerable orderedSearches, IEnumerable catalogs, string intermediateFolder) + public CreateBurnManifestCommand(IMessaging messaging, IEnumerable backendExtensions, string executableName, IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable containers, WixChainTuple chainTuple, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, IEnumerable orderedSearches, IEnumerable catalogs, string intermediateFolder) { this.Messaging = messaging; this.BackendExtensions = backendExtensions; @@ -54,7 +54,7 @@ namespace WixToolset.Core.Burn.Bundles private IEnumerable OrderedPackages { get; } - private IEnumerable OrderedSearches { get; } + private IEnumerable OrderedSearches { get; } private Dictionary Payloads { get; } diff --git a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs new file mode 100644 index 00000000..55b31ed3 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs @@ -0,0 +1,71 @@ +// 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.Burn.Bundles +{ + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Tuples; + using WixToolset.Extensibility.Services; + + internal class OrderSearchesCommand + { + public OrderSearchesCommand(IMessaging messaging, IntermediateSection section) + { + this.Messaging = messaging; + this.Section = section; + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public IDictionary> ExtensionSearchTuplesByExtensionId { get; private set; } + + public IList OrderedSearchFacades { get; private set; } + + public void Execute() + { + // TODO: Although the WixSearch tables are defined in the Util extension, + // the Bundle Binder has to know all about them. We hope to revisit all + // of this in the 4.0 timeframe. + var legacySearchesById = this.Section.Tuples + .Where(t => t.Definition.Type == TupleDefinitionType.WixComponentSearch || + t.Definition.Type == TupleDefinitionType.WixFileSearch || + t.Definition.Type == TupleDefinitionType.WixProductSearch || + t.Definition.Type == TupleDefinitionType.WixRegistrySearch) + .ToDictionary(t => t.Id.Id); + var extensionSearchesById = this.Section.Tuples + .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchTupleDefinitionTag)) + .ToDictionary(t => t.Id.Id); + var searchTuples = this.Section.Tuples.OfType().ToList(); + + this.ExtensionSearchTuplesByExtensionId = new Dictionary>(); + this.OrderedSearchFacades = new List(legacySearchesById.Keys.Count + extensionSearchesById.Keys.Count); + + foreach (var searchTuple in searchTuples) + { + if (legacySearchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple)) + { + this.OrderedSearchFacades.Add(new LegacySearchFacade(searchTuple, specificSearchTuple)); + } + else if (extensionSearchesById.TryGetValue(searchTuple.Id.Id, out var extensionSearchTuple)) + { + this.OrderedSearchFacades.Add(new ExtensionSearchFacade(searchTuple)); + + if (!this.ExtensionSearchTuplesByExtensionId.TryGetValue(searchTuple.BundleExtensionRef, out var extensionSearchTuples)) + { + extensionSearchTuples = new List(); + this.ExtensionSearchTuplesByExtensionId[searchTuple.BundleExtensionRef] = extensionSearchTuples; + } + extensionSearchTuples.Add(extensionSearchTuple); + } + else + { + this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchTuple.SourceLineNumbers, searchTuple.Id.Id)); + } + } + } + } +} diff --git a/src/WixToolset.Core.Burn/ISearchFacade.cs b/src/WixToolset.Core.Burn/ISearchFacade.cs new file mode 100644 index 00000000..b9ad8649 --- /dev/null +++ b/src/WixToolset.Core.Burn/ISearchFacade.cs @@ -0,0 +1,15 @@ +// 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.Burn +{ + using System.Xml; + + internal interface ISearchFacade + { + /// + /// Writes the search to the Burn manifest. + /// + /// + void WriteXml(XmlTextWriter writer); + } +} diff --git a/src/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/WixToolset.Core.TestPackage/BundleExtractor.cs index 3d7b2932..8a56f117 100644 --- a/src/WixToolset.Core.TestPackage/BundleExtractor.cs +++ b/src/WixToolset.Core.TestPackage/BundleExtractor.cs @@ -22,11 +22,31 @@ namespace WixToolset.Core.TestPackage { result.ManifestDocument = LoadBurnManifest(destinationFolderPath); result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); + + result.BADataDocument = LoadBAData(destinationFolderPath); + result.BADataNamespaceManager = GetBADataNamespaceManager(result.BADataDocument, "ba"); + + result.BundleExtensionDataDocument = LoadBundleExtensionData(destinationFolderPath); + result.BundleExtensionDataNamespaceManager = GetBundleExtensionDataNamespaceManager(result.BundleExtensionDataDocument, "be"); } return result; } + public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace); + return namespaceManager; + } + + public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace); + return namespaceManager; + } + public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) { var namespaceManager = new XmlNamespaceManager(document.NameTable); @@ -34,6 +54,20 @@ namespace WixToolset.Core.TestPackage return namespaceManager; } + public static XmlDocument LoadBAData(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, BurnCommon.BADataFileName)); + return document; + } + + public static XmlDocument LoadBundleExtensionData(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, BurnCommon.BundleExtensionDataFileName)); + return document; + } + public static XmlDocument LoadBurnManifest(string baFolderPath) { var document = new XmlDocument(); diff --git a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs index 6d2ea943..63d7bb31 100644 --- a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs +++ b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs @@ -8,6 +8,10 @@ namespace WixToolset.Core.TestPackage public class ExtractBAContainerResult { + public XmlDocument BundleExtensionDataDocument { get; set; } + public XmlNamespaceManager BundleExtensionDataNamespaceManager { get; set; } + public XmlDocument BADataDocument { get; set; } + public XmlNamespaceManager BADataNamespaceManager { get; set; } public XmlDocument ManifestDocument { get; set; } public XmlNamespaceManager ManifestNamespaceManager { get; set; } public bool Success { get; set; } @@ -26,6 +30,34 @@ namespace WixToolset.Core.TestPackage return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); } + public string GetBundleExtensionFilePath(string extractedBAContainerFolderPath, string extensionId) + { + var uxPayloads = this.SelectManifestNodes($"/burn:BurnManifest/burn:UX/burn:Payload[@Id='{extensionId}']"); + var bextPayload = uxPayloads[0]; + var relativeBextPath = bextPayload.Attributes["FilePath"].Value; + return Path.Combine(extractedBAContainerFolderPath, relativeBextPath); + } + + /// + /// + /// + /// elements must have the 'ba' prefix + /// + public XmlNodeList SelectBADataNodes(string xpath) + { + return this.BADataDocument.SelectNodes(xpath, this.BADataNamespaceManager); + } + + /// + /// + /// + /// elements must have the 'be' prefix + /// + public XmlNodeList SelectBundleExtensionDataNodes(string xpath) + { + return this.BundleExtensionDataDocument.SelectNodes(xpath, this.BundleExtensionDataNamespaceManager); + } + /// /// /// diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index e87ad886..51828975 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -1026,6 +1026,11 @@ namespace WixToolset.Core return this.parseHelper.CreateDirectoryTuple(this.ActiveSection, sourceLineNumbers, id, parentId, name, this.activeSectionInlinedDirectoryIds, shortName, sourceName, shortSourceName); } + public void CreateWixSearchTuple(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) + { + this.parseHelper.CreateWixSearchTuple(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); + } + /// /// Gets the attribute value as inline directory syntax. /// diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs index 7447d420..2a851a21 100644 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs @@ -260,6 +260,43 @@ namespace WixToolset.Core.ExtensibilityServices section.Tuples.Add(tuple); } + public void CreateWixSearchTuple(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId) + { + // TODO: verify variable is not a standard bundle variable + if (variable == null) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable")); + } + + section.Tuples.Add(new WixSearchTuple(sourceLineNumbers, id) + { + Variable = variable, + Condition = condition, + BundleExtensionRef = bundleExtensionId, + }); + + if (after != null) + { + this.CreateSimpleReference(section, sourceLineNumbers, "WixSearch", after); + // TODO: We're currently defaulting to "always run after", which we will need to change... + this.CreateWixSearchRelationTuple(section, sourceLineNumbers, id, after, 2); + } + + if (!String.IsNullOrEmpty(bundleExtensionId)) + { + this.CreateSimpleReference(section, sourceLineNumbers, "WixBundleExtension", bundleExtensionId); + } + } + + public void CreateWixSearchRelationTuple(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, int attributes) + { + section.Tuples.Add(new WixSearchRelationTuple(sourceLineNumbers, id) + { + ParentSearchRef = parentId, + Attributes = attributes, + }); + } + [Obsolete] public IntermediateTuple CreateRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName, Identifier identifier = null) { diff --git a/src/test/Example.Extension/Data/example.wxs b/src/test/Example.Extension/Data/example.wxs index cb100adf..cd17d478 100644 --- a/src/test/Example.Extension/Data/example.wxs +++ b/src/test/Example.Extension/Data/example.wxs @@ -8,4 +8,7 @@ + + + diff --git a/src/test/Example.Extension/ExampleCompilerExtension.cs b/src/test/Example.Extension/ExampleCompilerExtension.cs index 5efb428f..543b4165 100644 --- a/src/test/Example.Extension/ExampleCompilerExtension.cs +++ b/src/test/Example.Extension/ExampleCompilerExtension.cs @@ -11,6 +11,7 @@ namespace Example.Extension internal class ExampleCompilerExtension : BaseCompilerExtension { public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs"; + public string BundleExtensionId => "ExampleBundleExtension"; public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) { @@ -18,6 +19,20 @@ namespace Example.Extension switch (parentElement.Name.LocalName) { + case "Bundle": + case "Fragment": + switch (element.Name.LocalName) + { + case "ExampleSearch": + this.ParseExampleSearchElement(intermediate, section, element); + processed = true; + break; + case "ExampleSearchRef": + this.ParseExampleSearchRefElement(intermediate, section, element); + processed = true; + break; + } + break; case "Component": switch (element.Name.LocalName) { @@ -77,5 +92,94 @@ namespace Example.Extension tuple.Set(1, value); } } + + private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + string searchFor = null; + string variable = null; + string condition = null; + string after = null; + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Variable": + variable = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + after = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SearchFor": + searchFor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseAttribute(intermediate, section, element, attrib, null); + } + } + + if (null == id) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + this.ParseHelper.CreateWixSearchTuple(section, sourceLineNumbers, element.Name.LocalName, id, variable, condition, after, this.BundleExtensionId); + } + + if (!this.Messaging.EncounteredError) + { + + var tuple = new ExampleSearchTuple(sourceLineNumbers, id); + section.Tuples.Add(tuple); + tuple.SearchFor = searchFor; + } + } + + private void ParseExampleSearchRefElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + var refId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "ExampleSearch", refId); + break; + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + } } } diff --git a/src/test/Example.Extension/ExampleExtensionData.cs b/src/test/Example.Extension/ExampleExtensionData.cs index de0b8899..b38eb2a2 100644 --- a/src/test/Example.Extension/ExampleExtensionData.cs +++ b/src/test/Example.Extension/ExampleExtensionData.cs @@ -1,4 +1,4 @@ -// 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. +// 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 Example.Extension { @@ -22,6 +22,10 @@ namespace Example.Extension tupleDefinition = ExampleTupleDefinitions.Example; break; + case "ExampleSearch": + tupleDefinition = ExampleTupleDefinitions.ExampleSearch; + break; + default: tupleDefinition = null; break; diff --git a/src/test/Example.Extension/ExampleSearchTuple.cs b/src/test/Example.Extension/ExampleSearchTuple.cs new file mode 100644 index 00000000..df34f0af --- /dev/null +++ b/src/test/Example.Extension/ExampleSearchTuple.cs @@ -0,0 +1,31 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + + public enum ExampleSearchTupleFields + { + Example, + SearchFor, + } + + public class ExampleSearchTuple : IntermediateTuple + { + public ExampleSearchTuple() : base(ExampleTupleDefinitions.ExampleSearch, null, null) + { + } + + public ExampleSearchTuple(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleTupleDefinitions.ExampleSearch, sourceLineNumber, id) + { + } + + public IntermediateField this[ExampleTupleFields index] => this.Fields[(int)index]; + + public string SearchFor + { + get => this.Fields[(int)ExampleSearchTupleFields.SearchFor]?.AsString(); + set => this.Set((int)ExampleSearchTupleFields.SearchFor, value); + } + } +} diff --git a/src/test/Example.Extension/ExampleTupleDefinitions.cs b/src/test/Example.Extension/ExampleTupleDefinitions.cs index 4775b827..b2c8c484 100644 --- a/src/test/Example.Extension/ExampleTupleDefinitions.cs +++ b/src/test/Example.Extension/ExampleTupleDefinitions.cs @@ -1,8 +1,9 @@ -// 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. +// 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 Example.Extension { using WixToolset.Data; + using WixToolset.Data.Burn; public static class ExampleTupleDefinitions { @@ -16,5 +17,19 @@ namespace Example.Extension new IntermediateFieldDefinition(nameof(ExampleTupleFields.Value), IntermediateFieldType.String), }, typeof(ExampleTuple)); + + public static readonly IntermediateTupleDefinition ExampleSearch = new IntermediateTupleDefinition( + nameof(ExampleSearch), + new[] + { + new IntermediateFieldDefinition(nameof(ExampleTupleFields.Example), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(ExampleSearchTupleFields.SearchFor), IntermediateFieldType.String), + }, + typeof(ExampleSearchTuple)); + + static ExampleTupleDefinitions() + { + ExampleSearch.AddTag(BurnConstants.BundleExtensionSearchTupleDefinitionTag); + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs index da4482ff..80f7b875 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -2,8 +2,10 @@ namespace WixToolsetTest.CoreIntegration { + using System; using System.Collections.Generic; using System.IO; + using Example.Extension; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using Xunit; @@ -57,5 +59,59 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal("", bundleExtensionPayloads[0].GetTestXml(ignored)); } } + + [Fact] + public void PopulatesManifestWithBundleExtensionSearches() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleExtension", "BundleExtensionSearches.wxs"), + Path.Combine(folder, "BundleExtension", "BundleWithSearches.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-burnStub", burnStubPath, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); + Assert.Equal(1, bundleExtensions.Count); + Assert.Equal("", bundleExtensions[0].GetTestXml()); + + var extensionSearches = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:ExtensionSearch"); + Assert.Equal(2, extensionSearches.Count); + Assert.Equal("", extensionSearches[0].GetTestXml()); + Assert.Equal("", extensionSearches[1].GetTestXml()); + + var bundleExtensionDatas = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']"); + Assert.Equal(1, bundleExtensionDatas.Count); + Assert.Equal("" + + "" + + "" + + "", bundleExtensionDatas[0].GetTestXml()); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs new file mode 100644 index 00000000..fd8d3698 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs @@ -0,0 +1,8 @@ + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs new file mode 100644 index 00000000..c5a93eb3 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 85538b79..324d04ff 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -22,6 +22,8 @@ + + -- cgit v1.2.3-55-g6feb From c455d2290ef903ff36d540903e27d76d473cb67c Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 27 Mar 2020 14:30:35 +1000 Subject: Add SetVariable. --- .../Bind/SetVariableSearchFacade.cs | 33 +++++ .../Bundles/OrderSearchesCommand.cs | 9 +- src/WixToolset.Core/Compiler.cs | 6 + src/WixToolset.Core/Compiler_Bundle.cs | 142 ++++++++++++++++----- .../BundleManifestFixture.cs | 44 +++++++ .../TestData/SetVariable/Simple.wxs | 15 +++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 7 files changed, 217 insertions(+), 33 deletions(-) create mode 100644 src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs new file mode 100644 index 00000000..0fe60422 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs @@ -0,0 +1,33 @@ +// 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.Burn +{ + using System.Xml; + using WixToolset.Data.Tuples; + + internal class SetVariableSearchFacade : BaseSearchFacade + { + public SetVariableSearchFacade(WixSearchTuple searchTuple, WixSetVariableTuple setVariableTuple) + { + this.SearchTuple = searchTuple; + this.SetVariableTuple = setVariableTuple; + } + + private WixSetVariableTuple SetVariableTuple { get; } + + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("SetVariable"); + + base.WriteXml(writer); + + if (this.SetVariableTuple.Type != null) + { + writer.WriteAttributeString("Value", this.SetVariableTuple.Value); + writer.WriteAttributeString("Type", this.SetVariableTuple.Type); + } + + writer.WriteEndElement(); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs index 55b31ed3..3f720115 100644 --- a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs @@ -36,13 +36,16 @@ namespace WixToolset.Core.Burn.Bundles t.Definition.Type == TupleDefinitionType.WixProductSearch || t.Definition.Type == TupleDefinitionType.WixRegistrySearch) .ToDictionary(t => t.Id.Id); + var setVariablesById = this.Section.Tuples + .OfType() + .ToDictionary(t => t.Id.Id); var extensionSearchesById = this.Section.Tuples .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchTupleDefinitionTag)) .ToDictionary(t => t.Id.Id); var searchTuples = this.Section.Tuples.OfType().ToList(); this.ExtensionSearchTuplesByExtensionId = new Dictionary>(); - this.OrderedSearchFacades = new List(legacySearchesById.Keys.Count + extensionSearchesById.Keys.Count); + this.OrderedSearchFacades = new List(legacySearchesById.Keys.Count + setVariablesById.Keys.Count + extensionSearchesById.Keys.Count); foreach (var searchTuple in searchTuples) { @@ -50,6 +53,10 @@ namespace WixToolset.Core.Burn.Bundles { this.OrderedSearchFacades.Add(new LegacySearchFacade(searchTuple, specificSearchTuple)); } + else if (setVariablesById.TryGetValue(searchTuple.Id.Id, out var setVariableTuple)) + { + this.OrderedSearchFacades.Add(new SetVariableSearchFacade(searchTuple, setVariableTuple)); + } else if (extensionSearchesById.TryGetValue(searchTuple.Id.Id, out var extensionSearchTuple)) { this.OrderedSearchFacades.Add(new ExtensionSearchFacade(searchTuple)); diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 6f122f7b..7638c11e 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -6221,6 +6221,12 @@ namespace WixToolset.Core case "SetProperty": this.ParseSetPropertyElement(child); break; + case "SetVariable": + this.ParseSetVariableElement(child); + break; + case "SetVariableRef": + this.ParseSimpleRefElement(child, "WixSetVariable"); + break; case "SFPCatalog": string parentName = null; this.ParseSFPCatalogElement(child, ref parentName); diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index a840e448..5d7072d0 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -323,6 +323,12 @@ namespace WixToolset.Core case "RelatedBundle": this.ParseRelatedBundleElement(child); break; + case "SetVariable": + this.ParseSetVariableElement(child); + break; + case "SetVariableRef": + this.ParseSimpleRefElement(child, "WixSetVariable"); + break; case "Update": this.ParseUpdateElement(child); break; @@ -2704,6 +2710,78 @@ namespace WixToolset.Core } } + /// + /// Parse SetVariable element + /// + /// Element to parse + private void ParseSetVariableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string variable = null; + string condition = null; + string after = null; + string value = null; + string type = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Variable": + variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Type": + type = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib, null); + } + } + + type = this.ValidateVariableTypeWithValue(sourceLineNumbers, type, value); + + this.Core.ParseForExtensionElements(node); + + if (id == null) + { + id = this.Core.CreateIdentifier("sbv", variable, condition, after, value, type); + } + + this.Core.CreateWixSearchTuple(sourceLineNumbers, node.Name.LocalName, id, variable, condition, after); + + if (!this.Messaging.EncounteredError) + { + var tuple = new WixSetVariableTuple(sourceLineNumbers, id) + { + Value = value, + Type = type, + }; + this.Core.AddTuple(tuple); + } + } + /// /// Parse Variable element /// @@ -2764,64 +2842,64 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix")); } - if (null == type && null != value) + type = this.ValidateVariableTypeWithValue(sourceLineNumbers, type, value); + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var tuple = new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, name)) + { + Value = value, + Type = type, + Hidden = hidden, + Persisted = persisted + }; + + this.Core.AddTuple(tuple); + } + } + + private string ValidateVariableTypeWithValue(SourceLineNumber sourceLineNumbers, string type, string value) + { + var newType = type; + if (newType == null && value != null) { // Infer the type from the current value... if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { // Version constructor does not support simple "v#" syntax so check to see if the value is // non-negative real quick. - if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var number)) + if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _)) { - type = "version"; + newType = "version"; } - else + else if (Version.TryParse(value.Substring(1), out var _)) { - // Sadly, Version doesn't have a TryParse() method until .NET 4, so we have to try/catch to see if it parses. - try - { - var version = new Version(value.Substring(1)); - type = "version"; - } - catch (Exception) - { - } + newType = "version"; } } // Not a version, check for numeric. - if (null == type) + if (newType == null) { - if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var number)) + if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _)) { - type = "numeric"; + newType = "numeric"; } else { - type = "string"; + newType = "string"; } } } - if (null == value && null != type) + if (value == null && newType != null) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type")); } - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var tuple = new WixBundleVariableTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, name)) - { - Value = value, - Type = type, - Hidden = hidden, - Persisted = persisted - }; - - this.Core.AddTuple(tuple); - } + return newType; } private class RemotePayload diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs index 80f7b875..174ac21b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -113,5 +113,49 @@ namespace WixToolsetTest.CoreIntegration "", bundleExtensionDatas[0].GetTestXml()); } } + + [Fact] + public void PopulatesManifestWithSetVariables() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SetVariable", "Simple.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-burnStub", burnStubPath, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var setVariables = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:SetVariable"); + Assert.Equal(6, setVariables.Count); + Assert.Equal("", setVariables[0].GetTestXml()); + Assert.Equal("", setVariables[1].GetTestXml()); + Assert.Equal("", setVariables[2].GetTestXml()); + Assert.Equal("", setVariables[3].GetTestXml()); + Assert.Equal("", setVariables[4].GetTestXml()); + Assert.Equal("", setVariables[5].GetTestXml()); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs new file mode 100644 index 00000000..96c92e54 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 324d04ff..921c77f9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -112,6 +112,7 @@ + -- cgit v1.2.3-55-g6feb From bf435c69fd70f5140eddd99fe02d3dcdae75473a Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 27 Mar 2020 15:49:39 +1000 Subject: Order WixSearches in Core. --- .../Bundles/OrderSearchesCommand.cs | 303 ++++++++++++++++++++- src/test/Example.Extension/Data/example.wir | Bin 535 -> 0 bytes .../TestData/SetVariable/Simple.wxs | 2 +- 3 files changed, 295 insertions(+), 10 deletions(-) delete mode 100644 src/test/Example.Extension/Data/example.wir (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs index 3f720115..874258bf 100644 --- a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs @@ -2,7 +2,9 @@ namespace WixToolset.Core.Burn.Bundles { + using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using WixToolset.Data; using WixToolset.Data.Burn; @@ -26,6 +28,292 @@ namespace WixToolset.Core.Burn.Bundles public IList OrderedSearchFacades { get; private set; } public void Execute() + { + this.ExtensionSearchTuplesByExtensionId = new Dictionary>(); + this.OrderedSearchFacades = new List(); + + var searchRelationTuples = this.Section.Tuples.OfType().ToList(); + var searchTuples = this.Section.Tuples.OfType().ToList(); + if (searchTuples.Count == 0) + { + // nothing to do! + return; + } + + var tupleDictionary = searchTuples.ToDictionary(t => t.Id.Id); + + var constraints = new Constraints(); + if (searchRelationTuples.Count > 0) + { + // add relational info to our data... + foreach (var searchRelationTuple in searchRelationTuples) + { + constraints.AddConstraint(searchRelationTuple.Id.Id, searchRelationTuple.ParentSearchRef); + } + } + + this.FindCircularReference(constraints); + + if (this.Messaging.EncounteredError) + { + return; + } + + this.FlattenDependentReferences(constraints); + + // Reorder by topographical sort (http://en.wikipedia.org/wiki/Topological_sorting) + // We use a variation of Kahn (1962) algorithm as described in + // Wikipedia, with the additional criteria that start nodes are sorted + // lexicographically at each step to ensure a deterministic ordering + // based on 'after' dependencies and ID. + var sorter = new TopologicalSort(); + var sortedIds = sorter.Sort(tupleDictionary.Keys, constraints); + + // Now, create the search facades with the searches in order... + this.OrderSearches(sortedIds, tupleDictionary); + } + + /// + /// A dictionary of constraints, mapping an id to a list of ids. + /// + private class Constraints : Dictionary> + { + public void AddConstraint(string id, string afterId) + { + if (!this.ContainsKey(id)) + { + this.Add(id, new List()); + } + + // TODO: Show warning if a constraint is seen twice? + if (!this[id].Contains(afterId)) + { + this[id].Add(afterId); + } + } + + // TODO: Hide other Add methods? + } + + /// + /// Finds circular references in the constraints. + /// + /// Constraints to check. + /// This is not particularly performant, but it works. + private void FindCircularReference(Constraints constraints) + { + foreach (string id in constraints.Keys) + { + var seenIds = new List(); + string chain = null; + if (this.FindCircularReference(constraints, id, id, seenIds, out chain)) + { + // We will show a separate message for every ID that's in + // the loop. We could bail after the first one, but then + // we wouldn't catch disjoint loops in a single run. + this.Messaging.Write(ErrorMessages.CircularSearchReference(chain)); + } + } + } + + /// + /// Recursive function that finds circular references in the constraints. + /// + /// Constraints to check. + /// The identifier currently being looking for. (Fixed across a given run.) + /// The idenifier curently being tested. + /// A list of identifiers seen, to ensure each identifier is only expanded once. + /// If a circular reference is found, will contain the chain of references. + /// True if a circular reference is found, false otherwise. + private bool FindCircularReference(Constraints constraints, string checkId, string currentId, List seenIds, out string chain) + { + chain = null; + if (constraints.TryGetValue(currentId, out var afterList)) + { + foreach (string afterId in afterList) + { + if (afterId == checkId) + { + chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, afterId); + return true; + } + + if (!seenIds.Contains(afterId)) + { + seenIds.Add(afterId); + if (this.FindCircularReference(constraints, checkId, afterId, seenIds, out chain)) + { + chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, chain); + return true; + } + } + } + } + + return false; + } + + /// + /// Flattens any dependency chains to simplify reordering. + /// + /// + private void FlattenDependentReferences(Constraints constraints) + { + foreach (string id in constraints.Keys) + { + var flattenedIds = new List(); + this.AddDependentReferences(constraints, id, flattenedIds); + var constraintList = constraints[id]; + foreach (var flattenedId in flattenedIds) + { + if (!constraintList.Contains(flattenedId)) + { + constraintList.Add(flattenedId); + } + } + } + } + + /// + /// Adds dependent references to a list. + /// + /// + /// + /// + private void AddDependentReferences(Constraints constraints, string currentId, List seenIds) + { + if (constraints.TryGetValue(currentId, out var afterList)) + { + foreach (var afterId in afterList) + { + if (!seenIds.Contains(afterId)) + { + seenIds.Add(afterId); + this.AddDependentReferences(constraints, afterId, seenIds); + } + } + } + } + + /// + /// Reorder by topological sort + /// + /// + /// We use a variation of Kahn (1962) algorithm as described in + /// Wikipedia (http://en.wikipedia.org/wiki/Topological_sorting), with + /// the additional criteria that start nodes are sorted lexicographically + /// at each step to ensure a deterministic ordering based on 'after' + /// dependencies and ID. + /// + private class TopologicalSort + { + private readonly List startIds = new List(); + private Constraints constraints; + + /// + /// Reorder by topological sort + /// + /// The complete list of IDs. + /// Constraints to use. + /// The topologically sorted list of IDs. + internal List Sort(IEnumerable allIds, Constraints constraints) + { + this.startIds.Clear(); + this.CopyConstraints(constraints); + + this.FindInitialStartIds(allIds); + + // We always create a new sortedId list, because we return it + // to the caller and don't know what its lifetime may be. + var sortedIds = new List(); + + while (this.startIds.Count > 0) + { + this.SortStartIds(); + + var currentId = this.startIds[0]; + sortedIds.Add(currentId); + this.startIds.RemoveAt(0); + + this.ResolveConstraint(currentId); + } + + return sortedIds; + } + + /// + /// Copies a Constraints set (to prevent modifying the incoming data). + /// + /// Constraints to copy. + private void CopyConstraints(Constraints constraints) + { + this.constraints = new Constraints(); + foreach (var id in constraints.Keys) + { + foreach (var afterId in constraints[id]) + { + this.constraints.AddConstraint(id, afterId); + } + } + } + + /// + /// Finds initial start IDs. (Those with no constraints.) + /// + /// The complete list of IDs. + private void FindInitialStartIds(IEnumerable allIds) + { + foreach (var id in allIds) + { + if (!this.constraints.ContainsKey(id)) + { + this.startIds.Add(id); + } + } + } + + /// + /// Sorts start IDs. + /// + private void SortStartIds() + { + this.startIds.Sort(); + } + + /// + /// Removes the resolved constraint and updates the list of startIds + /// with any now-valid (all constraints resolved) IDs. + /// + /// The ID to resolve from the set of constraints. + private void ResolveConstraint(string resolvedId) + { + var newStartIds = new List(); + + foreach (var id in this.constraints.Keys) + { + if (this.constraints[id].Contains(resolvedId)) + { + this.constraints[id].Remove(resolvedId); + + // If we just removed the last constraint for this + // ID, it is now a valid start ID. + if (this.constraints[id].Count == 0) + { + newStartIds.Add(id); + } + } + } + + foreach (var id in newStartIds) + { + this.constraints.Remove(id); + } + + this.startIds.AddRange(newStartIds); + } + } + + private void OrderSearches(List sortedIds, Dictionary searchTupleDictionary) { // TODO: Although the WixSearch tables are defined in the Util extension, // the Bundle Binder has to know all about them. We hope to revisit all @@ -42,22 +330,19 @@ namespace WixToolset.Core.Burn.Bundles var extensionSearchesById = this.Section.Tuples .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchTupleDefinitionTag)) .ToDictionary(t => t.Id.Id); - var searchTuples = this.Section.Tuples.OfType().ToList(); - - this.ExtensionSearchTuplesByExtensionId = new Dictionary>(); - this.OrderedSearchFacades = new List(legacySearchesById.Keys.Count + setVariablesById.Keys.Count + extensionSearchesById.Keys.Count); - foreach (var searchTuple in searchTuples) + foreach (var searchId in sortedIds) { - if (legacySearchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple)) + var searchTuple = searchTupleDictionary[searchId]; + if (legacySearchesById.TryGetValue(searchId, out var specificSearchTuple)) { this.OrderedSearchFacades.Add(new LegacySearchFacade(searchTuple, specificSearchTuple)); } - else if (setVariablesById.TryGetValue(searchTuple.Id.Id, out var setVariableTuple)) + else if (setVariablesById.TryGetValue(searchId, out var setVariableTuple)) { this.OrderedSearchFacades.Add(new SetVariableSearchFacade(searchTuple, setVariableTuple)); } - else if (extensionSearchesById.TryGetValue(searchTuple.Id.Id, out var extensionSearchTuple)) + else if (extensionSearchesById.TryGetValue(searchId, out var extensionSearchTuple)) { this.OrderedSearchFacades.Add(new ExtensionSearchFacade(searchTuple)); @@ -70,7 +355,7 @@ namespace WixToolset.Core.Burn.Bundles } else { - this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchTuple.SourceLineNumbers, searchTuple.Id.Id)); + this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchTuple.SourceLineNumbers, searchId)); } } } diff --git a/src/test/Example.Extension/Data/example.wir b/src/test/Example.Extension/Data/example.wir deleted file mode 100644 index d1ee8b90..00000000 Binary files a/src/test/Example.Extension/Data/example.wir and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs index 96c92e54..7e8f2e99 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs @@ -9,7 +9,7 @@ + - -- cgit v1.2.3-55-g6feb From a26c9ac0e9b02360b298ae5c619ca4070d11ae9a Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 3 Apr 2020 16:14:32 +1000 Subject: Add failing test for when EnsureTable references an extension table with a different name than its tuple. --- .../Example.Extension/ExampleTableDefinitions.cs | 5 ++-- .../ExampleWindowsInstallerBackendExtension.cs | 5 +++- .../MsiQueryFixture.cs | 34 ++++++++++++++++++++++ .../TestData/EnsureTable/EnsureTable.wxs | 10 +++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/Example.Extension/ExampleTableDefinitions.cs b/src/test/Example.Extension/ExampleTableDefinitions.cs index dbd6491b..5dbf6d64 100644 --- a/src/test/Example.Extension/ExampleTableDefinitions.cs +++ b/src/test/Example.Extension/ExampleTableDefinitions.cs @@ -1,4 +1,4 @@ -// 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. +// 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 Example.Extension { @@ -7,7 +7,8 @@ namespace Example.Extension public static class ExampleTableDefinitions { public static readonly TableDefinition ExampleTable = new TableDefinition( - "Example", + "Wix4Example", + ExampleTupleDefinitions.Example.Name, new[] { new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), diff --git a/src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs b/src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs index d6741bc1..af9c8489 100644 --- a/src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs +++ b/src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs @@ -1,13 +1,16 @@ -// 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. +// 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 Example.Extension { + using System.Collections.Generic; using WixToolset.Data; using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility; internal class ExampleWindowsInstallerBackendExtension : BaseWindowsInstallerBackendBinderExtension { + public override IEnumerable TableDefinitions => ExampleTableDefinitions.All; + public override bool TryAddTupleToOutput(IntermediateTuple tuple, WindowsInstallerData output) { #if ALTERNATIVE_TO_USING_HELPER diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index d93b3d54..0010f3f2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -2,8 +2,10 @@ namespace WixToolsetTest.CoreIntegration { + using System; using System.IO; using System.Linq; + using Example.Extension; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using Xunit; @@ -526,6 +528,38 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void PopulatesExampleTableBecauseOfEnsureTable() + { + var folder = TestData.Get(@"TestData"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "EnsureTable", "EnsureTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabaseByTable(msiPath, new[] { "Wix4Example" }); + Assert.Empty(results["Wix4Example"]); + } + } + [Fact] public void PopulatesFeatureTableWithParent() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs new file mode 100644 index 00000000..01767abb --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 921c77f9..98402223 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -37,6 +37,7 @@ + -- cgit v1.2.3-55-g6feb From fcd88ec3995033bf802f0a637e7fce65e8739006 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 5 Apr 2020 19:15:44 +1000 Subject: Add test around ActionText and fix the table definition to get it passing. --- src/WixToolset.Core.WindowsInstaller/Data/tables.xml | 2 +- src/test/Example.Extension/Example.Extension.csproj | 2 +- src/test/Example.Extension/ExampleTableDefinitions.cs | 7 ++++--- src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 2 ++ .../TestData/CustomAction/UnscheduledCustomAction.wxs | 8 ++++++-- 5 files changed, 14 insertions(+), 7 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Data/tables.xml b/src/WixToolset.Core.WindowsInstaller/Data/tables.xml index 7cd1767b..e28e39c2 100644 --- a/src/WixToolset.Core.WindowsInstaller/Data/tables.xml +++ b/src/WixToolset.Core.WindowsInstaller/Data/tables.xml @@ -3,7 +3,7 @@ - + - + diff --git a/src/test/Example.Extension/ExampleTableDefinitions.cs b/src/test/Example.Extension/ExampleTableDefinitions.cs index 5dbf6d64..124e2406 100644 --- a/src/test/Example.Extension/ExampleTableDefinitions.cs +++ b/src/test/Example.Extension/ExampleTableDefinitions.cs @@ -8,13 +8,14 @@ namespace Example.Extension { public static readonly TableDefinition ExampleTable = new TableDefinition( "Wix4Example", - ExampleTupleDefinitions.Example.Name, new[] { new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), - } - ); + }, + tupleDefinitionName: ExampleTupleDefinitions.Example.Name, + tupleIdIsPrimaryKey: true + ); public static readonly TableDefinition[] All = new[] { ExampleTable }; } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 6b9f8af6..9462c4ff 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -313,6 +313,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { + "ActionText", "AdminExecuteSequence", "AdminUISequence", "AdvtExecuteSequence", @@ -324,6 +325,7 @@ namespace WixToolsetTest.CoreIntegration }).Where(x => !x.StartsWith("Property:") || x.StartsWith("Property:MsiHiddenProperties\t")).ToArray(); Assert.Equal(new[] { + "ActionText:CustomAction2\tProgess2Text\t", "AdminExecuteSequence:CostFinalize\t\t1000", "AdminExecuteSequence:CostInitialize\t\t800", "AdminExecuteSequence:CustomAction2\t\t801", diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs index 780529d6..2846d16e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs @@ -2,10 +2,10 @@ - + - + @@ -25,5 +25,9 @@ + + + Progess2Text + -- cgit v1.2.3-55-g6feb From 59ffa86b7d62ddc52ec813fb65c47f812aeded66 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 6 Apr 2020 14:06:29 +1000 Subject: Try to fix build flakiness with Example.Extension. Add failing test for the TableDefinition overload of EnsureTable. --- .../Example.Extension/Example.Extension.csproj | 2 +- .../Example.Extension/ExampleCompilerExtension.cs | 10 ++++++ .../Example.Extension/ExampleTableDefinitions.cs | 10 ++++++ .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 36 ++++++++++++++++++++++ .../TestData/BadEnsureTable/BadEnsureTable.wxs | 11 +++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 6 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/Example.Extension/Example.Extension.csproj b/src/test/Example.Extension/Example.Extension.csproj index d9ac2509..7f375cb6 100644 --- a/src/test/Example.Extension/Example.Extension.csproj +++ b/src/test/Example.Extension/Example.Extension.csproj @@ -33,7 +33,7 @@ - + diff --git a/src/test/Example.Extension/ExampleCompilerExtension.cs b/src/test/Example.Extension/ExampleCompilerExtension.cs index e821b7b6..9f0abd4c 100644 --- a/src/test/Example.Extension/ExampleCompilerExtension.cs +++ b/src/test/Example.Extension/ExampleCompilerExtension.cs @@ -23,6 +23,10 @@ namespace Example.Extension case "Fragment": switch (element.Name.LocalName) { + case "ExampleEnsureTable": + this.ParseExampleEnsureTableElement(intermediate, section, element); + processed = true; + break; case "ExampleSearch": this.ParseExampleSearchElement(intermediate, section, element); processed = true; @@ -93,6 +97,12 @@ namespace Example.Extension } } + private void ParseExampleEnsureTableElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + this.ParseHelper.EnsureTable(section, sourceLineNumbers, ExampleTableDefinitions.NotInAll); + } + private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element) { var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); diff --git a/src/test/Example.Extension/ExampleTableDefinitions.cs b/src/test/Example.Extension/ExampleTableDefinitions.cs index 124e2406..3532ffc3 100644 --- a/src/test/Example.Extension/ExampleTableDefinitions.cs +++ b/src/test/Example.Extension/ExampleTableDefinitions.cs @@ -17,6 +17,16 @@ namespace Example.Extension tupleIdIsPrimaryKey: true ); + public static readonly TableDefinition NotInAll = new TableDefinition( + "TableDefinitionNotExposedByExtension", + new[] + { + new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), + new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), + }, + tupleIdIsPrimaryKey: true + ); + public static readonly TableDefinition[] All = new[] { ExampleTable }; } } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 2141e68c..ad24f346 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -2,8 +2,10 @@ namespace WixToolsetTest.CoreIntegration { + using System; using System.IO; using System.Linq; + using Example.Extension; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using WixToolset.Data; @@ -882,5 +884,39 @@ namespace WixToolsetTest.CoreIntegration Assert.NotEmpty(output.SubStorages); } } + + [Fact(Skip = "Test demonstrates failure")] + public void FailsBuildAtLinkTimeForMissingEnsureTable() + { + var folder = TestData.Get(@"TestData"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadEnsureTable", "BadEnsureTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + Assert.Collection(result.Messages, + first => + { + Assert.Equal(MessageLevel.Error, first.Level); + Assert.Equal("The identifier 'WixCustomTable:TableDefinitionNotExposedByExtension' could not be found. Ensure you have typed the reference correctly and that all the necessary inputs are provided to the linker.", first.ToString()); + }); + + Assert.False(File.Exists(msiPath)); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs new file mode 100644 index 00000000..3caa20ff --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 98402223..dbc9357c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -21,6 +21,7 @@ + -- cgit v1.2.3-55-g6feb From 7c04bfdb54ccf5b4b67995c9715a3f7f9e454206 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 7 Apr 2020 11:20:36 +1000 Subject: Fix bugs when compiling UI.wixext --- .../Data/tables.xml | 2 +- src/WixToolset.Core/Compiler.cs | 2 +- src/WixToolset.Core/Compiler_UI.cs | 34 ++++----- .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 41 ----------- .../MsiQueryFixture.cs | 82 ++++++++++++++++++++++ .../DialogsInInstallUISequence/Package.en-us.wxl | 13 ---- .../DialogsInInstallUISequence/Package.wxs | 21 ------ .../PackageComponents.wxs | 10 ++- .../DialogsInInstallUISequence/data/test.txt | 1 - .../TestData/TextStyle/SizeLocalized.en-us.wxl | 13 ++++ .../TestData/TextStyle/SizeLocalized.wxs | 12 ++++ .../WixToolsetTest.CoreIntegration.csproj | 5 +- 12 files changed, 136 insertions(+), 100 deletions(-) delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/data/test.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Data/tables.xml b/src/WixToolset.Core.WindowsInstaller/Data/tables.xml index e28e39c2..a0c7ff55 100644 --- a/src/WixToolset.Core.WindowsInstaller/Data/tables.xml +++ b/src/WixToolset.Core.WindowsInstaller/Data/tables.xml @@ -165,7 +165,7 @@ - + Element to parse. /// Identifier for parent dialog. /// Table control belongs in. - /// Last row in the tab order. + /// Last control in the tab order. /// Name of the first control in the tab order. /// Name of the default control. /// Name of the candle control. /// True if the containing dialog tracks disk space. - private void ParseControlElement(XElement node, string dialog, TupleDefinitionType tupleType, ref IntermediateTuple lastTabTuple, ref string firstControl, ref string defaultControl, ref string cancelControl) + private void ParseControlElement(XElement node, string dialog, TupleDefinitionType tupleType, ref ControlTuple lastTabTuple, ref string firstControl, ref string defaultControl, ref string cancelControl) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier controlId = null; @@ -1529,7 +1529,15 @@ namespace WixToolset.Core if (!notTabbable) { - if (TupleDefinitionType.BBControl == tupleType) + if (tuple is ControlTuple controlTuple) + { + if (null != lastTabTuple) + { + lastTabTuple.NextControlRef = controlTuple.Control; + } + lastTabTuple = controlTuple; + } + else if (tuple != null) { this.Core.Write(ErrorMessages.TabbableControlNotAllowedInBillboard(sourceLineNumbers, node.Name.LocalName, controlType)); } @@ -1538,12 +1546,6 @@ namespace WixToolset.Core { firstControl = controlId.Id; } - - if (null != lastTabTuple) - { - lastTabTuple.Set(10, controlId.Id); - } - lastTabTuple = tuple; } // bitmap and icon controls contain a foreign key into the binary table in the text column; diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index ad24f346..64b3fa93 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -214,47 +214,6 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Currently fails")] - public void CanBuildDialogsInInstallUISequence() - { - var folder = TestData.Get(@"TestData\DialogsInInstallUISequence"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"test.wir")); - var section = intermediate.Sections.Single(); - - var textStyle = section.Tuples.OfType().Single(); - Assert.Equal("Tahoma", textStyle.FaceName); - Assert.Equal(8, textStyle.Size); - - var installUIActions = section.Tuples.OfType() - .Where(t => t.SequenceTable == SequenceTable.InstallUISequence) - .ToList(); - Assert.Equal(10, installUIActions.Count); - } - } - [Fact] public void CanBuildWithErrorTable() { diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 9462c4ff..bb44395f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -254,6 +254,54 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void PopulatesControlTables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DialogsInInstallUISequence", "PackageComponents.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + + var results = Query.QueryDatabase(msiPath, new[] { "CheckBox", "Control", "InstallUISequence" }); + Assert.Equal(new[] + { + "CheckBox:WIXUI_EXITDIALOGOPTIONALCHECKBOX\t1", + "Control:FirstDialog\tHeader\tText\t0\t13\t90\t13\t3\tFirstDialogHeader\tTitle\t\t", + "Control:FirstDialog\tTitle\tText\t0\t0\t90\t13\t3\tFirstDialogTitle\tHeader\t\t", + "Control:SecondDialog\tOptionalCheckBox\tCheckBox\t0\t13\t100\t40\t2\t[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]\tTitle\t\t", + "Control:SecondDialog\tTitle\tText\t0\t0\t90\t13\t3\tSecondDialogTitle\tOptionalCheckBox\t\t", + "InstallUISequence:CostFinalize\t\t1000", + "InstallUISequence:CostInitialize\t\t800", + "InstallUISequence:ExecuteAction\t\t1300", + "InstallUISequence:FileCost\t\t900", + "InstallUISequence:FindRelatedProducts\t\t25", + "InstallUISequence:FirstDialog\tInstalled AND PATCH\t1298", + "InstallUISequence:LaunchConditions\t\t100", + "InstallUISequence:MigrateFeatureStates\t\t1200", + "InstallUISequence:SecondDialog\tNOT Installed\t1299", + "InstallUISequence:ValidateProductID\t\t700", + }, results); + } + } + [Fact] public void PopulatesCreateFolderTableForNullKeypathComponents() { @@ -982,6 +1030,40 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesTextStyleTableWhenSizeIsLocalized() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "TextStyle", "SizeLocalized.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-loc", Path.Combine(folder, "TextStyle", "SizeLocalized.en-us.wxl"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); + Assert.Equal(new[] + { + "TextStyle:CustomFont\tTahoma\t8\t\t", + }, results); + } + } + [Fact] public void PopulatesTypeLibTableWhenLanguageIsZero() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.en-us.wxl deleted file mode 100644 index 77d46861..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.en-us.wxl +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - Tahoma - 8 - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.wxs deleted file mode 100644 index 6da3dcbe..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/Package.wxs +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs index 724c46ed..1101d901 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs @@ -2,17 +2,21 @@ - - + + Installed + Installed + + - NOT Installed diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl new file mode 100644 index 00000000..77d46861 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl @@ -0,0 +1,13 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + Tahoma + 8 + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs new file mode 100644 index 00000000..a591fdd9 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index dbc9357c..13611770 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -34,9 +34,6 @@ - - - @@ -157,6 +154,8 @@ + + -- cgit v1.2.3-55-g6feb From 8a42828f169796f01d9821790e4983639062d3b9 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 9 Apr 2020 14:33:18 +1000 Subject: Fix bug when parsing PayloadGroupRef. --- src/WixToolset.Core/Compiler_Bundle.cs | 2 +- .../SimpleBundle/MultiFileBootstrapperApplication.wxs | 2 +- .../TestData/SimpleBundle/MultiFileBundle.wxs | 19 +++++++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 5d7072d0..ff746f8d 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -1342,7 +1342,7 @@ namespace WixToolset.Core this.Core.ParseForExtensionElements(node); - this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId.Id, ComplexReferenceChildType.PayloadGroup, id.Id, previousType, previousId.Id); + this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id?.Id, previousType, previousId?.Id); return id; } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs index 2d36934f..88c4cf1b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs @@ -1,6 +1,6 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs index 205c58ca..48f53ae3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs @@ -1,11 +1,18 @@ - - - - - - + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 2d23530fde970972c927680ee3df6466538ae8ca Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 23 Apr 2020 08:33:03 +1000 Subject: Add and fix some bundle tests. --- .../Bundles/ProcessPayloadsCommand.cs | 4 +- .../BundleFixture.cs | 59 ++++++++++++++++++++++ .../SingleExeBundle/SingleExePackageGroup.wxs | 8 +++ .../SingleExeBundle/SingleExeRemotePayload.wxs | 31 ++++++++++++ .../WixToolsetTest.CoreIntegration.csproj | 2 + 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs index d0c1fdfc..42b1b5ab 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs @@ -56,7 +56,7 @@ namespace WixToolset.Core.Burn.Bundles // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden // in the .wixlib). var sourceFile = payload.SourceFile; - payload.ContentFile = !sourceFile.Embed; + payload.ContentFile = sourceFile != null && !sourceFile.Embed; this.UpdatePayloadPackagingType(payload); @@ -85,7 +85,7 @@ namespace WixToolset.Core.Burn.Bundles private void UpdatePayloadPackagingType(WixBundlePayloadTuple payload) { - if (PackagingType.Unknown == payload.Packaging) + if (!payload.Packaging.HasValue || PackagingType.Unknown == payload.Packaging) { if (!payload.Compressed.HasValue) { diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index 31cfed34..6e66aa74 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -140,5 +140,64 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); } } + + [Fact] + public void CanBuildSingleExeBundle() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-burnStub", burnStubPath, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + [Fact] + public void CanBuildSingleExeRemotePayloadBundle() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SingleExeBundle", "SingleExeRemotePayload.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-burnStub", burnStubPath, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs new file mode 100644 index 00000000..9d7a9511 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs new file mode 100644 index 00000000..709dc9e7 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 13611770..60cbde85 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -117,6 +117,8 @@ + + -- cgit v1.2.3-55-g6feb From bac3d761d99fb7ae1012f3591baee2dbec115b28 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 11 May 2020 17:09:47 -0400 Subject: Fix named bind paths. --- src/WixToolset.Core/Bind/FileResolver.cs | 42 +++++++++++++++------- src/WixToolset.Core/CommandLine/CommandLine.cs | 2 -- .../TestData/WixlibWithBinaries/Package.en-us.wxl | 11 ++++++ .../TestData/WixlibWithBinaries/Package.wxs | 21 +++++++++++ .../WixlibWithBinaries/PackageComponents.wxs | 26 ++++++++++++++ .../TestData/WixlibWithBinaries/data/alpha/foo.dll | 1 + .../TestData/WixlibWithBinaries/data/mips/foo.dll | 1 + .../WixlibWithBinaries/data/powerpc/foo.dll | 1 + .../TestData/WixlibWithBinaries/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 7 ++++ .../WixlibFixture.cs | 36 +++++++++++++++++++ 11 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Bind/FileResolver.cs b/src/WixToolset.Core/Bind/FileResolver.cs index b1676fad..6bc5a676 100644 --- a/src/WixToolset.Core/Bind/FileResolver.cs +++ b/src/WixToolset.Core/Bind/FileResolver.cs @@ -115,7 +115,7 @@ namespace WixToolset.Core.Bind } else // not a rooted path so let's try applying all the different source resolution options. { - string bindName = String.Empty; + string bindName = null; var path = source; string pathWithoutSourceDir = null; @@ -138,25 +138,41 @@ namespace WixToolset.Core.Bind foreach (var bindPath in bindPaths) { - if (!String.IsNullOrEmpty(pathWithoutSourceDir)) + if (!String.IsNullOrEmpty(bindName) && !String.IsNullOrEmpty(bindPath.Name)) { - var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) + if (String.Equals(bindName, bindPath.Name, StringComparison.OrdinalIgnoreCase) && String.IsNullOrEmpty(resolved)) { - resolved = filePath; + var filePath = Path.Combine(bindPath.Path, path); + + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } } } - - if (String.IsNullOrEmpty(resolved)) + else { - var filePath = Path.Combine(bindPath.Path, path); + if (!String.IsNullOrEmpty(pathWithoutSourceDir)) + { + var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } + } + + if (String.IsNullOrEmpty(resolved)) { - resolved = filePath; + var filePath = Path.Combine(bindPath.Path, path); + + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } } } } diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs index 683d1f5a..79f5d5bc 100644 --- a/src/WixToolset.Core/CommandLine/CommandLine.cs +++ b/src/WixToolset.Core/CommandLine/CommandLine.cs @@ -21,8 +21,6 @@ namespace WixToolset.Core.CommandLine internal class CommandLine : ICommandLine { - private static readonly char[] BindPathSplit = { '=' }; - public CommandLine(IWixToolsetServiceProvider serviceProvider) { this.ServiceProvider = serviceProvider; diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs new file mode 100644 index 00000000..85dcb695 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs new file mode 100644 index 00000000..7d1a4ae1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 60cbde85..3989699d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -172,6 +172,13 @@ + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs index f63d1144..63771248 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs @@ -52,6 +52,42 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildWixlibWithBinariesFromNamedBindPaths() + { + var folder = TestData.Get(@"TestData\WixlibWithBinaries"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-bf", + "-bindpath", Path.Combine(folder, "data"), + // Use names that aren't excluded in default .gitignores. + "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", + "-bindpath", $"MipsBits={Path.Combine(folder, "data", "mips")}", + "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var wixlib = Intermediate.Load(wixlibPath); + var binaryTuples = wixlib.Sections.SelectMany(s => s.Tuples).OfType().ToList(); + Assert.Equal(3, binaryTuples.Count); + Assert.Single(binaryTuples.Where(t => t.Data.Path == "wix-ir/foo.dll")); + Assert.Single(binaryTuples.Where(t => t.Data.Path == "wix-ir/foo.dll-1")); + Assert.Single(binaryTuples.Where(t => t.Data.Path == "wix-ir/foo.dll-2")); + } + } + [Fact] public void CanBuildSingleFileUsingWixlib() { -- cgit v1.2.3-55-g6feb From 4d96895a19c79ced1543d44e181527824c82c8e8 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 16 May 2020 16:38:14 +1000 Subject: Process Unreal custom tables in CreateBootstrapperApplicationManifestCommand --- ...CreateBootstrapperApplicationManifestCommand.cs | 47 ++++++++++++++++++++++ .../BundleManifestFixture.cs | 39 ++++++++++++++++++ .../BundleCustomTable/BundleCustomTable.wxs | 26 ++++++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 4 files changed, 113 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs index 84c02ac9..2a230a90 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -16,6 +16,8 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateBootstrapperApplicationManifestCommand { + private static readonly char[] ColonCharacter = new[] { ':' }; + public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable chainPackages, int lastUXPayloadIndex, Dictionary payloadTuples, string intermediateFolder) { this.Section = section; @@ -277,6 +279,51 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); } } + + var dataTablesById = this.Section.Tuples.OfType() + .Where(t => t.Unreal && t.Id != null) + .ToDictionary(t => t.Id.Id); + var dataRowsByTable = this.Section.Tuples.OfType() + .GroupBy(t => t.Table); + foreach (var tableDataRows in dataRowsByTable) + { + var tableName = tableDataRows.Key; + if (!dataTablesById.TryGetValue(tableName, out var tableTuple)) + { + // This should have been a linker error. + continue; + } + + var columnNames = tableTuple.ColumnNames.Split('\t'); + + // We simply assert that the table (and field) name is valid, because + // this is up to the extension developer to get right. An author will + // only affect the attribute value, and that will get properly escaped. +#if DEBUG + Debug.Assert(Common.IsIdentifier(tableName)); + foreach (var columnName in columnNames) + { + Debug.Assert(Common.IsIdentifier(columnName)); + } +#endif // DEBUG + + foreach (var rowTuple in tableDataRows) + { + writer.WriteStartElement(tableName); + + //var rowFields = rowTuple.FieldDataSeparated; + foreach (var field in rowTuple.FieldDataSeparated) + { + var splitField = field.Split(ColonCharacter, 2); + if (splitField.Length == 2) + { + writer.WriteAttributeString(splitField[0], splitField[1]); + } + } + + writer.WriteEndElement(); + } + } } private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs index ebfdb872..53036919 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -12,6 +12,45 @@ namespace WixToolsetTest.CoreIntegration public class BundleManifestFixture { + [Fact] + public void PopulatesBAManifestWithUnrealCustomTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTable"); + Assert.Equal(3, customElements.Count); + Assert.Equal("", customElements[0].GetTestXml()); + Assert.Equal("", customElements[1].GetTestXml()); + Assert.Equal("", customElements[2].GetTestXml()); + } + } + [Fact] public void PopulatesManifestWithBundleExtension() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs new file mode 100644 index 00000000..dacbc014 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + one + two + + + < + > + + + 1 + 2 + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 3989699d..0651ec7a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -22,6 +22,7 @@ + -- cgit v1.2.3-55-g6feb From 6b30680fd7a712b45538c3f0a89d652f0457a893 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 22 May 2020 14:53:27 -0700 Subject: Support merging merge modules --- .../Bind/BindDatabaseCommand.cs | 5 +- .../Bind/ConfigurationCallback.cs | 1 + .../Bind/ExtractMergeModuleFilesCommand.cs | 3 +- .../Bind/MergeModulesCommand.cs | 10 +- .../Bind/UpdateMediaSequencesCommand.cs | 2 +- .../Msi/MsmInterop.cs | 505 --------------------- src/WixToolset.Core/Linker.cs | 4 +- .../MsiQueryFixture.cs | 50 ++ .../TestData/SimpleMerge/.data/test.msm | Bin 0 -> 24576 bytes .../TestData/SimpleMerge/Package.en-us.wxl | 11 + .../TestData/SimpleMerge/Package.wxs | 24 + .../WixToolsetTest.CoreIntegration.csproj | 3 + 12 files changed, 105 insertions(+), 513 deletions(-) delete mode 100644 src/WixToolset.Core.WindowsInstaller/Msi/MsmInterop.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 32da410f..22858d1f 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -505,7 +505,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // necessary. foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) { - var sequenceTableName = sequence.ToString(); + var sequenceTableName = (sequence == SequenceTable.AdvertiseExecuteSequence) ? "AdvtExecuteSequence" : sequence.ToString(); var sequenceTable = output.Tables[sequenceTableName]; if (null == sequenceTable) @@ -519,8 +519,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - var command = new MergeModulesCommand(); + var command = new MergeModulesCommand(this.Messaging); command.FileFacades = fileFacades; + command.IntermediateFolder = this.IntermediateFolder; command.Output = output; command.OutputPath = this.OutputPath; command.SuppressedTableNames = suppressedTableNames; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs index 0cc5996a..9a609463 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs @@ -5,6 +5,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System; using System.Collections; using System.Globalization; + using WixToolset.Core.Native; using WixToolset.Core.WindowsInstaller.Msi; /// diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs index 49b6a6f8..62f7fce3 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs @@ -48,7 +48,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var mergeModulesFileFacades = new List(); - var merge = MsmInterop.GetMsmMerge(); + var interop = new MsmInterop(); + var merge = interop.GetMsmMerge(); // Index all of the file rows to be able to detect collisions with files in the Merge Modules. // It may seem a bit expensive to build up this index solely for the purpose of checking collisions diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs index b90aecd1..cd6170d0 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -9,9 +9,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System.Runtime.InteropServices; using System.Text; using WixToolset.Core.Bind; + using WixToolset.Core.Native; using WixToolset.Core.WindowsInstaller.Msi; using WixToolset.Data; - using WixToolset.Data.Tuples; using WixToolset.Data.WindowsInstaller; using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility.Services; @@ -21,6 +21,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class MergeModulesCommand { + public MergeModulesCommand(IMessaging messaging) + { + this.Messaging = messaging; + } + public IEnumerable FileFacades { private get; set; } public IMessaging Messaging { private get; set; } @@ -51,7 +56,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind string logPath = null; try { - merge = MsmInterop.GetMsmMerge(); + var interop = new MsmInterop(); + merge = interop.GetMsmMerge(); logPath = Path.Combine(this.IntermediateFolder, "merge.log"); merge.OpenLog(logPath); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs index ae872f45..5d18a230 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs @@ -74,7 +74,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind patchGroup.Add(facade); } - else + else if (!facade.FromModule) { var fileRow = fileRows.Get(facade.Id); fileRow.Sequence = ++lastSequence; diff --git a/src/WixToolset.Core.WindowsInstaller/Msi/MsmInterop.cs b/src/WixToolset.Core.WindowsInstaller/Msi/MsmInterop.cs deleted file mode 100644 index 970d5aaa..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Msi/MsmInterop.cs +++ /dev/null @@ -1,505 +0,0 @@ -// 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.Msi -{ - using System; - using System.Runtime.InteropServices; - - /// - /// Errors returned by merge operations. - /// - [Guid("0ADDA825-2C26-11D2-AD65-00A0C9AF11A6")] - public enum MsmErrorType - { - /// - /// A request was made to open a module with a language not supported by the module. - /// No more general language is supported by the module. - /// Adds msmErrorLanguageUnsupported to the Type property and the requested language - /// to the Language Property (Error Object). All Error object properties are empty. - /// The OpenModule function returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). - /// - msmErrorLanguageUnsupported = 1, - - /// - /// A request was made to open a module with a supported language but the module has - /// an invalid language transform. Adds msmErrorLanguageFailed to the Type property - /// and the applied transform's language to the Language Property of the Error object. - /// This may not be the requested language if a more general language was used. - /// All other properties of the Error object are empty. The OpenModule function - /// returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). - /// - msmErrorLanguageFailed = 2, - - /// - /// The module cannot be merged because it excludes, or is excluded by, another module - /// in the database. Adds msmErrorExclusion to the Type property of the Error object. - /// The ModuleKeys property or DatabaseKeys property contains the primary keys of the - /// excluded module's row in the ModuleExclusion table. If an existing module excludes - /// the module being merged, the excluded module's ModuleSignature information is added - /// to ModuleKeys. If the module being merged excludes an existing module, DatabaseKeys - /// contains the excluded module's ModuleSignature information. All other properties - /// are empty (or -1). - /// - msmErrorExclusion = 3, - - /// - /// Merge conflict during merge. The value of the Type property is set to - /// msmErrorTableMerge. The DatabaseTable property and DatabaseKeys property contain - /// the table name and primary keys of the conflicting row in the database. The - /// ModuleTable property and ModuleKeys property contain the table name and primary keys - /// of the conflicting row in the module. The ModuleTable and ModuleKeys entries may be - /// null if the row does not exist in the database. For example, if the conflict is in a - /// generated FeatureComponents table entry. On Windows Installer version 2.0, when - /// merging a configurable merge module, configuration may cause these properties to - /// refer to rows that do not exist in the module. - /// - msmErrorTableMerge = 4, - - /// - /// There was a problem resequencing a sequence table to contain the necessary merged - /// actions. The Type property is set to msmErrorResequenceMerge. The DatabaseTable - /// and DatabaseKeys properties contain the sequence table name and primary keys - /// (action name) of the conflicting row. The ModuleTable and ModuleKeys properties - /// contain the sequence table name and primary key (action name) of the conflicting row. - /// On Windows Installer version 2.0, when merging a configurable merge module, - /// configuration may cause these properties to refer to rows that do not exist in the module. - /// - msmErrorResequenceMerge = 5, - - /// - /// Not used. - /// - msmErrorFileCreate = 6, - - /// - /// There was a problem creating a directory to extract a file to disk. The Path property - /// contains the directory that could not be created. All other properties are empty or -1. - /// Not available with Windows Installer version 1.0. - /// - msmErrorDirCreate = 7, - - /// - /// A feature name is required to complete the merge, but no feature name was provided. - /// The Type property is set to msmErrorFeatureRequired. The DatabaseTable and DatabaseKeys - /// contain the table name and primary keys of the conflicting row. The ModuleTable and - /// ModuleKeys properties contain the table name and primary keys of the row cannot be merged. - /// On Windows Installer version 2.0, when merging a configurable merge module, configuration - /// may cause these properties to refer to rows that do not exist in the module. - /// If the failure is in a generated FeatureComponents table, the DatabaseTable and - /// DatabaseKeys properties are empty and the ModuleTable and ModuleKeys properties refer to - /// the row in the Component table causing the failure. - /// - msmErrorFeatureRequired = 8, - - /// - /// Available with Window Installer version 2.0. Substitution of a Null value into a - /// non-nullable column. This enters msmErrorBadNullSubstitution in the Type property and - /// enters "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row - /// into the ModuleTable property and ModuleKeys property. All other properties of the Error - /// object are set to an empty string or -1. This error causes the immediate failure of the - /// merge and the MergeEx function to return E_FAIL. - /// - msmErrorBadNullSubstitution = 9, - - /// - /// Available with Window Installer version 2.0. Substitution of Text Format Type or Integer - /// Format Type into a Binary Type data column. This type of error returns - /// msmErrorBadSubstitutionType in the Type property and enters "ModuleSubstitution" and the - /// keys from the ModuleSubstitution table for this row into the ModuleTable property. - /// All other properties of the Error object are set to an empty string or -1. This error - /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. - /// - msmErrorBadSubstitutionType = 10, - - /// - /// Available with Window Installer Version 2.0. A row in the ModuleSubstitution table - /// references a configuration item not defined in the ModuleConfiguration table. - /// This type of error returns msmErrorMissingConfigItem in the Type property and enters - /// "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row into - /// the ModuleTable property. All other properties of the Error object are set to an empty - /// string or -1. This error causes the immediate failure of the merge and the MergeEx - /// function to return E_FAIL. - /// - msmErrorMissingConfigItem = 11, - - /// - /// Available with Window Installer version 2.0. The authoring tool has returned a Null - /// value for an item marked with the msmConfigItemNonNullable attribute. An error of this - /// type returns msmErrorBadNullResponse in the Type property and enters "ModuleSubstitution" - /// and the keys from the ModuleSubstitution table for for the item into the ModuleTable property. - /// All other properties of the Error object are set to an empty string or -1. This error - /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. - /// - msmErrorBadNullResponse = 12, - - /// - /// Available with Window Installer version 2.0. The authoring tool returned a failure code - /// (not S_OK or S_FALSE) when asked for data. An error of this type will return - /// msmErrorDataRequestFailed in the Type property and enters "ModuleSubstitution" - /// and the keys from the ModuleSubstitution table for the item into the ModuleTable property. - /// All other properties of the Error object are set to an empty string or -1. This error - /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. - /// - msmErrorDataRequestFailed = 13, - - /// - /// Available with Windows Installer 2.0 and later versions. Indicates that an attempt was - /// made to merge a 64-bit module into a package that was not a 64-bit package. An error of - /// this type returns msmErrorPlatformMismatch in the Type property. All other properties of - /// the error object are set to an empty string or -1. This error causes the immediate failure - /// of the merge and causes the Merge function or MergeEx function to return E_FAIL. - /// - msmErrorPlatformMismatch = 14, - } - - /// - /// IMsmMerge2 interface. - /// - [ComImport, Guid("351A72AB-21CB-47ab-B7AA-C4D7B02EA305")] - public interface IMsmMerge2 - { - /// - /// The OpenDatabase method of the Merge object opens a Windows Installer installation - /// database, located at a specified path, that is to be merged with a module. - /// - /// Path to the database being opened. - void OpenDatabase(string path); - - /// - /// The OpenModule method of the Merge object opens a Windows Installer merge module - /// in read-only mode. A module must be opened before it can be merged with an installation database. - /// - /// Fully qualified file name pointing to a merge module. - /// A valid language identifier (LANGID). - void OpenModule(string fileName, short language); - - /// - /// The CloseDatabase method of the Merge object closes the currently open Windows Installer database. - /// - /// true if changes should be saved, false otherwise. - void CloseDatabase(bool commit); - - /// - /// The CloseModule method of the Merge object closes the currently open Windows Installer merge module. - /// - void CloseModule(); - - /// - /// The OpenLog method of the Merge object opens a log file that receives progress and error messages. - /// If the log file already exists, the installer appends new messages. If the log file does not exist, - /// the installer creates a log file. - /// - /// Fully qualified filename pointing to a file to open or create. - void OpenLog(string fileName); - - /// - /// The CloseLog method of the Merge object closes the current log file. - /// - void CloseLog(); - - /// - /// The Log method of the Merge object writes a text string to the currently open log file. - /// - /// The text string to display. - void Log(string message); - - /// - /// Gets the errors from the last merge operation. - /// - /// The errors from the last merge operation. - IMsmErrors Errors - { - get; - } - - /// - /// Gets a collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. - /// - /// A collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. - object Dependencies - { - get; - } - - /// - /// The Merge method of the Merge object executes a merge of the current database and current - /// module. The merge attaches the components in the module to the feature identified by Feature. - /// The root of the module's directory tree is redirected to the location given by RedirectDir. - /// - /// The name of a feature in the database. - /// The key of an entry in the Directory table of the database. - /// This parameter may be NULL or an empty string. - void Merge(string feature, string redirectDir); - - /// - /// The Connect method of the Merge object connects a module to an additional feature. - /// The module must have already been merged into the database or will be merged into the database. - /// The feature must exist before calling this function. - /// - /// The name of a feature already existing in the database. - void Connect(string feature); - - /// - /// The ExtractCAB method of the Merge object extracts the embedded .cab file from a module and - /// saves it as the specified file. The installer creates this file if it does not already exist - /// and overwritten if it does exist. - /// - /// The fully qualified destination file. - void ExtractCAB(string fileName); - - /// - /// The ExtractFiles method of the Merge object extracts the embedded .cab file from a module - /// and then writes those files to the destination directory. - /// - /// The fully qualified destination directory. - void ExtractFiles(string path); - - /// - /// The MergeEx method of the Merge object is equivalent to the Merge function, except that it - /// takes an extra argument. The Merge method executes a merge of the current database and - /// current module. The merge attaches the components in the module to the feature identified - /// by Feature. The root of the module's directory tree is redirected to the location given by RedirectDir. - /// - /// The name of a feature in the database. - /// The key of an entry in the Directory table of the database. This parameter may - /// be NULL or an empty string. - /// The pConfiguration argument is an interface implemented by the client. The argument may - /// be NULL. The presence of this argument indicates that the client is capable of supporting the configuration - /// functionality, but does not obligate the client to provide configuration data for any specific configurable item. - void MergeEx(string feature, string redirectDir, IMsmConfigureModule configuration); - - /// - /// The ExtractFilesEx method of the Merge object extracts the embedded .cab file from a module and - /// then writes those files to the destination directory. - /// - /// The fully qualified destination directory. - /// Set to specify using long file names for path segments and final file names. - /// This is a list of fully-qualified paths for the files that were successfully extracted. - /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null. - void ExtractFilesEx(string path, bool longFileNames, ref IntPtr filePaths); - - /// - /// Gets a collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. - /// - /// A collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. - /// Semantically, each interface in the enumerator represents an item that can be configured by the module consumer. - /// The collection is a read-only collection and implements the standard read-only collection interfaces of Item(), Count() and _NewEnum(). - /// The IEnumMsmConfigItems enumerator implements Next(), Skip(), Reset(), and Clone() with the standard semantics. - object ConfigurableItems - { - get; - } - - /// - /// The CreateSourceImage method of the Merge object allows the client to extract the files from a module to - /// a source image on disk after a merge, taking into account changes to the module that might have been made - /// during module configuration. The list of files to be extracted is taken from the file table of the module - /// during the merge process. The list of files consists of every file successfully copied from the file table - /// of the module to the target database. File table entries that were not copied due to primary key conflicts - /// with existing rows in the database are not a part of this list. At image creation time, the directory for - /// each of these files comes from the open (post-merge) database. The path specified in the Path parameter is - /// the root of the source image for the install. fLongFileNames determines whether or not long file names are - /// used for both path segments and final file names. The function fails if no database is open, no module is - /// open, or no merge has been performed. - /// - /// The path of the root of the source image for the install. - /// Determines whether or not long file names are used for both path segments and final file names. - /// This is a list of fully-qualified paths for the files that were successfully extracted. - /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null. - void CreateSourceImage(string path, bool longFileNames, ref IntPtr filePaths); - - /// - /// The get_ModuleFiles function implements the ModuleFiles property of the GetFiles object. This function - /// returns the primary keys in the File table of the currently open module. The primary keys are returned - /// as a collection of strings. The module must be opened by a call to the OpenModule function before calling get_ModuleFiles. - /// - IMsmStrings ModuleFiles - { - get; - } - } - - /// - /// Collection of merge errors. - /// - [ComImport, Guid("0ADDA82A-2C26-11D2-AD65-00A0C9AF11A6")] - public interface IMsmErrors - { - /// - /// Gets the IMsmError at the specified index. - /// - /// The one-based index of the IMsmError to get. - IMsmError this[int index] - { - get; - } - - /// - /// Gets the count of IMsmErrors in this collection. - /// - /// The count of IMsmErrors in this collection. - int Count - { - get; - } - } - - /// - /// A merge error. - /// - [ComImport, Guid("0ADDA828-2C26-11D2-AD65-00A0C9AF11A6")] - public interface IMsmError - { - /// - /// Gets the type of merge error. - /// - /// The type of merge error. - MsmErrorType Type - { - get; - } - - /// - /// Gets the path information from the merge error. - /// - /// The path information from the merge error. - string Path - { - get; - } - - /// - /// Gets the language information from the merge error. - /// - /// The language information from the merge error. - short Language - { - get; - } - - /// - /// Gets the database table from the merge error. - /// - /// The database table from the merge error. - string DatabaseTable - { - get; - } - - /// - /// Gets the collection of database keys from the merge error. - /// - /// The collection of database keys from the merge error. - IMsmStrings DatabaseKeys - { - get; - } - - /// - /// Gets the module table from the merge error. - /// - /// The module table from the merge error. - string ModuleTable - { - get; - } - - /// - /// Gets the collection of module keys from the merge error. - /// - /// The collection of module keys from the merge error. - IMsmStrings ModuleKeys - { - get; - } - } - - /// - /// A collection of strings. - /// - [ComImport, Guid("0ADDA827-2C26-11D2-AD65-00A0C9AF11A6")] - public interface IMsmStrings - { - /// - /// Gets the string at the specified index. - /// - /// The one-based index of the string to get. - string this[int index] - { - get; - } - - /// - /// Gets the count of strings in this collection. - /// - /// The count of strings in this collection. - int Count - { - get; - } - } - - /// - /// Callback for configurable merge modules. - /// - [ComImport, Guid("AC013209-18A7-4851-8A21-2353443D70A0"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] - public interface IMsmConfigureModule - { - /// - /// Callback to retrieve text data for configurable merge modules. - /// - /// Name of the data to be retrieved. - /// The data corresponding to the name. - /// The error code (HRESULT). - [PreserveSig] - int ProvideTextData([In, MarshalAs(UnmanagedType.BStr)] string name, [MarshalAs(UnmanagedType.BStr)] out string configData); - - /// - /// Callback to retrieve integer data for configurable merge modules. - /// - /// Name of the data to be retrieved. - /// The data corresponding to the name. - /// The error code (HRESULT). - [PreserveSig] - int ProvideIntegerData([In, MarshalAs(UnmanagedType.BStr)] string name, out int configData); - } - - /// - /// Merge merge modules into an MSI file. - /// - [ComImport, Guid("F94985D5-29F9-4743-9805-99BC3F35B678")] - public class MsmMerge2 - { - } - - /// - /// Defines the standard COM IClassFactory interface. - /// - [ComImport, Guid("00000001-0000-0000-C000-000000000046")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IClassFactory - { - [return:MarshalAs(UnmanagedType.IUnknown)] - object CreateInstance(IntPtr unkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); - } - - /// - /// Contains native methods for merge operations. - /// - public class MsmInterop - { - [DllImport("mergemod.dll", EntryPoint="DllGetClassObject", PreserveSig=false)] - [return: MarshalAs(UnmanagedType.IUnknown)] - private static extern object MergeModGetClassObject([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); - - /// - /// Load the merge object directly from a local mergemod.dll without going through COM registration. - /// - /// Merge interface. - public static IMsmMerge2 GetMsmMerge() - { - IClassFactory classFactory = (IClassFactory) MergeModGetClassObject(typeof(MsmMerge2).GUID, typeof(IClassFactory).GUID); - return (IMsmMerge2) classFactory.CreateInstance(IntPtr.Zero, typeof(IMsmMerge2).GUID); - } - } -} diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index 98232901..7b381347 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -356,7 +356,7 @@ namespace WixToolset.Core case TupleDefinitionType.WixMerge: if (SectionType.Product == resolvedSection.Type) { - this.ResolveFeatures(tuple, 0, 7, modulesToFeatures, null); + this.ResolveFeatures(tuple, -1, (int)WixMergeTupleFields.FeatureRef, modulesToFeatures, null); } break; @@ -1316,7 +1316,7 @@ namespace WixToolset.Core /// Hashtable of known components under multiple features. private void ResolveFeatures(IntermediateTuple tuple, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents) { - var connectionId = tuple.AsString(connectionColumn); + var connectionId = connectionColumn < 0 ? tuple.Id.Id : tuple.AsString(connectionColumn); var featureId = tuple.AsString(featureColumn); if (EmptyGuid == featureId) diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index bb44395f..aa8a0a0d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -8,6 +8,9 @@ namespace WixToolsetTest.CoreIntegration using Example.Extension; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; using Xunit; public class MsiQueryFixture @@ -1171,5 +1174,52 @@ namespace WixToolsetTest.CoreIntegration }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p)); } } + + [Fact] + public void CanMergeModule() + { + var folder = TestData.Get(@"TestData\SimpleMerge"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); + var cabPath = Path.Combine(intermediateFolder, @"bin\cab1.cab"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, ".data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + Assert.Empty(section.Tuples.OfType()); + + var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + Assert.Null(data.Tables["File"]); + + var results = Query.QueryDatabase(msiPath, new[] { "File" }); + Assert.Equal(new[] + { + "File:filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent.243FB739_4D05_472F_9CFB_EF6B1017B6DE\ttest.txt\t17\t\t\t512\t0" + }, results); + + var files = Query.GetCabinetFiles(cabPath); + Assert.Equal(new[] + { + "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" + }, files.Select(f => f.Name).ToArray()); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm new file mode 100644 index 00000000..6f179aba Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs new file mode 100644 index 00000000..303e2ba8 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 0651ec7a..9d2cf1d6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -77,6 +77,9 @@ + + + -- cgit v1.2.3-55-g6feb From 9c54d2fce80983bbee5f0f113b5aa30f22bc8a23 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 23 May 2020 21:54:53 +1000 Subject: Add failing test for RegistryKey without Id and child extension element. --- .../BadInputFixture.cs | 36 ++++++++++++++++++++++ .../TestData/BadInput/RegistryKey.wxs | 13 ++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 50 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs new file mode 100644 index 00000000..8482dcbe --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs @@ -0,0 +1,36 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class BadInputFixture + { + [Fact(Skip = "Test demonstrates failure")] + public void RegistryKeyWithoutAttributesDoesntCrash() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "RegistryKey.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs new file mode 100644 index 00000000..c717680b --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 9d2cf1d6..f9f1ba44 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -22,6 +22,7 @@ + -- cgit v1.2.3-55-g6feb From 9317f7c8ea709da55e4602eaaba06952bbf315b7 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 3 Jun 2020 02:19:16 -0700 Subject: Redesign CustomTable tuples to support resolving binary columns --- ...CreateBootstrapperApplicationManifestCommand.cs | 27 +- .../Bind/CreateOutputFromIRCommand.cs | 447 +++++++++++---------- .../Bind/GenerateDatabaseCommand.cs | 7 +- .../Bind/LoadTableDefinitionsCommand.cs | 257 ++++++------ src/WixToolset.Core/Bind/ResolveFieldsCommand.cs | 57 +-- src/WixToolset.Core/Compiler.cs | 275 +++++++------ .../BundleManifestFixture.cs | 2 +- .../MsiQueryFixture.cs | 115 ++++++ .../TestData/CustomTable/CustomTableWithFile.wxs | 22 + .../CustomTable/LocalizedCustomTable.en-us.wxl | 7 + .../TestData/CustomTable/LocalizedCustomTable.wxs | 21 + .../TestData/CustomTable/data/file1.txt | 1 + .../TestData/CustomTable/data/file2.txt | 1 + .../TestData/CustomTable/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 6 + 15 files changed, 731 insertions(+), 515 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs index 2a230a90..dba2a9ba 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -281,20 +281,20 @@ namespace WixToolset.Core.Burn.Bundles } var dataTablesById = this.Section.Tuples.OfType() - .Where(t => t.Unreal && t.Id != null) - .ToDictionary(t => t.Id.Id); - var dataRowsByTable = this.Section.Tuples.OfType() - .GroupBy(t => t.Table); - foreach (var tableDataRows in dataRowsByTable) + .Where(t => t.Unreal && t.Id != null) + .ToDictionary(t => t.Id.Id); + var cellsByTable = this.Section.Tuples.OfType() + .GroupBy(t => t.TableRef); + foreach (var tableCells in cellsByTable) { - var tableName = tableDataRows.Key; + var tableName = tableCells.Key; if (!dataTablesById.TryGetValue(tableName, out var tableTuple)) { // This should have been a linker error. continue; } - var columnNames = tableTuple.ColumnNames.Split('\t'); + var columnNames = tableTuple.ColumnNamesSeparated; // We simply assert that the table (and field) name is valid, because // this is up to the extension developer to get right. An author will @@ -307,17 +307,18 @@ namespace WixToolset.Core.Burn.Bundles } #endif // DEBUG - foreach (var rowTuple in tableDataRows) + foreach (var rowCells in tableCells.GroupBy(t => t.RowId)) { + var rowDataByColumn = rowCells.ToDictionary(t => t.ColumnRef, t => t.Data); + writer.WriteStartElement(tableName); - //var rowFields = rowTuple.FieldDataSeparated; - foreach (var field in rowTuple.FieldDataSeparated) + // Write all row data as attributes in table column order. + foreach (var column in columnNames) { - var splitField = field.Split(ColonCharacter, 2); - if (splitField.Length == 2) + if (rowDataByColumn.TryGetValue(column, out var data)) { - writer.WriteAttributeString(splitField[0], splitField[1]); + writer.WriteAttributeString(column, data); } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index ffc4e84d..0c1aa312 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -18,8 +18,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) - private static readonly char[] ColonCharacter = new[] { ':' }; - public CreateOutputFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, IEnumerable backendExtensions, IWindowsInstallerBackendHelper backendHelper) { this.Messaging = messaging; @@ -54,171 +52,174 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void AddSectionToOutput() { + var cellsByTableAndRowId = new Dictionary>(); + foreach (var tuple in this.Section.Tuples) { var unknownTuple = false; switch (tuple.Definition.Type) { - case TupleDefinitionType.AppSearch: - this.AddTupleDefaultly(tuple); - this.Output.EnsureTable(this.TableDefinitions["Signature"]); - break; + case TupleDefinitionType.AppSearch: + this.AddTupleDefaultly(tuple); + this.Output.EnsureTable(this.TableDefinitions["Signature"]); + break; - case TupleDefinitionType.Assembly: - this.AddAssemblyTuple((AssemblyTuple)tuple); - break; + case TupleDefinitionType.Assembly: + this.AddAssemblyTuple((AssemblyTuple)tuple); + break; - case TupleDefinitionType.BBControl: - this.AddBBControlTuple((BBControlTuple)tuple); - break; + case TupleDefinitionType.BBControl: + this.AddBBControlTuple((BBControlTuple)tuple); + break; - case TupleDefinitionType.Class: - this.AddClassTuple((ClassTuple)tuple); - break; + case TupleDefinitionType.Class: + this.AddClassTuple((ClassTuple)tuple); + break; - case TupleDefinitionType.Control: - this.AddControlTuple((ControlTuple)tuple); - break; + case TupleDefinitionType.Control: + this.AddControlTuple((ControlTuple)tuple); + break; - case TupleDefinitionType.Component: - this.AddComponentTuple((ComponentTuple)tuple); - break; + case TupleDefinitionType.Component: + this.AddComponentTuple((ComponentTuple)tuple); + break; - case TupleDefinitionType.CustomAction: - this.AddCustomActionTuple((CustomActionTuple)tuple); - break; + case TupleDefinitionType.CustomAction: + this.AddCustomActionTuple((CustomActionTuple)tuple); + break; - case TupleDefinitionType.Dialog: - this.AddDialogTuple((DialogTuple)tuple); - break; + case TupleDefinitionType.Dialog: + this.AddDialogTuple((DialogTuple)tuple); + break; - case TupleDefinitionType.Directory: - this.AddDirectoryTuple((DirectoryTuple)tuple); - break; + case TupleDefinitionType.Directory: + this.AddDirectoryTuple((DirectoryTuple)tuple); + break; - case TupleDefinitionType.Environment: - this.AddEnvironmentTuple((EnvironmentTuple)tuple); - break; + case TupleDefinitionType.Environment: + this.AddEnvironmentTuple((EnvironmentTuple)tuple); + break; - case TupleDefinitionType.Error: - this.AddErrorTuple((ErrorTuple)tuple); - break; + case TupleDefinitionType.Error: + this.AddErrorTuple((ErrorTuple)tuple); + break; - case TupleDefinitionType.Feature: - this.AddFeatureTuple((FeatureTuple)tuple); - break; + case TupleDefinitionType.Feature: + this.AddFeatureTuple((FeatureTuple)tuple); + break; - case TupleDefinitionType.File: - this.AddFileTuple((FileTuple)tuple); - break; + case TupleDefinitionType.File: + this.AddFileTuple((FileTuple)tuple); + break; - case TupleDefinitionType.IniFile: - this.AddIniFileTuple((IniFileTuple)tuple); - break; + case TupleDefinitionType.IniFile: + this.AddIniFileTuple((IniFileTuple)tuple); + break; - case TupleDefinitionType.Media: - this.AddMediaTuple((MediaTuple)tuple); - break; + case TupleDefinitionType.Media: + this.AddMediaTuple((MediaTuple)tuple); + break; - case TupleDefinitionType.ModuleConfiguration: - this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple); - break; + case TupleDefinitionType.ModuleConfiguration: + this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple); + break; - case TupleDefinitionType.MsiEmbeddedUI: - this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple); - break; + case TupleDefinitionType.MsiEmbeddedUI: + this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple); + break; - case TupleDefinitionType.MsiServiceConfig: - this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple); - break; + case TupleDefinitionType.MsiServiceConfig: + this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple); + break; - case TupleDefinitionType.MsiServiceConfigFailureActions: - this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple); - break; + case TupleDefinitionType.MsiServiceConfigFailureActions: + this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple); + break; - case TupleDefinitionType.MoveFile: - this.AddMoveFileTuple((MoveFileTuple)tuple); - break; + case TupleDefinitionType.MoveFile: + this.AddMoveFileTuple((MoveFileTuple)tuple); + break; - case TupleDefinitionType.ProgId: - this.AddTupleDefaultly(tuple); - this.Output.EnsureTable(this.TableDefinitions["Extension"]); - break; + case TupleDefinitionType.ProgId: + this.AddTupleDefaultly(tuple); + this.Output.EnsureTable(this.TableDefinitions["Extension"]); + break; - case TupleDefinitionType.Property: - this.AddPropertyTuple((PropertyTuple)tuple); - break; + case TupleDefinitionType.Property: + this.AddPropertyTuple((PropertyTuple)tuple); + break; - case TupleDefinitionType.RemoveFile: - this.AddRemoveFileTuple((RemoveFileTuple)tuple); - break; + case TupleDefinitionType.RemoveFile: + this.AddRemoveFileTuple((RemoveFileTuple)tuple); + break; - case TupleDefinitionType.Registry: - this.AddRegistryTuple((RegistryTuple)tuple); - break; + case TupleDefinitionType.Registry: + this.AddRegistryTuple((RegistryTuple)tuple); + break; - case TupleDefinitionType.RegLocator: - this.AddRegLocatorTuple((RegLocatorTuple)tuple); - break; + case TupleDefinitionType.RegLocator: + this.AddRegLocatorTuple((RegLocatorTuple)tuple); + break; - case TupleDefinitionType.RemoveRegistry: - this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple); - break; + case TupleDefinitionType.RemoveRegistry: + this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple); + break; - case TupleDefinitionType.ServiceControl: - this.AddServiceControlTuple((ServiceControlTuple)tuple); - break; + case TupleDefinitionType.ServiceControl: + this.AddServiceControlTuple((ServiceControlTuple)tuple); + break; - case TupleDefinitionType.ServiceInstall: - this.AddServiceInstallTuple((ServiceInstallTuple)tuple); - break; + case TupleDefinitionType.ServiceInstall: + this.AddServiceInstallTuple((ServiceInstallTuple)tuple); + break; - case TupleDefinitionType.Shortcut: - this.AddShortcutTuple((ShortcutTuple)tuple); - break; + case TupleDefinitionType.Shortcut: + this.AddShortcutTuple((ShortcutTuple)tuple); + break; - case TupleDefinitionType.TextStyle: - this.AddTextStyleTuple((TextStyleTuple)tuple); - break; + case TupleDefinitionType.TextStyle: + this.AddTextStyleTuple((TextStyleTuple)tuple); + break; - case TupleDefinitionType.Upgrade: - this.AddUpgradeTuple((UpgradeTuple)tuple); - break; + case TupleDefinitionType.Upgrade: + this.AddUpgradeTuple((UpgradeTuple)tuple); + break; - case TupleDefinitionType.WixAction: - this.AddWixActionTuple((WixActionTuple)tuple); - break; + case TupleDefinitionType.WixAction: + this.AddWixActionTuple((WixActionTuple)tuple); + break; - case TupleDefinitionType.WixMediaTemplate: - this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple); - break; + case TupleDefinitionType.WixMediaTemplate: + this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple); + break; - case TupleDefinitionType.WixCustomRow: - this.AddWixCustomRowTuple((WixCustomRowTuple)tuple); - break; + case TupleDefinitionType.WixCustomTableCell: + this.IndexCustomTableCellTuple((WixCustomTableCellTuple)tuple, cellsByTableAndRowId); + break; - case TupleDefinitionType.WixEnsureTable: - this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple); - break; + case TupleDefinitionType.WixEnsureTable: + this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple); + break; - // ignored. - case TupleDefinitionType.WixComponentGroup: - case TupleDefinitionType.WixDeltaPatchFile: - case TupleDefinitionType.WixFeatureGroup: - case TupleDefinitionType.WixPatchBaseline: + // ignored. + case TupleDefinitionType.WixComponentGroup: + case TupleDefinitionType.WixDeltaPatchFile: + case TupleDefinitionType.WixFeatureGroup: + case TupleDefinitionType.WixPatchBaseline: break; - // Already processed. - case TupleDefinitionType.WixCustomTable: - break; + // Already processed by LoadTableDefinitions. + case TupleDefinitionType.WixCustomTable: + case TupleDefinitionType.WixCustomTableColumn: + break; - case TupleDefinitionType.MustBeFromAnExtension: - unknownTuple = !this.AddTupleFromExtension(tuple); - break; + case TupleDefinitionType.MustBeFromAnExtension: + unknownTuple = !this.AddTupleFromExtension(tuple); + break; - default: - unknownTuple = !this.AddTupleDefaultly(tuple); - break; + default: + unknownTuple = !this.AddTupleDefaultly(tuple); + break; } if (unknownTuple) @@ -226,6 +227,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.Messaging.Write(WarningMessages.TupleNotTranslatedToOutput(tuple)); } } + + this.AddIndexedCellTuples(cellsByTableAndRowId); } private void AddAssemblyTuple(AssemblyTuple tuple) @@ -383,16 +386,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void AddDialogTuple(DialogTuple tuple) { var attributes = tuple.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0; - attributes|= tuple.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; - attributes|= tuple.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0; - attributes|= tuple.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette: 0; - attributes|= tuple.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0; - attributes|= tuple.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0; - attributes|= tuple.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0; - attributes|= tuple.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0; - attributes|= tuple.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0; - attributes|= tuple.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; - attributes|= tuple.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; + attributes |= tuple.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; + attributes |= tuple.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0; + attributes |= tuple.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette : 0; + attributes |= tuple.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0; + attributes |= tuple.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0; + attributes |= tuple.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0; + attributes |= tuple.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0; + attributes |= tuple.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0; + attributes |= tuple.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; + attributes |= tuple.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; var row = this.CreateRow(tuple, "Dialog"); row[0] = tuple.Id.Id; @@ -419,7 +422,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind targetName = "."; } - var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName ; + var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName; var row = this.CreateRow(tuple, "Directory"); row[0] = tuple.Id.Id; @@ -436,25 +439,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind switch (tuple.Action) { - case EnvironmentActionType.Create: - action = "+"; - break; - case EnvironmentActionType.Set: - action = "="; - break; - case EnvironmentActionType.Remove: - action = "!"; - break; + case EnvironmentActionType.Create: + action = "+"; + break; + case EnvironmentActionType.Set: + action = "="; + break; + case EnvironmentActionType.Remove: + action = "!"; + break; } switch (tuple.Part) { - case EnvironmentPartType.First: - value = String.Concat(value, tuple.Separator, "[~]"); - break; - case EnvironmentPartType.Last: - value = String.Concat("[~]", tuple.Separator, value); - break; + case EnvironmentPartType.First: + value = String.Concat(value, tuple.Separator, "[~]"); + break; + case EnvironmentPartType.Last: + value = String.Concat("[~]", tuple.Separator, value); + break; } var row = this.CreateRow(tuple, "Environment"); @@ -661,40 +664,40 @@ namespace WixToolset.Core.WindowsInstaller.Bind switch (tuple.ValueType) { - case RegistryValueType.Binary: - value = String.Concat("#x", value); - break; - case RegistryValueType.Expandable: - value = String.Concat("#%", value); - break; - case RegistryValueType.Integer: - value = String.Concat("#", value); - break; - case RegistryValueType.MultiString: - switch (tuple.ValueAction) - { - case RegistryValueActionType.Append: - value = String.Concat("[~]", value); + case RegistryValueType.Binary: + value = String.Concat("#x", value); break; - case RegistryValueActionType.Prepend: - value = String.Concat(value, "[~]"); + case RegistryValueType.Expandable: + value = String.Concat("#%", value); break; - case RegistryValueActionType.Write: - default: - if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) + case RegistryValueType.Integer: + value = String.Concat("#", value); + break; + case RegistryValueType.MultiString: + switch (tuple.ValueAction) { - value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); + case RegistryValueActionType.Append: + value = String.Concat("[~]", value); + break; + case RegistryValueActionType.Prepend: + value = String.Concat(value, "[~]"); + break; + case RegistryValueActionType.Write: + default: + if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) + { + value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); + } + break; + } + break; + case RegistryValueType.String: + // escape the leading '#' character for string registry keys + if (null != value && value.StartsWith("#", StringComparison.Ordinal)) + { + value = String.Concat("#", value); } break; - } - break; - case RegistryValueType.String: - // escape the leading '#' character for string registry keys - if (null != value && value.StartsWith("#", StringComparison.Ordinal)) - { - value = String.Concat("#", value); - } - break; } var row = this.CreateRow(tuple, "Registry"); @@ -757,7 +760,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[3] = tuple.Arguments; if (tuple.Wait.HasValue) { - row[4] = tuple.Wait.Value ? 1 : 0; + row[4] = tuple.Wait.Value ? 1 : 0; } row[5] = tuple.ComponentRef; } @@ -938,83 +941,89 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[2] = tuple.Sequence; } } - - private void AddWixCustomRowTuple(WixCustomRowTuple tuple) - { - var customTableDefinition = this.TableDefinitions[tuple.Table]; - if (customTableDefinition.Unreal) + private void IndexCustomTableCellTuple(WixCustomTableCellTuple wixCustomTableCellTuple, Dictionary> cellsByTableAndRowId) + { + var tableAndRowId = wixCustomTableCellTuple.TableRef + "/" + wixCustomTableCellTuple.RowId; + if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells)) { - - return; + cells = new List(); + cellsByTableAndRowId.Add(tableAndRowId, cells); } - var customRow = this.CreateRow(tuple, customTableDefinition); + cells.Add(wixCustomTableCellTuple); + } -#if TODO // SectionId seems like a good thing to preserve. - customRow.SectionId = tuple.SectionId; -#endif + private void AddIndexedCellTuples(Dictionary> cellsByTableAndRowId) + { + foreach (var rowOfCells in cellsByTableAndRowId.Values) + { + var firstCellTuple = rowOfCells[0]; + var customTableDefinition = this.TableDefinitions[firstCellTuple.TableRef]; - var data = tuple.FieldDataSeparated; + if (customTableDefinition.Unreal) + { + return; + } - for (var i = 0; i < data.Length; ++i) - { - var foundColumn = false; - var item = data[i].Split(ColonCharacter, 2); + var customRow = this.CreateRow(firstCellTuple, customTableDefinition); + var customRowFieldsByColumnName = customRow.Fields.ToDictionary(f => f.Column.Name); - for (var j = 0; j < customRow.Fields.Length; ++j) +#if TODO // SectionId seems like a good thing to preserve. + customRow.SectionId = tuple.SectionId; +#endif + foreach (var cell in rowOfCells) { - if (customRow.Fields[j].Column.Name == item[0]) + var data = cell.Data; + + if (customRowFieldsByColumnName.TryGetValue(cell.ColumnRef, out var rowField)) { - if (0 < item[1].Length) + if (!String.IsNullOrEmpty(data)) { - if (ColumnType.Number == customRow.Fields[j].Column.Type) + if (rowField.Column.Type == ColumnType.Number) { try { - customRow.Fields[j].Data = Convert.ToInt32(item[1], CultureInfo.InvariantCulture); + rowField.Data = Convert.ToInt32(data, CultureInfo.InvariantCulture); } catch (FormatException) { - this.Messaging.Write(ErrorMessages.IllegalIntegerValue(tuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1])); + this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); } catch (OverflowException) { - this.Messaging.Write(ErrorMessages.IllegalIntegerValue(tuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1])); + this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); } } - else if (ColumnCategory.Identifier == customRow.Fields[j].Column.Category) + else if (rowField.Column.Category == ColumnCategory.Identifier) { - if (Common.IsIdentifier(item[1]) || Common.IsValidBinderVariable(item[1]) || ColumnCategory.Formatted == customRow.Fields[j].Column.Category) + if (Common.IsIdentifier(data) || Common.IsValidBinderVariable(data) || ColumnCategory.Formatted == rowField.Column.Category) { - customRow.Fields[j].Data = item[1]; + rowField.Data = data; } else { - this.Messaging.Write(ErrorMessages.IllegalIdentifier(tuple.SourceLineNumbers, "Data", item[1])); + this.Messaging.Write(ErrorMessages.IllegalIdentifier(cell.SourceLineNumbers, "Data", data)); } } else { - customRow.Fields[j].Data = item[1]; + rowField.Data = data; } } - foundColumn = true; - break; + } + else + { + this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(cell.SourceLineNumbers, cell.ColumnRef)); } } - if (!foundColumn) - { - this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(tuple.SourceLineNumbers, item[0])); - } - } - - for (var i = 0; i < customTableDefinition.Columns.Length; ++i) - { - if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) + for (var i = 0; i < customTableDefinition.Columns.Length; ++i) { - this.Messaging.Write(ErrorMessages.NoDataForColumn(tuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); + if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) + { + this.Messaging.Write(ErrorMessages.NoDataForColumn(firstCellTuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); + } } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs index eff94e80..6edbdd1c 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs @@ -314,18 +314,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind break; case ColumnType.Object: - if (null != row[i]) + var path = row.FieldAsString(i); + if (null != path) { needStream = true; try { - record.SetStream(i + 1, row.FieldAsString(i)); + record.SetStream(i + 1, path); } catch (Win32Exception e) { if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME { - throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, row.FieldAsString(i))); + throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, path)); } else { diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs index eba7bdbe..d7809034 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs @@ -32,11 +32,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind public TableDefinitionCollection Execute() { var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + var customColumnsById = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); - foreach (var tuple in this.Section.Tuples.OfType()) + if (customColumnsById.Any()) { - var customTableDefinition = this.CreateCustomTable(tuple); - tableDefinitions.Add(customTableDefinition); + foreach (var tuple in this.Section.Tuples.OfType()) + { + var customTableDefinition = this.CreateCustomTable(tuple, customColumnsById); + tableDefinitions.Add(customTableDefinition); + } } foreach (var backendExtension in this.BackendExtensions) @@ -56,177 +60,150 @@ namespace WixToolset.Core.WindowsInstaller.Bind return this.TableDefinitions; } - private TableDefinition CreateCustomTable(WixCustomTableTuple tuple) + private TableDefinition CreateCustomTable(WixCustomTableTuple tuple, Dictionary customColumnsById) { - var columnNames = tuple.ColumnNames.Split('\t'); - var columnTypes = tuple.ColumnTypes.Split('\t'); - var primaryKeys = tuple.PrimaryKeys.Split('\t'); - var minValues = tuple.MinValues?.Split('\t'); - var maxValues = tuple.MaxValues?.Split('\t'); - var keyTables = tuple.KeyTables?.Split('\t'); - var keyColumns = tuple.KeyColumns?.Split('\t'); - var categories = tuple.Categories?.Split('\t'); - var sets = tuple.Sets?.Split('\t'); - var descriptions = tuple.Descriptions?.Split('\t'); - var modularizations = tuple.Modularizations?.Split('\t'); - - var currentPrimaryKey = 0; - + var columnNames = tuple.ColumnNamesSeparated; var columns = new List(columnNames.Length); - for (var i = 0; i < columnNames.Length; ++i) + + foreach (var name in columnNames) { - var name = columnNames[i]; + var column = customColumnsById[tuple.Id.Id + "/" + name]; + var type = ColumnType.Unknown; - if (columnTypes[i].StartsWith("s", StringComparison.OrdinalIgnoreCase)) - { - type = ColumnType.String; - } - else if (columnTypes[i].StartsWith("l", StringComparison.OrdinalIgnoreCase)) + if (column.Type == IntermediateFieldType.String) { - type = ColumnType.Localized; + type = column.Localizable ? ColumnType.Localized : ColumnType.String; } - else if (columnTypes[i].StartsWith("i", StringComparison.OrdinalIgnoreCase)) + else if (column.Type == IntermediateFieldType.Number) { type = ColumnType.Number; } - else if (columnTypes[i].StartsWith("v", StringComparison.OrdinalIgnoreCase)) + else if (column.Type == IntermediateFieldType.Path) { type = ColumnType.Object; } - var nullable = columnTypes[i].Substring(0, 1) == columnTypes[i].Substring(0, 1).ToUpperInvariant(); - var length = Convert.ToInt32(columnTypes[i].Substring(1), CultureInfo.InvariantCulture); - - var primaryKey = false; - if (currentPrimaryKey < primaryKeys.Length && primaryKeys[currentPrimaryKey] == columnNames[i]) - { - primaryKey = true; - currentPrimaryKey++; - } - - var minValue = String.IsNullOrEmpty(minValues?[i]) ? (int?)null : Convert.ToInt32(minValues[i], CultureInfo.InvariantCulture); - var maxValue = String.IsNullOrEmpty(maxValues?[i]) ? (int?)null : Convert.ToInt32(maxValues[i], CultureInfo.InvariantCulture); - var keyColumn = String.IsNullOrEmpty(keyColumns?[i]) ? (int?)null : Convert.ToInt32(keyColumns[i], CultureInfo.InvariantCulture); - var category = ColumnCategory.Unknown; - if (null != categories && null != categories[i] && 0 < categories[i].Length) + switch (column.Category) { - switch (categories[i]) - { - case "Text": - category = ColumnCategory.Text; - break; - case "UpperCase": - category = ColumnCategory.UpperCase; - break; - case "LowerCase": - category = ColumnCategory.LowerCase; - break; - case "Integer": - category = ColumnCategory.Integer; - break; - case "DoubleInteger": - category = ColumnCategory.DoubleInteger; - break; - case "TimeDate": - category = ColumnCategory.TimeDate; - break; - case "Identifier": - category = ColumnCategory.Identifier; - break; - case "Property": - category = ColumnCategory.Property; - break; - case "Filename": - category = ColumnCategory.Filename; - break; - case "WildCardFilename": - category = ColumnCategory.WildCardFilename; - break; - case "Path": - category = ColumnCategory.Path; - break; - case "Paths": - category = ColumnCategory.Paths; - break; - case "AnyPath": - category = ColumnCategory.AnyPath; - break; - case "DefaultDir": - category = ColumnCategory.DefaultDir; - break; - case "RegPath": - category = ColumnCategory.RegPath; - break; - case "Formatted": - category = ColumnCategory.Formatted; - break; - case "FormattedSddl": - category = ColumnCategory.FormattedSDDLText; - break; - case "Template": - category = ColumnCategory.Template; - break; - case "Condition": - category = ColumnCategory.Condition; - break; - case "Guid": - category = ColumnCategory.Guid; - break; - case "Version": - category = ColumnCategory.Version; - break; - case "Language": - category = ColumnCategory.Language; - break; - case "Binary": - category = ColumnCategory.Binary; - break; - case "CustomSource": - category = ColumnCategory.CustomSource; - break; - case "Cabinet": - category = ColumnCategory.Cabinet; - break; - case "Shortcut": - category = ColumnCategory.Shortcut; - break; - default: - break; - } + case "Text": + category = ColumnCategory.Text; + break; + case "UpperCase": + category = ColumnCategory.UpperCase; + break; + case "LowerCase": + category = ColumnCategory.LowerCase; + break; + case "Integer": + category = ColumnCategory.Integer; + break; + case "DoubleInteger": + category = ColumnCategory.DoubleInteger; + break; + case "TimeDate": + category = ColumnCategory.TimeDate; + break; + case "Identifier": + category = ColumnCategory.Identifier; + break; + case "Property": + category = ColumnCategory.Property; + break; + case "Filename": + category = ColumnCategory.Filename; + break; + case "WildCardFilename": + category = ColumnCategory.WildCardFilename; + break; + case "Path": + category = ColumnCategory.Path; + break; + case "Paths": + category = ColumnCategory.Paths; + break; + case "AnyPath": + category = ColumnCategory.AnyPath; + break; + case "DefaultDir": + category = ColumnCategory.DefaultDir; + break; + case "RegPath": + category = ColumnCategory.RegPath; + break; + case "Formatted": + category = ColumnCategory.Formatted; + break; + case "FormattedSddl": + category = ColumnCategory.FormattedSDDLText; + break; + case "Template": + category = ColumnCategory.Template; + break; + case "Condition": + category = ColumnCategory.Condition; + break; + case "Guid": + category = ColumnCategory.Guid; + break; + case "Version": + category = ColumnCategory.Version; + break; + case "Language": + category = ColumnCategory.Language; + break; + case "Binary": + category = ColumnCategory.Binary; + break; + case "CustomSource": + category = ColumnCategory.CustomSource; + break; + case "Cabinet": + category = ColumnCategory.Cabinet; + break; + case "Shortcut": + category = ColumnCategory.Shortcut; + break; + default: + break; } - var keyTable = keyTables?[i]; - var setValue = sets?[i]; - var description = descriptions?[i]; - var modString = modularizations?[i]; var modularization = ColumnModularizeType.None; - switch (modString) + switch (column.Modularize) { case null: - case "None": + case WixCustomTableColumnModularizeType.None: modularization = ColumnModularizeType.None; break; - case "Column": + case WixCustomTableColumnModularizeType.Column: modularization = ColumnModularizeType.Column; break; - case "Property": - modularization = ColumnModularizeType.Property; + case WixCustomTableColumnModularizeType.CompanionFile: + modularization = ColumnModularizeType.CompanionFile; break; - case "Condition": + case WixCustomTableColumnModularizeType.Condition: modularization = ColumnModularizeType.Condition; break; - case "CompanionFile": - modularization = ColumnModularizeType.CompanionFile; + case WixCustomTableColumnModularizeType.ControlEventArgument: + modularization = ColumnModularizeType.ControlEventArgument; + break; + case WixCustomTableColumnModularizeType.ControlText: + modularization = ColumnModularizeType.ControlText; + break; + case WixCustomTableColumnModularizeType.Icon: + modularization = ColumnModularizeType.Icon; + break; + case WixCustomTableColumnModularizeType.Property: + modularization = ColumnModularizeType.Property; break; - case "SemicolonDelimited": + case WixCustomTableColumnModularizeType.SemicolonDelimited: modularization = ColumnModularizeType.SemicolonDelimited; break; } - var columnDefinition = new ColumnDefinition(name, type, length, primaryKey, nullable, category, minValue, maxValue, keyTable, keyColumn, setValue, description, modularization, ColumnType.Localized == type, true); + var columnDefinition = new ColumnDefinition(name, type, column.Width, column.PrimaryKey, column.Nullable, category, column.MinValue, column.MaxValue, column.KeyTable, column.KeyColumn, column.Set, column.Description, modularization, ColumnType.Localized == type, useCData: true, column.Unreal); columns.Add(columnDefinition); } diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs index 3e680a98..af7e262a 100644 --- a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs @@ -4,7 +4,9 @@ namespace WixToolset.Core.Bind { using System; using System.Collections.Generic; + using System.Linq; using WixToolset.Data; + using WixToolset.Data.Tuples; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; @@ -42,6 +44,9 @@ namespace WixToolset.Core.Bind var fileResolver = new FileResolver(this.BindPaths, this.Extensions); + // Build the column lookup only when needed. + Dictionary customColumnsById = null; + foreach (var sections in this.Intermediate.Sections) { foreach (var tuple in sections.Tuples) @@ -53,13 +58,37 @@ namespace WixToolset.Core.Bind continue; } + var fieldType = field.Type; + + // Custom table cells require an extra look up to the column definition as the + // cell's data type is always a string (because strings can store anything) but + // the column definition may be more specific. + if (tuple.Definition.Type == TupleDefinitionType.WixCustomTableCell) + { + // We only care about the Data in a CustomTable cell. + if (field.Name != nameof(WixCustomTableCellTupleFields.Data)) + { + continue; + } + + if (customColumnsById == null) + { + customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Tuples.OfType()).ToDictionary(t => t.Id.Id); + } + + if (customColumnsById.TryGetValue(tuple.Fields[(int)WixCustomTableCellTupleFields.TableRef].AsString() + "/" + tuple.Fields[(int)WixCustomTableCellTupleFields.ColumnRef].AsString(), out var customColumn)) + { + fieldType = customColumn.Type; + } + } + var isDefault = true; // Check to make sure we're in a scenario where we can handle variable resolution. if (null != delayedFields) { // resolve localization and wix variables - if (field.Type == IntermediateFieldType.String) + if (fieldType == IntermediateFieldType.String) { var original = field.AsString(); if (!String.IsNullOrEmpty(original)) @@ -87,7 +116,7 @@ namespace WixToolset.Core.Bind } // Resolve file paths - if (field.Type == IntermediateFieldType.Path) + if (fieldType == IntermediateFieldType.Path) { var objectField = field.AsPath(); @@ -226,29 +255,5 @@ namespace WixToolset.Core.Bind this.DelayedFields = delayedFields; } - -#if false - private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage = BindStage.Normal) - { - string path = null; - foreach (var extension in this.Extensions) - { - path = extension.ResolveFile(source, type, sourceLineNumbers, bindStage); - if (null != path) - { - break; - } - } - - throw new NotImplementedException(); // need to do default binder stuff - - //if (null == path) - //{ - // throw new WixFileNotFoundException(sourceLineNumbers, source, type); - //} - - //return path; - } -#endif } } diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 32e9b9d6..35a73e00 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -8,6 +8,7 @@ namespace WixToolset.Core using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; + using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; using WixToolset.Data; @@ -3667,20 +3668,8 @@ namespace WixToolset.Core { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string tableId = null; - - string categories = null; - var columnCount = 0; - string columnNames = null; - string columnTypes = null; - string descriptions = null; - string keyColumns = null; - string keyTables = null; - string maxValues = null; - string minValues = null; - string modularizations = null; - string primaryKeys = null; - string sets = null; - var bootstrapperApplicationData = false; + var unreal = false; + var columns = new List(); foreach (var attrib in node.Attributes()) { @@ -3692,7 +3681,7 @@ namespace WixToolset.Core tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; case "Unreal": - bootstrapperApplicationData = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); @@ -3722,22 +3711,20 @@ namespace WixToolset.Core switch (child.Name.LocalName) { case "Column": - ++columnCount; - - var category = String.Empty; string columnName = null; - string columnType = null; + var category = String.Empty; + IntermediateFieldType? columnType = null; var description = String.Empty; - var keyColumn = CompilerConstants.IntegerNotSet; + int? keyColumn = null; var keyTable = String.Empty; var localizable = false; - var maxValue = CompilerConstants.LongNotSet; - var minValue = CompilerConstants.LongNotSet; - var modularization = "None"; + long? maxValue = null; + long? minValue = null; + var modularization = WixCustomTableColumnModularizeType.None; var nullable = false; var primaryKey = false; var setValues = String.Empty; - string typeName = null; + var columnUnreal = false; var width = 0; foreach (var childAttrib in child.Attributes()) @@ -3769,7 +3756,43 @@ namespace WixToolset.Core minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); break; case "Modularize": - modularization = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (modularizeValue) + { + case "column": + modularization = WixCustomTableColumnModularizeType.Column; + break; + case "companionFile": + modularization = WixCustomTableColumnModularizeType.CompanionFile; + break; + case "condition": + modularization = WixCustomTableColumnModularizeType.Condition; + break; + case "controlEventArgument": + modularization = WixCustomTableColumnModularizeType.ControlEventArgument; + break; + case "controlText": + modularization = WixCustomTableColumnModularizeType.ControlText; + break; + case "icon": + modularization = WixCustomTableColumnModularizeType.Icon; + break; + case "none": + modularization = WixCustomTableColumnModularizeType.None; + break; + case "property": + modularization = WixCustomTableColumnModularizeType.Property; + break; + case "semicolonDelimited": + modularization = WixCustomTableColumnModularizeType.SemicolonDelimited; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; + } break; case "Nullable": nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); @@ -3785,24 +3808,28 @@ namespace WixToolset.Core switch (typeValue) { case "binary": - typeName = "OBJECT"; + columnType = IntermediateFieldType.Path; break; case "int": - typeName = "SHORT"; + columnType = IntermediateFieldType.Number; break; case "string": - typeName = "CHAR"; + columnType = IntermediateFieldType.String; break; case "": break; default: this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. break; } break; case "Width": width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue); break; + case "Unreal": + columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; default: this.Core.UnexpectedAttribute(child, childAttrib); break; @@ -3814,100 +3841,59 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); } - if (null == typeName) + if (!columnType.HasValue) { this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type")); } - else if ("SHORT" == typeName) + else if (columnType == IntermediateFieldType.Number) { if (2 != width && 4 != width) { this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width)); } - columnType = String.Concat(nullable ? "I" : "i", width); } - else if ("CHAR" == typeName) + else if (columnType == IntermediateFieldType.Path) { - var typeChar = localizable ? "l" : "s"; - columnType = String.Concat(nullable ? typeChar.ToUpper(CultureInfo.InvariantCulture) : typeChar.ToLower(CultureInfo.InvariantCulture), width); - } - else if ("OBJECT" == typeName) - { - if ("Binary" != category) + if (String.IsNullOrEmpty(category)) { - this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); + category = "Binary"; } - columnType = String.Concat(nullable ? "V" : "v", width); - } - - this.Core.ParseForExtensionElements(child); - - columnNames = String.Concat(columnNames, null == columnNames ? String.Empty : "\t", columnName); - columnTypes = String.Concat(columnTypes, null == columnTypes ? String.Empty : "\t", columnType); - if (primaryKey) - { - primaryKeys = String.Concat(primaryKeys, null == primaryKeys ? String.Empty : "\t", columnName); - } - - minValues = String.Concat(minValues, null == minValues ? String.Empty : "\t", CompilerConstants.LongNotSet != minValue ? minValue.ToString(CultureInfo.InvariantCulture) : String.Empty); - maxValues = String.Concat(maxValues, null == maxValues ? String.Empty : "\t", CompilerConstants.LongNotSet != maxValue ? maxValue.ToString(CultureInfo.InvariantCulture) : String.Empty); - keyTables = String.Concat(keyTables, null == keyTables ? String.Empty : "\t", keyTable); - keyColumns = String.Concat(keyColumns, null == keyColumns ? String.Empty : "\t", CompilerConstants.IntegerNotSet != keyColumn ? keyColumn.ToString(CultureInfo.InvariantCulture) : String.Empty); - categories = String.Concat(categories, null == categories ? String.Empty : "\t", category); - sets = String.Concat(sets, null == sets ? String.Empty : "\t", setValues); - descriptions = String.Concat(descriptions, null == descriptions ? String.Empty : "\t", description); - modularizations = String.Concat(modularizations, null == modularizations ? String.Empty : "\t", modularization); - - break; - case "Row": - string dataValue = null; - - foreach (var childAttrib in child.Attributes()) - { - this.Core.ParseExtensionAttribute(child, childAttrib); - } - - foreach (var data in child.Elements()) - { - var dataSourceLineNumbers = Preprocessor.GetSourceLineNumbers(data); - switch (data.Name.LocalName) + else if (category != "Binary") { - case "Data": - columnName = null; - foreach (var dataAttrib in data.Attributes()) - { - switch (dataAttrib.Name.LocalName) - { - case "Column": - columnName = this.Core.GetAttributeValue(dataSourceLineNumbers, dataAttrib); - break; - default: - this.Core.UnexpectedAttribute(data, dataAttrib); - break; - } - } - - if (null == columnName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(dataSourceLineNumbers, data.Name.LocalName, "Column")); - } - - dataValue = String.Concat(dataValue, null == dataValue ? String.Empty : WixCustomRowTuple.FieldSeparator.ToString(), columnName, ":", Common.GetInnerText(data)); - break; + this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); } } - this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.WixCustomTable, tableId); + this.Core.ParseForExtensionElements(child); if (!this.Core.EncounteredError) { - this.Core.AddTuple(new WixCustomRowTuple(childSourceLineNumbers) + var attributes = primaryKey ? WixCustomTableColumnTupleAttributes.PrimaryKey : WixCustomTableColumnTupleAttributes.None; + attributes |= localizable ? WixCustomTableColumnTupleAttributes.Localizable : WixCustomTableColumnTupleAttributes.None; + attributes |= nullable ? WixCustomTableColumnTupleAttributes.Nullable : WixCustomTableColumnTupleAttributes.None; + attributes |= columnUnreal ? WixCustomTableColumnTupleAttributes.Unreal : WixCustomTableColumnTupleAttributes.None; + + columns.Add(new WixCustomTableColumnTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, tableId, columnName)) { - Table = tableId, - FieldData = dataValue, + TableRef = tableId, + Name = columnName, + Type = columnType.Value, + Attributes = attributes, + Width = width, + Category = category, + Description = description, + KeyColumn = keyColumn, + KeyTable = keyTable, + MaxValue = maxValue, + MinValue = minValue, + Modularize = modularization, + Set = setValues, }); } break; + case "Row": + this.ParseRow(child, tableId); + break; default: this.Core.UnexpectedElement(node, child); break; @@ -3919,35 +3905,98 @@ namespace WixToolset.Core } } - if (0 < columnCount) + if (columns.Count > 0) { - if (null == primaryKeys || 0 == primaryKeys.Length) + if (!columns.Where(c => c.PrimaryKey).Any()) { this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers)); } if (!this.Core.EncounteredError) { + var columnNames = String.Join(new string(WixCustomTableTuple.ColumnNamesSeparator, 1), columns.Select(c => c.Name)); + this.Core.AddTuple(new WixCustomTableTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, tableId)) { - ColumnCount = columnCount, ColumnNames = columnNames, - ColumnTypes = columnTypes, - PrimaryKeys = primaryKeys, - MinValues = minValues, - MaxValues = maxValues, - KeyTables = keyTables, - KeyColumns = keyColumns, - Categories = categories, - Sets = sets, - Descriptions = descriptions, - Modularizations = modularizations, - Unreal = bootstrapperApplicationData, + Unreal = unreal, }); + + foreach (var column in columns) + { + this.Core.AddTuple(column); + } } } } + private void ParseRow(XElement node, string tableId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var rowId = Guid.NewGuid().ToString("N").ToUpperInvariant(); + + foreach (var attrib in node.Attributes()) + { + this.Core.ParseExtensionAttribute(node, attrib); + } + + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "Data": + string columnName = null; + string data = null; + foreach (var attrib in child.Attributes()) + { + switch (attrib.Name.LocalName) + { + case "Column": + columnName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Value": + data = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.ParseExtensionAttribute(child, attrib); + break; + } + } + + if (null == columnName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Column")); + } + + if (String.IsNullOrEmpty(data)) + { + data = Common.GetInnerText(child); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddTuple(new WixCustomTableCellTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, tableId, rowId, columnName)) + { + RowId = rowId, + ColumnRef = columnName, + TableRef = tableId, + Data = data + }); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + + if (!this.Core.EncounteredError) + { + this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.WixCustomTable, tableId); + } + } + /// /// Parses a directory element. /// diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs index 53036919..80c00ef1 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -46,7 +46,7 @@ namespace WixToolsetTest.CoreIntegration var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTable"); Assert.Equal(3, customElements.Count); Assert.Equal("", customElements[0].GetTestXml()); - Assert.Equal("", customElements[1].GetTestXml()); + Assert.Equal("", customElements[1].GetTestXml()); Assert.Equal("", customElements[2].GetTestXml()); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index aa8a0a0d..78a8f0a4 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -472,6 +472,121 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void PopulatesCustomTableWithLocalization() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" }); + Assert.Equal(new[] + { + "CustomTableLocalized:Row1\tThis is row one", + "CustomTableLocalized:Row2\tThis is row two", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithFilePath() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); + Assert.Equal(new[] + { + "CustomTableWithFile:Row1\t[Binary data]", + "CustomTableWithFile:Row2\t[Binary data]", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithFilePathSerialized() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-lib", wixlibPath, + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); + Assert.Equal(new[] + { + "CustomTableWithFile:Row1\t[Binary data]", + "CustomTableWithFile:Row2\t[Binary data]", + }, results); + } + } + [Fact] public void UnrealCustomTableIsNotPresentInMsi() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs new file mode 100644 index 00000000..ad5ed233 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs @@ -0,0 +1,22 @@ + + + + + + + + + + + + Row1 + file1.txt + + + SourceDir\file2.txt + Row2 + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl new file mode 100644 index 00000000..bc2ccf04 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl @@ -0,0 +1,7 @@ + + + + This is row one + This is row two + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs new file mode 100644 index 00000000..e1da74f8 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt new file mode 100644 index 00000000..97f701ce --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt @@ -0,0 +1 @@ +This is file1.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt new file mode 100644 index 00000000..46493186 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt @@ -0,0 +1 @@ +This is file2.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index f9f1ba44..51775cd0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -34,7 +34,13 @@ + + + + + + -- cgit v1.2.3-55-g6feb From 49ce77951ca980848b275cef082309c49b117f47 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 12 Jun 2020 06:51:37 -0700 Subject: Fix custom table column values case in compiler and decompiler --- .../Bind/LoadTableDefinitionsCommand.cs | 53 ++--- .../Decompile/Decompiler.cs | 52 ++--- src/WixToolset.Core/Compiler.cs | 100 ++++++++- .../CustomTableFixture.cs | 244 +++++++++++++++++++++ .../MsiQueryFixture.cs | 179 --------------- .../TestData/CustomTable/CustomTable-Expected.wxs | 33 +++ .../TestData/CustomTable/CustomTable.wxs | 4 +- .../ProductWithComponentGroupRef/Product.wxs | 2 +- .../WixToolsetTest.CoreIntegration.csproj | 1 + 9 files changed, 429 insertions(+), 239 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs index d7809034..0312ab44 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs @@ -87,84 +87,85 @@ namespace WixToolset.Core.WindowsInstaller.Bind var category = ColumnCategory.Unknown; switch (column.Category) { - case "Text": + case WixCustomTableColumnCategoryType.Text: category = ColumnCategory.Text; break; - case "UpperCase": + case WixCustomTableColumnCategoryType.UpperCase: category = ColumnCategory.UpperCase; break; - case "LowerCase": + case WixCustomTableColumnCategoryType.LowerCase: category = ColumnCategory.LowerCase; break; - case "Integer": + case WixCustomTableColumnCategoryType.Integer: category = ColumnCategory.Integer; break; - case "DoubleInteger": + case WixCustomTableColumnCategoryType.DoubleInteger: category = ColumnCategory.DoubleInteger; break; - case "TimeDate": + case WixCustomTableColumnCategoryType.TimeDate: category = ColumnCategory.TimeDate; break; - case "Identifier": + case WixCustomTableColumnCategoryType.Identifier: category = ColumnCategory.Identifier; break; - case "Property": + case WixCustomTableColumnCategoryType.Property: category = ColumnCategory.Property; break; - case "Filename": + case WixCustomTableColumnCategoryType.Filename: category = ColumnCategory.Filename; break; - case "WildCardFilename": + case WixCustomTableColumnCategoryType.WildCardFilename: category = ColumnCategory.WildCardFilename; break; - case "Path": + case WixCustomTableColumnCategoryType.Path: category = ColumnCategory.Path; break; - case "Paths": + case WixCustomTableColumnCategoryType.Paths: category = ColumnCategory.Paths; break; - case "AnyPath": + case WixCustomTableColumnCategoryType.AnyPath: category = ColumnCategory.AnyPath; break; - case "DefaultDir": + case WixCustomTableColumnCategoryType.DefaultDir: category = ColumnCategory.DefaultDir; break; - case "RegPath": + case WixCustomTableColumnCategoryType.RegPath: category = ColumnCategory.RegPath; break; - case "Formatted": + case WixCustomTableColumnCategoryType.Formatted: category = ColumnCategory.Formatted; break; - case "FormattedSddl": + case WixCustomTableColumnCategoryType.FormattedSddl: category = ColumnCategory.FormattedSDDLText; break; - case "Template": + case WixCustomTableColumnCategoryType.Template: category = ColumnCategory.Template; break; - case "Condition": + case WixCustomTableColumnCategoryType.Condition: category = ColumnCategory.Condition; break; - case "Guid": + case WixCustomTableColumnCategoryType.Guid: category = ColumnCategory.Guid; break; - case "Version": + case WixCustomTableColumnCategoryType.Version: category = ColumnCategory.Version; break; - case "Language": + case WixCustomTableColumnCategoryType.Language: category = ColumnCategory.Language; break; - case "Binary": + case WixCustomTableColumnCategoryType.Binary: category = ColumnCategory.Binary; break; - case "CustomSource": + case WixCustomTableColumnCategoryType.CustomSource: category = ColumnCategory.CustomSource; break; - case "Cabinet": + case WixCustomTableColumnCategoryType.Cabinet: category = ColumnCategory.Cabinet; break; - case "Shortcut": + case WixCustomTableColumnCategoryType.Shortcut: category = ColumnCategory.Shortcut; break; + case null: default: break; } diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index 560b5437..54a92f3c 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -4481,82 +4481,82 @@ namespace WixToolset.Core.WindowsInstaller switch (columnDefinition.Category) { case ColumnCategory.Text: - column.Category = Wix.Column.CategoryType.Text; + column.Category = Wix.Column.CategoryType.text; break; case ColumnCategory.UpperCase: - column.Category = Wix.Column.CategoryType.UpperCase; + column.Category = Wix.Column.CategoryType.upperCase; break; case ColumnCategory.LowerCase: - column.Category = Wix.Column.CategoryType.LowerCase; + column.Category = Wix.Column.CategoryType.lowerCase; break; case ColumnCategory.Integer: - column.Category = Wix.Column.CategoryType.Integer; + column.Category = Wix.Column.CategoryType.integer; break; case ColumnCategory.DoubleInteger: - column.Category = Wix.Column.CategoryType.DoubleInteger; + column.Category = Wix.Column.CategoryType.doubleInteger; break; case ColumnCategory.TimeDate: - column.Category = Wix.Column.CategoryType.TimeDate; + column.Category = Wix.Column.CategoryType.timeDate; break; case ColumnCategory.Identifier: - column.Category = Wix.Column.CategoryType.Identifier; + column.Category = Wix.Column.CategoryType.identifier; break; case ColumnCategory.Property: - column.Category = Wix.Column.CategoryType.Property; + column.Category = Wix.Column.CategoryType.property; break; case ColumnCategory.Filename: - column.Category = Wix.Column.CategoryType.Filename; + column.Category = Wix.Column.CategoryType.filename; break; case ColumnCategory.WildCardFilename: - column.Category = Wix.Column.CategoryType.WildCardFilename; + column.Category = Wix.Column.CategoryType.wildCardFilename; break; case ColumnCategory.Path: - column.Category = Wix.Column.CategoryType.Path; + column.Category = Wix.Column.CategoryType.path; break; case ColumnCategory.Paths: - column.Category = Wix.Column.CategoryType.Paths; + column.Category = Wix.Column.CategoryType.paths; break; case ColumnCategory.AnyPath: - column.Category = Wix.Column.CategoryType.AnyPath; + column.Category = Wix.Column.CategoryType.anyPath; break; case ColumnCategory.DefaultDir: - column.Category = Wix.Column.CategoryType.DefaultDir; + column.Category = Wix.Column.CategoryType.defaultDir; break; case ColumnCategory.RegPath: - column.Category = Wix.Column.CategoryType.RegPath; + column.Category = Wix.Column.CategoryType.regPath; break; case ColumnCategory.Formatted: - column.Category = Wix.Column.CategoryType.Formatted; + column.Category = Wix.Column.CategoryType.formatted; break; case ColumnCategory.FormattedSDDLText: - column.Category = Wix.Column.CategoryType.FormattedSddl; + column.Category = Wix.Column.CategoryType.formattedSddl; break; case ColumnCategory.Template: - column.Category = Wix.Column.CategoryType.Template; + column.Category = Wix.Column.CategoryType.template; break; case ColumnCategory.Condition: - column.Category = Wix.Column.CategoryType.Condition; + column.Category = Wix.Column.CategoryType.condition; break; case ColumnCategory.Guid: - column.Category = Wix.Column.CategoryType.Guid; + column.Category = Wix.Column.CategoryType.guid; break; case ColumnCategory.Version: - column.Category = Wix.Column.CategoryType.Version; + column.Category = Wix.Column.CategoryType.version; break; case ColumnCategory.Language: - column.Category = Wix.Column.CategoryType.Language; + column.Category = Wix.Column.CategoryType.language; break; case ColumnCategory.Binary: - column.Category = Wix.Column.CategoryType.Binary; + column.Category = Wix.Column.CategoryType.binary; break; case ColumnCategory.CustomSource: - column.Category = Wix.Column.CategoryType.CustomSource; + column.Category = Wix.Column.CategoryType.customSource; break; case ColumnCategory.Cabinet: - column.Category = Wix.Column.CategoryType.Cabinet; + column.Category = Wix.Column.CategoryType.cabinet; break; case ColumnCategory.Shortcut: - column.Category = Wix.Column.CategoryType.Shortcut; + column.Category = Wix.Column.CategoryType.shortcut; break; default: throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'."); diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 3365789f..da0806fb 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -3712,7 +3712,6 @@ namespace WixToolset.Core { case "Column": string columnName = null; - var category = String.Empty; IntermediateFieldType? columnType = null; var description = String.Empty; int? keyColumn = null; @@ -3720,6 +3719,7 @@ namespace WixToolset.Core var localizable = false; long? maxValue = null; long? minValue = null; + WixCustomTableColumnCategoryType? category = null; var modularization = WixCustomTableColumnModularizeType.None; var nullable = false; var primaryKey = false; @@ -3735,7 +3735,97 @@ namespace WixToolset.Core columnName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, childAttrib); break; case "Category": - category = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + var categoryValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (categoryValue) + { + case "text": + category = WixCustomTableColumnCategoryType.Text; + break; + case "upperCase": + category = WixCustomTableColumnCategoryType.UpperCase; + break; + case "lowerCase": + category = WixCustomTableColumnCategoryType.LowerCase; + break; + case "integer": + category = WixCustomTableColumnCategoryType.Integer; + break; + case "doubleInteger": + category = WixCustomTableColumnCategoryType.DoubleInteger; + break; + case "timeDate": + category = WixCustomTableColumnCategoryType.TimeDate; + break; + case "identifier": + category = WixCustomTableColumnCategoryType.Identifier; + break; + case "property": + category = WixCustomTableColumnCategoryType.Property; + break; + case "filename": + category = WixCustomTableColumnCategoryType.Filename; + break; + case "wildCardFilename": + category = WixCustomTableColumnCategoryType.WildCardFilename; + break; + case "path": + category = WixCustomTableColumnCategoryType.Path; + break; + case "paths": + category = WixCustomTableColumnCategoryType.Paths; + break; + case "anyPath": + category = WixCustomTableColumnCategoryType.AnyPath; + break; + case "defaultDir": + category = WixCustomTableColumnCategoryType.DefaultDir; + break; + case "regPath": + category = WixCustomTableColumnCategoryType.RegPath; + break; + case "formatted": + category = WixCustomTableColumnCategoryType.Formatted; + break; + case "formattedSddl": + category = WixCustomTableColumnCategoryType.FormattedSddl; + break; + case "template": + category = WixCustomTableColumnCategoryType.Template; + break; + case "condition": + category = WixCustomTableColumnCategoryType.Condition; + break; + case "guid": + category = WixCustomTableColumnCategoryType.Guid; + break; + case "version": + category = WixCustomTableColumnCategoryType.Version; + break; + case "language": + category = WixCustomTableColumnCategoryType.Language; + break; + case "binary": + category = WixCustomTableColumnCategoryType.Binary; + break; + case "customSource": + category = WixCustomTableColumnCategoryType.CustomSource; + break; + case "cabinet": + category = WixCustomTableColumnCategoryType.Cabinet; + break; + case "shortcut": + category = WixCustomTableColumnCategoryType.Shortcut; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Category", categoryValue, + "text", "upperCase", "lowerCase", "integer", "doubleInteger", "timeDate", "identifier", "property", "filename", + "wildCardFilename", "path", "paths", "anyPath", "defaultDir", "regPath", "formatted", "formattedSddl", "template", + "condition", "guid", "version", "language", "binary", "customSource", "cabinet", "shortcut")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; + } break; case "Description": description = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); @@ -3854,11 +3944,11 @@ namespace WixToolset.Core } else if (columnType == IntermediateFieldType.Path) { - if (String.IsNullOrEmpty(category)) + if (!category.HasValue) { - category = "Binary"; + category = WixCustomTableColumnCategoryType.Binary; } - else if (category != "Binary") + else if (category != WixCustomTableColumnCategoryType.Binary) { this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); } diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs new file mode 100644 index 00000000..85a0ffae --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs @@ -0,0 +1,244 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using Microsoft.Build.Tasks; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class CustomTableFixture + { + [Fact] + public void PopulatesCustomTable1() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTable1" }); + Assert.Equal(new[] + { + "CustomTable1:Row1\ttest.txt", + "CustomTable1:Row2\ttest.txt", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithLocalization() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" }); + Assert.Equal(new[] + { + "CustomTableLocalized:Row1\tThis is row one", + "CustomTableLocalized:Row2\tThis is row two", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithFilePath() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); + Assert.Equal(new[] + { + "CustomTableWithFile:Row1\t[Binary data]", + "CustomTableWithFile:Row2\t[Binary data]", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithFilePathSerialized() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-lib", wixlibPath, + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); + Assert.Equal(new[] + { + "CustomTableWithFile:Row1\t[Binary data]", + "CustomTableWithFile:Row2\t[Binary data]", + }, results); + } + } + + [Fact] + public void UnrealCustomTableIsNotPresentInMsi() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" }); + Assert.Empty(results); + } + } + + [Fact] + public void CanCompileAndDecompile() + { + var folder = TestData.Get(@"TestData"); + var expectedFile = Path.Combine(folder, "CustomTable", "CustomTable-Expected.wxs"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + var decompiledWxsPath = Path.Combine(baseFolder, @"decompiled.wxs"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + Assert.True(File.Exists(msiPath)); + + result = WixRunner.Execute(new[] + { + "decompile", msiPath, + "-intermediateFolder", intermediateFolder, + "-o", decompiledWxsPath + }); + + result.AssertSuccess(); + + CompareLineByLine(expectedFile, decompiledWxsPath); + } + } + + private static void CompareLineByLine(string expectedFile, string actualFile) + { + var expectedLines = File.ReadAllLines(expectedFile); + var actualLines = File.ReadAllLines(actualFile); + for (var i = 0; i < expectedLines.Length; ++i) + { + Assert.True(actualLines.Length > i, $"{i}: Expected file longer than actual file"); + Assert.Equal($"{i}: {expectedLines[i]}", $"{i}: {actualLines[i]}"); + } + Assert.True(expectedLines.Length == actualLines.Length, "Actual file longer than expected file"); + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 78a8f0a4..70d6612e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -438,185 +438,6 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact] - public void PopulatesCustomTable1() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTable1" }); - Assert.Equal(new[] - { - "CustomTable1:Row1\ttest.txt", - "CustomTable1:Row2\ttest.txt", - }, results); - } - } - - [Fact] - public void PopulatesCustomTableWithLocalization() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" }); - Assert.Equal(new[] - { - "CustomTableLocalized:Row1\tThis is row one", - "CustomTableLocalized:Row2\tThis is row two", - }, results); - } - } - - [Fact] - public void PopulatesCustomTableWithFilePath() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "CustomTable", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); - Assert.Equal(new[] - { - "CustomTableWithFile:Row1\t[Binary data]", - "CustomTableWithFile:Row2\t[Binary data]", - }, results); - } - } - - [Fact] - public void PopulatesCustomTableWithFilePathSerialized() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), - "-bindpath", Path.Combine(folder, "CustomTable", "data"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-lib", wixlibPath, - "-bindpath", Path.Combine(folder, "CustomTable", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); - Assert.Equal(new[] - { - "CustomTableWithFile:Row1\t[Binary data]", - "CustomTableWithFile:Row2\t[Binary data]", - }, results); - } - } - - [Fact] - public void UnrealCustomTableIsNotPresentInMsi() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" }); - Assert.Empty(results); - } - } - [Fact] public void PopulatesDirectoryTableWithValidDefaultDir() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs new file mode 100644 index 00000000..68386612 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs @@ -0,0 +1,33 @@ + + + + + + + + + Row1 + test.txt + + + Row2 + test.txt + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs index 8eb4fbf9..51aee5f2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs @@ -6,8 +6,8 @@ - - + + Row1 test.txt diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs index 0d1e89e6..e0e5345a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -1,6 +1,6 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 51775cd0..7ede6655 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -34,6 +34,7 @@ + -- cgit v1.2.3-55-g6feb From 678c92c50c6fb7aa9a093f0d74d4f92742abd5e8 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 15 Jun 2020 16:07:45 -0700 Subject: Reorganize media assignment to correctly place facade order optimization --- .../Bind/AssignMediaCommand.cs | 159 ++++++++++----------- .../Bind/BindDatabaseCommand.cs | 32 ++--- .../Bind/CabinetBuilder.cs | 14 +- .../Bind/CreateCabinetsCommand.cs | 1 - .../Bind/OptimizeFileFacadesOrderCommand.cs | 38 +++++ .../Bind/UpdateMediaSequencesCommand.cs | 15 +- .../WixToolsetTest.CoreIntegration/CabFixture.cs | 71 +++++++++ .../MultiFileCompressed/PackageComponents.wxs | 4 +- 8 files changed, 208 insertions(+), 126 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/CabFixture.cs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs index 1d677a70..b75956b4 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs @@ -35,18 +35,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind private bool FilesCompressed { get; } - public string CabinetNameTemplate { private get; set; } + private string CabinetNameTemplate { get; set; } /// /// Gets cabinets with their file rows. /// public Dictionary> FileFacadesByCabinetMedia { get; private set; } - /// - /// Get media rows. - /// - public Dictionary MediaRows { get; private set; } - /// /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no. /// This contains all the files when Package element is marked with compression=no @@ -55,17 +50,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind public void Execute() { - var filesByCabinetMedia = new Dictionary>(); - - var mediaRows = new Dictionary(); - - var uncompressedFiles = new List(); - - var mediaTable = this.Section.Tuples.OfType().ToList(); - var mediaTemplateTable = this.Section.Tuples.OfType().ToList(); + var mediaTuples = this.Section.Tuples.OfType().ToList(); + var mediaTemplateTuples = this.Section.Tuples.OfType().ToList(); - // If both tables are authored, it is an error. - if (mediaTemplateTable.Count > 0 && mediaTable.Count > 1) + // If both tuples are authored, it is an error. + if (mediaTemplateTuples.Count > 0 && mediaTuples.Count > 1) { throw new WixException(ErrorMessages.MediaTableCollision(null)); } @@ -78,34 +67,44 @@ namespace WixToolset.Core.WindowsInstaller.Bind Cabinet = "#MergeModule.CABinet", }); - filesByCabinetMedia.Add(mergeModuleMediaTuple, new List(this.FileFacades)); + this.FileFacadesByCabinetMedia = new Dictionary> + { + { mergeModuleMediaTuple, this.FileFacades } + }; + + this.UncompressedFileFacades = Array.Empty(); } - else if (mediaTemplateTable.Count == 0) + else if (mediaTemplateTuples.Count == 0) { - this.ManuallyAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); + var filesByCabinetMedia = new Dictionary>(); + + var uncompressedFiles = new List(); + + this.ManuallyAssignFiles(mediaTuples, filesByCabinetMedia, uncompressedFiles); + + this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable)kvp.Value); + + this.UncompressedFileFacades = uncompressedFiles; } else { - this.AutoAssignFiles(mediaTable, filesByCabinetMedia, mediaRows, uncompressedFiles); - } + var filesByCabinetMedia = new Dictionary>(); - this.FileFacadesByCabinetMedia = new Dictionary>(); + var uncompressedFiles = new List(); - foreach (var mediaRowWithFiles in filesByCabinetMedia) - { - this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value); - } + this.AutoAssignFiles(mediaTuples, filesByCabinetMedia, uncompressedFiles); - this.MediaRows = mediaRows; + this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable)kvp.Value); - this.UncompressedFileFacades = uncompressedFiles; + this.UncompressedFileFacades = uncompressedFiles; + } } /// /// Assign files to cabinets based on MediaTemplate authoring. /// /// FileRowCollection - private void AutoAssignFiles(List mediaTable, Dictionary> filesByCabinetMedia, Dictionary mediaRows, List uncompressedFiles) + private void AutoAssignFiles(List mediaTable, Dictionary> filesByCabinetMedia, List uncompressedFiles) { const int MaxCabIndex = 999; @@ -158,6 +157,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); } + var mediaTuplesByDiskId = new Dictionary(); + foreach (var facade in this.FileFacades) { // When building a product, if the current file is not to be compressed or if @@ -171,44 +172,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (currentCabIndex == MaxCabIndex) { // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. - var cabinetFiles = filesByCabinetMedia[currentMediaRow]; - facade.DiskId = currentCabIndex; - cabinetFiles.Add(facade); - continue; - } - - // Update current cab size. - currentPreCabSize += (ulong)facade.FileSize; - - if (currentPreCabSize > maxPreCabSizeInBytes) - { - // Overflow due to current file - currentMediaRow = this.AddMediaRow(mediaTemplateRow, ++currentCabIndex); - mediaRows.Add(currentMediaRow.DiskId, currentMediaRow); - filesByCabinetMedia.Add(currentMediaRow, new List()); - - var cabinetFileRows = filesByCabinetMedia[currentMediaRow]; - facade.DiskId = currentCabIndex; - cabinetFileRows.Add(facade); - // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize - currentPreCabSize = (ulong)facade.FileSize; } else { - // File fits in the current cab. - if (currentMediaRow == null) + // Update current cab size. + currentPreCabSize += (ulong)facade.FileSize; + + // Overflow due to current file + if (currentPreCabSize > maxPreCabSizeInBytes) { - // Create new cab and MediaRow - currentMediaRow = this.AddMediaRow(mediaTemplateRow, ++currentCabIndex); - mediaRows.Add(currentMediaRow.DiskId, currentMediaRow); + currentMediaRow = this.AddMediaTuple(mediaTemplateRow, ++currentCabIndex); + mediaTuplesByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); filesByCabinetMedia.Add(currentMediaRow, new List()); - } - // Associate current file with current cab. - var cabinetFiles = filesByCabinetMedia[currentMediaRow]; - facade.DiskId = currentCabIndex; - cabinetFiles.Add(facade); + // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize + currentPreCabSize = (ulong)facade.FileSize; + } + else // file fits in the current cab. + { + if (currentMediaRow == null) + { + // Create new cab and MediaRow + currentMediaRow = this.AddMediaTuple(mediaTemplateRow, ++currentCabIndex); + mediaTuplesByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); + filesByCabinetMedia.Add(currentMediaRow, new List()); + } + } } + + // Associate current file with current cab. + var cabinetFiles = filesByCabinetMedia[currentMediaRow]; + facade.DiskId = currentCabIndex; + cabinetFiles.Add(facade); } // If there are uncompressed files and no MediaRow, create a default one. @@ -219,51 +214,45 @@ namespace WixToolset.Core.WindowsInstaller.Bind DiskId = 1, }); - mediaRows.Add(1, defaultMediaRow); + mediaTuplesByDiskId.Add(1, defaultMediaRow); } } /// /// Assign files to cabinets based on Media authoring. /// - /// - /// - private void ManuallyAssignFiles(List mediaTable, IEnumerable fileFacades, Dictionary> filesByCabinetMedia, Dictionary mediaRows, List uncompressedFiles) + private void ManuallyAssignFiles(List mediaTuples, Dictionary> filesByCabinetMedia, List uncompressedFiles) { - if (mediaTable.Any()) + var mediaTuplesByDiskId = new Dictionary(); + + if (mediaTuples.Any()) { - var cabinetMediaRows = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var mediaRow in mediaTable) + var cabinetMediaTuples = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var mediaTuple in mediaTuples) { // If the Media row has a cabinet, make sure it is unique across all Media rows. - if (!String.IsNullOrEmpty(mediaRow.Cabinet)) + if (!String.IsNullOrEmpty(mediaTuple.Cabinet)) { - if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out var existingRow)) + if (cabinetMediaTuples.TryGetValue(mediaTuple.Cabinet, out var existingRow)) { - this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet)); + this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaTuple.SourceLineNumbers, mediaTuple.Cabinet)); this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); } else { - cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow); + cabinetMediaTuples.Add(mediaTuple.Cabinet, mediaTuple); } - } - mediaRows.Add(mediaRow.DiskId, mediaRow); - } - } + filesByCabinetMedia.Add(mediaTuple, new List()); + } - foreach (var mediaRow in mediaRows.Values) - { - if (null != mediaRow.Cabinet) - { - filesByCabinetMedia.Add(mediaRow, new List()); + mediaTuplesByDiskId.Add(mediaTuple.DiskId, mediaTuple); } } - foreach (var facade in fileFacades) + foreach (var facade in this.FileFacades) { - if (!mediaRows.TryGetValue(facade.DiskId, out var mediaRow)) + if (!mediaTuplesByDiskId.TryGetValue(facade.DiskId, out var mediaTuple)) { this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId)); continue; @@ -279,7 +268,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else // file is marked compressed. { - if (filesByCabinetMedia.TryGetValue(mediaRow, out var cabinetFiles)) + if (filesByCabinetMedia.TryGetValue(mediaTuple, out var cabinetFiles)) { cabinetFiles.Add(facade); } @@ -292,12 +281,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind } /// - /// Adds a row to the media table with cab name template filled in. + /// Adds a tuple to the section with cab name template filled in. /// /// /// /// - private MediaTuple AddMediaRow(WixMediaTemplateTuple mediaTemplateTuple, int cabIndex) + private MediaTuple AddMediaTuple(WixMediaTemplateTuple mediaTemplateTuple, int cabIndex) { return this.Section.AddTuple(new MediaTuple(mediaTemplateTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, cabIndex)) { diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 6c2968ec..da92be69 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -281,19 +281,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind command.Execute(); } - // Assign files to media. - Dictionary assignedMediaRows; - Dictionary> filesByCabinetMedia; - IEnumerable uncompressedFiles; - { - var command = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed); - command.Execute(); - - assignedMediaRows = command.MediaRows; - filesByCabinetMedia = command.FileFacadesByCabinetMedia; - uncompressedFiles = command.UncompressedFileFacades; - } - // stop processing if an error previously occurred if (this.Messaging.EncounteredError) { @@ -366,10 +353,23 @@ namespace WixToolset.Core.WindowsInstaller.Bind command.Execute(); } - // Update file sequence. + // Assign files to media and update file sequences. + Dictionary> filesByCabinetMedia; + IEnumerable uncompressedFiles; { - var command = new UpdateMediaSequencesCommand(section, fileFacades); - command.Execute(); + var order = new OptimizeFileFacadesOrderCommand(fileFacades); + order.Execute(); + + fileFacades = order.FileFacades; + + var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed); + assign.Execute(); + + filesByCabinetMedia = assign.FileFacadesByCabinetMedia; + uncompressedFiles = assign.UncompressedFileFacades; + + var update = new UpdateMediaSequencesCommand(section, fileFacades); + update.Execute(); } // stop processing if an error previously occurred diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs index 486ee67a..dce89f78 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs @@ -7,7 +7,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System.IO; using System.Linq; using System.Threading; - using WixToolset.Core.Bind; using WixToolset.Core.Native; using WixToolset.Data; using WixToolset.Extensibility.Services; @@ -18,12 +17,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal sealed class CabinetBuilder { - private Queue cabinetWorkItems; - private object lockObject; + private readonly object lockObject = new object(); + + private readonly Queue cabinetWorkItems; private int threadCount; // Address of Binder's callback function for Cabinet Splitting - private IntPtr newCabNamesCallBackAddress; + private readonly IntPtr newCabNamesCallBackAddress; /// /// Instantiate a new CabinetBuilder. @@ -38,7 +38,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind } this.cabinetWorkItems = new Queue(); - this.lockObject = new object(); this.Messaging = messaging; this.threadCount = threadCount; @@ -56,10 +55,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// Enqueues a CabinetWorkItem to the queue. /// /// cabinet work item - public void Enqueue(CabinetWorkItem cabinetWorkItem) - { - this.cabinetWorkItems.Enqueue(cabinetWorkItem); - } + public void Enqueue(CabinetWorkItem cabinetWorkItem) => this.cabinetWorkItems.Enqueue(cabinetWorkItem); /// /// Create the queued cabinets. diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs index de357e53..9741fcd9 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs @@ -171,7 +171,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind return cabbingThreadCount; } - /// /// Creates a work item to create a cabinet. /// diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs new file mode 100644 index 00000000..6943d345 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs @@ -0,0 +1,38 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using WixToolset.Core.Bind; + + internal class OptimizeFileFacadesOrderCommand + { + public OptimizeFileFacadesOrderCommand(List fileFacades) + { + this.FileFacades = fileFacades; + } + + public List FileFacades { get; private set; } + + public List Execute() + { + this.FileFacades.Sort(FileFacadeOptimizer.Instance); + + return this.FileFacades; + } + + private class FileFacadeOptimizer : IComparer + { + public static readonly FileFacadeOptimizer Instance = new FileFacadeOptimizer(); + + public int Compare(FileFacade x, FileFacade y) + { + // TODO: Sort these facades even smarter by directory path and component id + // and maybe file size or file extension and other creative ideas to + // get optimal install speed out of MSI. + return String.Compare(x.ComponentRef, y.ComponentRef, StringComparison.Ordinal); + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs index 9aab7b98..bf28b279 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs @@ -29,9 +29,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var lastSequence = 0; - // Order by Component to group the files by directory. - var optimized = this.OptimizedFileFacades(); - foreach (var facade in optimized) + foreach (var facade in this.FileFacades) { facade.Sequence = ++lastSequence; } @@ -43,8 +41,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var patchGroups = new Dictionary>(); // sequence the non-patch-added files - var optimized = this.OptimizedFileFacades(); - foreach (var facade in optimized) + foreach (var facade in this.FileFacades) { if (null == mediaTuple) { @@ -108,13 +105,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } } - - private IEnumerable OptimizedFileFacades() - { - // TODO: Sort these facades even smarter by directory path and component id - // and maybe file size or file extension and other creative ideas to - // get optimal install speed out of MSI. - return this.FileFacades.OrderBy(f => f.ComponentRef); - } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs new file mode 100644 index 00000000..79471554 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs @@ -0,0 +1,71 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class CabFixture + { + [Fact] + public void CabinetFilesSequencedCorrectly() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + Assert.True(File.Exists(cabPath)); + + var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); + var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); + + Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray()); + Assert.Equal(new[] { "test.txt", "Notepad.exe" }, fileRows.Select(f => f.Name).ToArray()); + + var files = Query.GetCabinetFiles(cabPath); + Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); + } + } + + private class FileRow + { + public FileRow(string row) + { + row = row.Substring("File:".Length); + + var split = row.Split('\t'); + this.Id = split[0]; + this.Name = split[2]; + this.Sequence = Convert.ToInt32(split[7]); + } + + public string Id { get; set; } + + public string Name { get; set; } + + public int Sequence { get; set; } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs index d65a07df..82797ebe 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs @@ -3,10 +3,10 @@ - + - + -- cgit v1.2.3-55-g6feb From abff61df823505abc01776cec7b207501c671bf2 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 19 Jun 2020 13:52:20 +1000 Subject: Implement BundleCustomData. Remove Unreal from CustomTable. --- .../Bind/GenerateManifestDataFromIRCommand.cs | 98 ++-- .../Bind/CreateOutputFromIRCommand.cs | 4 +- .../Bind/LoadTableDefinitionsCommand.cs | 2 +- src/WixToolset.Core/Compiler.cs | 566 +++++++++++---------- src/WixToolset.Core/Compiler_Bundle.cs | 244 +++++++++ .../BundleManifestFixture.cs | 49 +- .../CustomTableFixture.cs | 31 -- .../BundleCustomTable/BundleCustomTable.wxs | 52 +- .../TestData/CustomTable/CustomTable.wxs | 13 - 9 files changed, 680 insertions(+), 379 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs index 20ecd157..7fd510c6 100644 --- a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs @@ -2,6 +2,7 @@ namespace WixToolset.Core.Burn.Bind { + using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -37,8 +38,8 @@ namespace WixToolset.Core.Burn.Bind public void Execute() { var tuples = this.Section.Tuples.ToList(); - var cellsByTableAndRowId = new Dictionary>(); - var customTablesById = new Dictionary(); + var cellsByCustomDataAndElementId = new Dictionary>(); + var customDataById = new Dictionary(); foreach (var kvp in this.ExtensionSearchTuplesById) { @@ -63,6 +64,7 @@ namespace WixToolset.Core.Burn.Bind case TupleDefinitionType.WixBundle: case TupleDefinitionType.WixBundleCatalog: case TupleDefinitionType.WixBundleContainer: + case TupleDefinitionType.WixBundleCustomDataAttribute: case TupleDefinitionType.WixBundleExePackage: case TupleDefinitionType.WixBundleExtension: case TupleDefinitionType.WixBundleMsiFeature: @@ -82,7 +84,6 @@ namespace WixToolset.Core.Burn.Bind case TupleDefinitionType.WixBundleVariable: case TupleDefinitionType.WixChain: case TupleDefinitionType.WixComponentSearch: - case TupleDefinitionType.WixCustomTableColumn: case TupleDefinitionType.WixDependencyProvider: case TupleDefinitionType.WixFileSearch: case TupleDefinitionType.WixGroup: @@ -95,12 +96,12 @@ namespace WixToolset.Core.Burn.Bind case TupleDefinitionType.WixUpdateRegistration: break; - case TupleDefinitionType.WixCustomTable: - unknownTuple = !this.IndexCustomTableTuple((WixCustomTableTuple)tuple, customTablesById); + case TupleDefinitionType.WixBundleCustomData: + unknownTuple = !this.IndexBundleCustomDataTuple((WixBundleCustomDataTuple)tuple, customDataById); break; - case TupleDefinitionType.WixCustomTableCell: - this.IndexCustomTableCellTuple((WixCustomTableCellTuple)tuple, cellsByTableAndRowId); + case TupleDefinitionType.WixBundleCustomDataCell: + this.IndexBundleCustomDataCellTuple((WixBundleCustomDataCellTuple)tuple, cellsByCustomDataAndElementId); break; case TupleDefinitionType.MustBeFromAnExtension: @@ -118,68 +119,87 @@ namespace WixToolset.Core.Burn.Bind } } - this.AddIndexedCellTuples(customTablesById, cellsByTableAndRowId); + this.AddIndexedCellTuples(customDataById, cellsByCustomDataAndElementId); } - private bool IndexCustomTableTuple(WixCustomTableTuple wixCustomTableTuple, Dictionary customTablesById) + private bool IndexBundleCustomDataTuple(WixBundleCustomDataTuple wixBundleCustomDataTuple, Dictionary customDataById) { - if (!wixCustomTableTuple.Unreal) + switch (wixBundleCustomDataTuple.Type) { - return false; + case WixBundleCustomDataType.BootstrapperApplication: + case WixBundleCustomDataType.BundleExtension: + break; + default: + return false; } - var tableId = wixCustomTableTuple.Id.Id; - customTablesById.Add(tableId, wixCustomTableTuple); + var customDataId = wixBundleCustomDataTuple.Id.Id; + customDataById.Add(customDataId, wixBundleCustomDataTuple); return true; } - private void IndexCustomTableCellTuple(WixCustomTableCellTuple wixCustomTableCellTuple, Dictionary> cellsByTableAndRowId) + private void IndexBundleCustomDataCellTuple(WixBundleCustomDataCellTuple wixBundleCustomDataCellTuple, Dictionary> cellsByCustomDataAndElementId) { - var tableAndRowId = wixCustomTableCellTuple.TableRef + "/" + wixCustomTableCellTuple.RowId; - if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells)) + var tableAndRowId = wixBundleCustomDataCellTuple.CustomDataRef + "/" + wixBundleCustomDataCellTuple.ElementId; + if (!cellsByCustomDataAndElementId.TryGetValue(tableAndRowId, out var cells)) { - cells = new List(); - cellsByTableAndRowId.Add(tableAndRowId, cells); + cells = new List(); + cellsByCustomDataAndElementId.Add(tableAndRowId, cells); } - cells.Add(wixCustomTableCellTuple); + cells.Add(wixBundleCustomDataCellTuple); } - private void AddIndexedCellTuples(Dictionary customTablesById, Dictionary> cellsByTableAndRowId) + private void AddIndexedCellTuples(Dictionary customDataById, Dictionary> cellsByCustomDataAndElementId) { - var sb = new StringBuilder(); - using (var writer = XmlWriter.Create(sb, BurnBackendHelper.WriterSettings)) + foreach (var elementValues in cellsByCustomDataAndElementId.Values) { - foreach (var rowOfCells in cellsByTableAndRowId.Values) - { - var tableId = rowOfCells[0].TableRef; - var tableTuple = customTablesById[tableId]; - - if (!tableTuple.Unreal) - { - return; - } + var elementName = elementValues[0].CustomDataRef; + var customDataTuple = customDataById[elementName]; - var columnNames = tableTuple.ColumnNamesSeparated; + var attributeNames = customDataTuple.AttributeNamesSeparated; - var rowDataByColumn = rowOfCells.ToDictionary(t => t.ColumnRef, t => t.Data); + var elementValuesByAttribute = elementValues.ToDictionary(t => t.AttributeRef, t => t.Value); - writer.WriteStartElement(tableId, BurnCommon.BADataNamespace); + var sb = new StringBuilder(); + using (var writer = XmlWriter.Create(sb, BurnBackendHelper.WriterSettings)) + { + switch (customDataTuple.Type) + { + case WixBundleCustomDataType.BootstrapperApplication: + writer.WriteStartElement(elementName, BurnCommon.BADataNamespace); + break; + case WixBundleCustomDataType.BundleExtension: + writer.WriteStartElement(elementName, BurnCommon.BundleExtensionDataNamespace); + break; + default: + throw new NotImplementedException(); + } // Write all row data as attributes in table column order. - foreach (var column in columnNames) + foreach (var attributeName in attributeNames) { - if (rowDataByColumn.TryGetValue(column, out var data)) + if (elementValuesByAttribute.TryGetValue(attributeName, out var value)) { - writer.WriteAttributeString(column, data); + writer.WriteAttributeString(attributeName, value); } } writer.WriteEndElement(); } - } - this.BackendHelper.AddBootstrapperApplicationData(sb.ToString()); + switch (customDataTuple.Type) + { + case WixBundleCustomDataType.BootstrapperApplication: + this.BackendHelper.AddBootstrapperApplicationData(sb.ToString()); + break; + case WixBundleCustomDataType.BundleExtension: + this.BackendHelper.AddBundleExtensionData(customDataTuple.BundleExtensionRef, sb.ToString()); + break; + default: + throw new NotImplementedException(); + } + } } private bool AddTupleFromExtension(IntermediateTuple tuple) diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index daf4c96e..553b470b 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -4,6 +4,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { using System; using System.Collections.Generic; + using System.Diagnostics; using System.Globalization; using System.Linq; using WixToolset.Data; @@ -978,7 +979,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (customTableDefinition.Unreal) { - return; + Debug.Assert(false, "CustomTableDefinition should never be unreal."); + continue; } var customRow = this.CreateRow(firstCellTuple, customTableDefinition); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs index 0312ab44..cfb46ff9 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs @@ -208,7 +208,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind columns.Add(columnDefinition); } - var customTable = new TableDefinition(tuple.Id.Id, null, columns, tuple.Unreal); + var customTable = new TableDefinition(tuple.Id.Id, null, columns); return customTable; } } diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index da0806fb..bbd6b292 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -3668,7 +3668,6 @@ namespace WixToolset.Core { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string tableId = null; - var unreal = false; var columns = new List(); foreach (var attrib in node.Attributes()) @@ -3680,9 +3679,6 @@ namespace WixToolset.Core case "Id": tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; - case "Unreal": - unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; default: this.Core.UnexpectedAttribute(node, attrib); break; @@ -3710,315 +3706,336 @@ namespace WixToolset.Core var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); switch (child.Name.LocalName) { - case "Column": - string columnName = null; - IntermediateFieldType? columnType = null; - var description = String.Empty; - int? keyColumn = null; - var keyTable = String.Empty; - var localizable = false; - long? maxValue = null; - long? minValue = null; - WixCustomTableColumnCategoryType? category = null; - var modularization = WixCustomTableColumnModularizeType.None; - var nullable = false; - var primaryKey = false; - var setValues = String.Empty; - var columnUnreal = false; - var width = 0; - - foreach (var childAttrib in child.Attributes()) - { - switch (childAttrib.Name.LocalName) + case "Column": + var column = this.ParseColumnElement(child, childSourceLineNumbers, tableId); + if (column != null) { - case "Id": - columnName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, childAttrib); + columns.Add(column); + } + break; + case "Row": + this.ParseRowElement(child, childSourceLineNumbers, tableId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (columns.Count > 0) + { + if (!columns.Where(c => c.PrimaryKey).Any()) + { + this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers)); + } + + if (!this.Core.EncounteredError) + { + var columnNames = String.Join(new string(WixCustomTableTuple.ColumnNamesSeparator, 1), columns.Select(c => c.Name)); + + this.Core.AddTuple(new WixCustomTableTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, tableId)) + { + ColumnNames = columnNames, + }); + } + } + } + + /// + /// Parses a Column element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// Table Id. + private WixCustomTableColumnTuple ParseColumnElement(XElement child, SourceLineNumber childSourceLineNumbers, string tableId) + { + string columnName = null; + IntermediateFieldType? columnType = null; + var description = String.Empty; + int? keyColumn = null; + var keyTable = String.Empty; + var localizable = false; + long? maxValue = null; + long? minValue = null; + WixCustomTableColumnCategoryType? category = null; + var modularization = WixCustomTableColumnModularizeType.None; + var nullable = false; + var primaryKey = false; + var setValues = String.Empty; + var columnUnreal = false; + var width = 0; + + foreach (var childAttrib in child.Attributes()) + { + switch (childAttrib.Name.LocalName) + { + case "Id": + columnName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, childAttrib); + break; + case "Category": + var categoryValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (categoryValue) + { + case "text": + category = WixCustomTableColumnCategoryType.Text; break; - case "Category": - var categoryValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (categoryValue) - { - case "text": - category = WixCustomTableColumnCategoryType.Text; - break; - case "upperCase": - category = WixCustomTableColumnCategoryType.UpperCase; - break; - case "lowerCase": - category = WixCustomTableColumnCategoryType.LowerCase; - break; - case "integer": - category = WixCustomTableColumnCategoryType.Integer; - break; - case "doubleInteger": - category = WixCustomTableColumnCategoryType.DoubleInteger; - break; - case "timeDate": - category = WixCustomTableColumnCategoryType.TimeDate; - break; - case "identifier": - category = WixCustomTableColumnCategoryType.Identifier; - break; - case "property": - category = WixCustomTableColumnCategoryType.Property; - break; - case "filename": - category = WixCustomTableColumnCategoryType.Filename; - break; - case "wildCardFilename": - category = WixCustomTableColumnCategoryType.WildCardFilename; - break; - case "path": - category = WixCustomTableColumnCategoryType.Path; - break; - case "paths": - category = WixCustomTableColumnCategoryType.Paths; - break; - case "anyPath": - category = WixCustomTableColumnCategoryType.AnyPath; - break; - case "defaultDir": - category = WixCustomTableColumnCategoryType.DefaultDir; - break; - case "regPath": - category = WixCustomTableColumnCategoryType.RegPath; - break; - case "formatted": - category = WixCustomTableColumnCategoryType.Formatted; - break; - case "formattedSddl": - category = WixCustomTableColumnCategoryType.FormattedSddl; - break; - case "template": - category = WixCustomTableColumnCategoryType.Template; - break; - case "condition": - category = WixCustomTableColumnCategoryType.Condition; - break; - case "guid": - category = WixCustomTableColumnCategoryType.Guid; - break; - case "version": - category = WixCustomTableColumnCategoryType.Version; - break; - case "language": - category = WixCustomTableColumnCategoryType.Language; - break; - case "binary": - category = WixCustomTableColumnCategoryType.Binary; - break; - case "customSource": - category = WixCustomTableColumnCategoryType.CustomSource; - break; - case "cabinet": - category = WixCustomTableColumnCategoryType.Cabinet; - break; - case "shortcut": - category = WixCustomTableColumnCategoryType.Shortcut; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Category", categoryValue, - "text", "upperCase", "lowerCase", "integer", "doubleInteger", "timeDate", "identifier", "property", "filename", - "wildCardFilename", "path", "paths", "anyPath", "defaultDir", "regPath", "formatted", "formattedSddl", "template", - "condition", "guid", "version", "language", "binary", "customSource", "cabinet", "shortcut")); - columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. - break; - } + case "upperCase": + category = WixCustomTableColumnCategoryType.UpperCase; break; - case "Description": - description = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + case "lowerCase": + category = WixCustomTableColumnCategoryType.LowerCase; break; - case "KeyColumn": - keyColumn = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 1, 32); + case "integer": + category = WixCustomTableColumnCategoryType.Integer; break; - case "KeyTable": - keyTable = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + case "doubleInteger": + category = WixCustomTableColumnCategoryType.DoubleInteger; break; - case "Localizable": - localizable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + case "timeDate": + category = WixCustomTableColumnCategoryType.TimeDate; break; - case "MaxValue": - maxValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); + case "identifier": + category = WixCustomTableColumnCategoryType.Identifier; break; - case "MinValue": - minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); + case "property": + category = WixCustomTableColumnCategoryType.Property; break; - case "Modularize": - var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (modularizeValue) - { - case "column": - modularization = WixCustomTableColumnModularizeType.Column; - break; - case "companionFile": - modularization = WixCustomTableColumnModularizeType.CompanionFile; - break; - case "condition": - modularization = WixCustomTableColumnModularizeType.Condition; - break; - case "controlEventArgument": - modularization = WixCustomTableColumnModularizeType.ControlEventArgument; - break; - case "controlText": - modularization = WixCustomTableColumnModularizeType.ControlText; - break; - case "icon": - modularization = WixCustomTableColumnModularizeType.Icon; - break; - case "none": - modularization = WixCustomTableColumnModularizeType.None; - break; - case "property": - modularization = WixCustomTableColumnModularizeType.Property; - break; - case "semicolonDelimited": - modularization = WixCustomTableColumnModularizeType.SemicolonDelimited; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited")); - columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. - break; - } + case "filename": + category = WixCustomTableColumnCategoryType.Filename; break; - case "Nullable": - nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + case "wildCardFilename": + category = WixCustomTableColumnCategoryType.WildCardFilename; break; - case "PrimaryKey": - primaryKey = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + case "path": + category = WixCustomTableColumnCategoryType.Path; break; - case "Set": - setValues = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + case "paths": + category = WixCustomTableColumnCategoryType.Paths; break; - case "Type": - var typeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (typeValue) - { - case "binary": - columnType = IntermediateFieldType.Path; - break; - case "int": - columnType = IntermediateFieldType.Number; - break; - case "string": - columnType = IntermediateFieldType.String; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string")); - columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. - break; - } + case "anyPath": + category = WixCustomTableColumnCategoryType.AnyPath; + break; + case "defaultDir": + category = WixCustomTableColumnCategoryType.DefaultDir; + break; + case "regPath": + category = WixCustomTableColumnCategoryType.RegPath; + break; + case "formatted": + category = WixCustomTableColumnCategoryType.Formatted; + break; + case "formattedSddl": + category = WixCustomTableColumnCategoryType.FormattedSddl; + break; + case "template": + category = WixCustomTableColumnCategoryType.Template; + break; + case "condition": + category = WixCustomTableColumnCategoryType.Condition; + break; + case "guid": + category = WixCustomTableColumnCategoryType.Guid; break; - case "Width": - width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue); + case "version": + category = WixCustomTableColumnCategoryType.Version; break; - case "Unreal": - columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + case "language": + category = WixCustomTableColumnCategoryType.Language; + break; + case "binary": + category = WixCustomTableColumnCategoryType.Binary; + break; + case "customSource": + category = WixCustomTableColumnCategoryType.CustomSource; + break; + case "cabinet": + category = WixCustomTableColumnCategoryType.Cabinet; + break; + case "shortcut": + category = WixCustomTableColumnCategoryType.Shortcut; + break; + case "": break; default: - this.Core.UnexpectedAttribute(child, childAttrib); + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Category", categoryValue, + "text", "upperCase", "lowerCase", "integer", "doubleInteger", "timeDate", "identifier", "property", "filename", + "wildCardFilename", "path", "paths", "anyPath", "defaultDir", "regPath", "formatted", "formattedSddl", "template", + "condition", "guid", "version", "language", "binary", "customSource", "cabinet", "shortcut")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. break; - } - } - - if (null == columnName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); - } - - if (!columnType.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type")); } - else if (columnType == IntermediateFieldType.Number) - { - if (2 != width && 4 != width) - { - this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width)); - } - } - else if (columnType == IntermediateFieldType.Path) + break; + case "Description": + description = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + case "KeyColumn": + keyColumn = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 1, 32); + break; + case "KeyTable": + keyTable = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + case "Localizable": + localizable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + case "MaxValue": + maxValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); + break; + case "MinValue": + minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); + break; + case "Modularize": + var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (modularizeValue) { - if (!category.HasValue) - { - category = WixCustomTableColumnCategoryType.Binary; - } - else if (category != WixCustomTableColumnCategoryType.Binary) - { - this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); - } + case "column": + modularization = WixCustomTableColumnModularizeType.Column; + break; + case "companionFile": + modularization = WixCustomTableColumnModularizeType.CompanionFile; + break; + case "condition": + modularization = WixCustomTableColumnModularizeType.Condition; + break; + case "controlEventArgument": + modularization = WixCustomTableColumnModularizeType.ControlEventArgument; + break; + case "controlText": + modularization = WixCustomTableColumnModularizeType.ControlText; + break; + case "icon": + modularization = WixCustomTableColumnModularizeType.Icon; + break; + case "none": + modularization = WixCustomTableColumnModularizeType.None; + break; + case "property": + modularization = WixCustomTableColumnModularizeType.Property; + break; + case "semicolonDelimited": + modularization = WixCustomTableColumnModularizeType.SemicolonDelimited; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; } - - this.Core.ParseForExtensionElements(child); - - if (!this.Core.EncounteredError) + break; + case "Nullable": + nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + case "PrimaryKey": + primaryKey = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + case "Set": + setValues = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (typeValue) { - var attributes = primaryKey ? WixCustomTableColumnTupleAttributes.PrimaryKey : WixCustomTableColumnTupleAttributes.None; - attributes |= localizable ? WixCustomTableColumnTupleAttributes.Localizable : WixCustomTableColumnTupleAttributes.None; - attributes |= nullable ? WixCustomTableColumnTupleAttributes.Nullable : WixCustomTableColumnTupleAttributes.None; - attributes |= columnUnreal ? WixCustomTableColumnTupleAttributes.Unreal : WixCustomTableColumnTupleAttributes.None; - - var column = this.Core.AddTuple(new WixCustomTableColumnTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, tableId, columnName)) - { - TableRef = tableId, - Name = columnName, - Type = columnType.Value, - Attributes = attributes, - Width = width, - Category = category, - Description = description, - KeyColumn = keyColumn, - KeyTable = keyTable, - MaxValue = maxValue, - MinValue = minValue, - Modularize = modularization, - Set = setValues, - }); - columns.Add(column); + case "binary": + columnType = IntermediateFieldType.Path; + break; + case "int": + columnType = IntermediateFieldType.Number; + break; + case "string": + columnType = IntermediateFieldType.String; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; } break; - case "Row": - this.ParseRow(child, tableId); + case "Width": + width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue); + break; + case "Unreal": + columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); break; default: - this.Core.UnexpectedElement(node, child); + this.Core.UnexpectedAttribute(child, childAttrib); break; - } } - else + } + + if (null == columnName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); + } + + if (!columnType.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type")); + } + else if (columnType == IntermediateFieldType.Number) + { + if (2 != width && 4 != width) { - this.Core.ParseExtensionElement(node, child); + this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width)); } } - - if (columns.Count > 0) + else if (columnType == IntermediateFieldType.Path) { - if (!columns.Where(c => c.PrimaryKey).Any()) + if (!category.HasValue) { - this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers)); + category = WixCustomTableColumnCategoryType.Binary; } - - if (!this.Core.EncounteredError) + else if (category != WixCustomTableColumnCategoryType.Binary) { - var columnNames = String.Join(new string(WixCustomTableTuple.ColumnNamesSeparator, 1), columns.Select(c => c.Name)); - - this.Core.AddTuple(new WixCustomTableTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, tableId)) - { - ColumnNames = columnNames, - Unreal = unreal, - }); + this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); } } + + this.Core.ParseForExtensionElements(child); + + if (this.Core.EncounteredError) + { + return null; + } + + var attributes = primaryKey ? WixCustomTableColumnTupleAttributes.PrimaryKey : WixCustomTableColumnTupleAttributes.None; + attributes |= localizable ? WixCustomTableColumnTupleAttributes.Localizable : WixCustomTableColumnTupleAttributes.None; + attributes |= nullable ? WixCustomTableColumnTupleAttributes.Nullable : WixCustomTableColumnTupleAttributes.None; + attributes |= columnUnreal ? WixCustomTableColumnTupleAttributes.Unreal : WixCustomTableColumnTupleAttributes.None; + + var column = this.Core.AddTuple(new WixCustomTableColumnTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, tableId, columnName)) + { + TableRef = tableId, + Name = columnName, + Type = columnType.Value, + Attributes = attributes, + Width = width, + Category = category, + Description = description, + KeyColumn = keyColumn, + KeyTable = keyTable, + MaxValue = maxValue, + MinValue = minValue, + Modularize = modularization, + Set = setValues, + }); + return column; } - private void ParseRow(XElement node, string tableId) + /// + /// Parses a Row element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// Table Id. + private void ParseRowElement(XElement node, SourceLineNumber sourceLineNumbers, string tableId) { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var rowId = Guid.NewGuid().ToString("N").ToUpperInvariant(); foreach (var attrib in node.Attributes()) @@ -6204,6 +6221,9 @@ namespace WixToolset.Core case "BootstrapperApplicationRef": this.ParseBootstrapperApplicationRefElement(child); break; + case "BundleCustomData": + this.ParseBundleCustomDataElement(child); + break; case "BundleExtension": this.ParseBundleExtensionElement(child); break; diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 31896a42..5154a72f 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -7,6 +7,7 @@ namespace WixToolset.Core using System.Diagnostics; using System.Globalization; using System.IO; + using System.Linq; using System.Xml.Linq; using WixToolset.Data; using WixToolset.Data.Burn; @@ -276,6 +277,9 @@ namespace WixToolset.Core case "BootstrapperApplicationRef": this.ParseBootstrapperApplicationRefElement(child); break; + case "BundleCustomData": + this.ParseBundleCustomDataElement(child); + break; case "BundleExtension": this.ParseBundleExtensionElement(child); break; @@ -767,6 +771,246 @@ namespace WixToolset.Core } } + + + /// + /// Parses a BundleCustomData element. + /// + /// Element to parse. + private void ParseBundleCustomDataElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string customDataId = null; + WixBundleCustomDataType? customDataType = null; + string extensionId = null; + var attributeDefinitions = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "bootstrapperApplication": + customDataType = WixBundleCustomDataType.BootstrapperApplication; + break; + case "bundleExtension": + customDataType = WixBundleCustomDataType.BundleExtension; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "bootstrapperApplication", "bundleExtension")); + customDataType = WixBundleCustomDataType.Unknown; // set a value to prevent expected attribute error below. + break; + } + break; + case "ExtensionId": + extensionId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.WixBundleExtension, extensionId); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == customDataId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + var hasExtensionId = null != extensionId; + if (hasExtensionId && !customDataType.HasValue) + { + customDataType = WixBundleCustomDataType.BundleExtension; + } + + if (!customDataType.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); + } + else if (hasExtensionId) + { + if (customDataType.Value == WixBundleCustomDataType.BootstrapperApplication) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensonId", "Type", "bootstrapperApplication")); + } + } + else if (customDataType.Value == WixBundleCustomDataType.BundleExtension) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensionId", "Type", "bundleExtension")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "BundleAttributeDefinition": + var attributeDefinition = this.ParseBundleAttributeDefinitionElement(child, childSourceLineNumbers, customDataId); + if (attributeDefinition != null) + { + attributeDefinitions.Add(attributeDefinition); + } + break; + case "BundleElement": + this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (attributeDefinitions.Count > 0) + { + if (!this.Core.EncounteredError) + { + var attributeNames = String.Join(new string(WixBundleCustomDataTuple.AttributeNamesSeparator, 1), attributeDefinitions.Select(c => c.Name)); + + this.Core.AddTuple(new WixBundleCustomDataTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, customDataId)) + { + AttributeNames = attributeNames, + Type = customDataType.Value, + BundleExtensionRef = extensionId, + }); + } + } + } + + /// + /// Parses a BundleAttributeDefinition element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// BundleCustomData Id. + private WixBundleCustomDataAttributeTuple ParseBundleAttributeDefinitionElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) + { + string attributeName = null; + + foreach (var attrib in node.Attributes()) + { + switch (attrib.Name.LocalName) + { + case "Id": + attributeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + + if (null == attributeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (this.Core.EncounteredError) + { + return null; + } + + var customDataAttribute = this.Core.AddTuple(new WixBundleCustomDataAttributeTuple(sourceLineNumbers, new Identifier(AccessModifier.Private, customDataId, attributeName)) + { + CustomDataRef = customDataId, + Name = attributeName, + }); + return customDataAttribute; + } + + /// + /// Parses a BundleElement element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// BundleCustomData Id. + private void ParseBundleElementElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) + { + var elementId = Guid.NewGuid().ToString("N").ToUpperInvariant(); + + foreach (var attrib in node.Attributes()) + { + this.Core.ParseExtensionAttribute(node, attrib); + } + + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "BundleAttribute": + string attributeName = null; + string value = null; + foreach (var attrib in child.Attributes()) + { + switch (attrib.Name.LocalName) + { + case "Id": + attributeName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.ParseExtensionAttribute(child, attrib); + break; + } + } + + if (null == attributeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); + } + + if (String.IsNullOrEmpty(value)) + { + value = Common.GetInnerText(child); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddTuple(new WixBundleCustomDataCellTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, customDataId, elementId, attributeName)) + { + ElementId = elementId, + AttributeRef = attributeName, + CustomDataRef = customDataId, + Value = value, + }); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + + if (!this.Core.EncounteredError) + { + this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.WixBundleCustomData, customDataId); + } + } + /// /// Parse the BundleExtension element. /// diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs index 4b1ec718..ae83150a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -13,7 +13,7 @@ namespace WixToolsetTest.CoreIntegration public class BundleManifestFixture { [Fact] - public void PopulatesBAManifestWithUnrealCustomTable() + public void PopulatesBAManifestWithBootstrapperApplicationBundleCustomData() { var folder = TestData.Get(@"TestData"); @@ -43,11 +43,50 @@ namespace WixToolsetTest.CoreIntegration var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); extractResult.AssertSuccess(); - var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTable"); + var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTableBA"); Assert.Equal(3, customElements.Count); - Assert.Equal("", customElements[0].GetTestXml()); - Assert.Equal("", customElements[1].GetTestXml()); - Assert.Equal("", customElements[2].GetTestXml()); + Assert.Equal("", customElements[0].GetTestXml()); + Assert.Equal("", customElements[1].GetTestXml()); + Assert.Equal("", customElements[2].GetTestXml()); + } + } + + [Fact] + public void PopulatesBEManifestWithBundleExtensionBundleCustomData() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var customElements = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='CustomTableExtension']/be:BundleCustomTableBE"); + Assert.Equal(3, customElements.Count); + Assert.Equal("", customElements[0].GetTestXml()); + Assert.Equal("", customElements[1].GetTestXml()); + Assert.Equal("", customElements[2].GetTestXml()); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs index 85a0ffae..afba1cbc 100644 --- a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs @@ -3,7 +3,6 @@ namespace WixToolsetTest.CoreIntegration { using System.IO; - using Microsoft.Build.Tasks; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using Xunit; @@ -159,36 +158,6 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact] - public void UnrealCustomTableIsNotPresentInMsi() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" }); - Assert.Empty(results); - } - } - [Fact] public void CanCompileAndDecompile() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs index dacbc014..38d207ca 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs @@ -5,22 +5,42 @@ - - - + + + - - one - two - - - < - > - - - 1 - 2 - - + + one + two + + + < + > + + + 1 + 2 + + + + + + + + + one + two + + + < + > + + + 1 + 2 + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs index 51aee5f2..d6a2521e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs @@ -17,18 +17,5 @@ test.txt - - - - - - RowA - test.txt - - - RowB - test.txt - - -- cgit v1.2.3-55-g6feb From 89416eb8c4e7dc8ae4dd2aa27aa7c5930421f61a Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 19 Jun 2020 14:44:23 +1000 Subject: Restore Unreal to CustomTable. --- .../Bind/CreateOutputFromIRCommand.cs | 2 -- .../Bind/LoadTableDefinitionsCommand.cs | 2 +- src/WixToolset.Core/Compiler.cs | 5 ++++ .../CustomTableFixture.cs | 30 ++++++++++++++++++++++ .../TestData/CustomTable/CustomTable.wxs | 13 ++++++++++ 5 files changed, 49 insertions(+), 3 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 553b470b..90d1c148 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -4,7 +4,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind { using System; using System.Collections.Generic; - using System.Diagnostics; using System.Globalization; using System.Linq; using WixToolset.Data; @@ -979,7 +978,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (customTableDefinition.Unreal) { - Debug.Assert(false, "CustomTableDefinition should never be unreal."); continue; } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs index cfb46ff9..0312ab44 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs @@ -208,7 +208,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind columns.Add(columnDefinition); } - var customTable = new TableDefinition(tuple.Id.Id, null, columns); + var customTable = new TableDefinition(tuple.Id.Id, null, columns, tuple.Unreal); return customTable; } } diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index bbd6b292..cb85281d 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -3668,6 +3668,7 @@ namespace WixToolset.Core { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string tableId = null; + var unreal = false; var columns = new List(); foreach (var attrib in node.Attributes()) @@ -3679,6 +3680,9 @@ namespace WixToolset.Core case "Id": tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; + case "Unreal": + unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; default: this.Core.UnexpectedAttribute(node, attrib); break; @@ -3741,6 +3745,7 @@ namespace WixToolset.Core this.Core.AddTuple(new WixCustomTableTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, tableId)) { ColumnNames = columnNames, + Unreal = unreal, }); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs index afba1cbc..0a45c914 100644 --- a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs @@ -158,6 +158,36 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void UnrealCustomTableIsNotPresentInMsi() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" }); + Assert.Empty(results); + } + } + [Fact] public void CanCompileAndDecompile() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs index d6a2521e..51aee5f2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs @@ -17,5 +17,18 @@ test.txt + + + + + + RowA + test.txt + + + RowB + test.txt + + -- cgit v1.2.3-55-g6feb From b3197fdf8b437d0d8fcc2e564cb1e3484bb1392a Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 21 Jun 2020 16:40:17 +1000 Subject: Add BundleCustomDataRef element. Default BundleCustomData to Type=bootstrapperApplication. --- src/WixToolset.Core/Compiler.cs | 3 + src/WixToolset.Core/Compiler_Bundle.cs | 77 +++++++++++++++++++++- .../BundleCustomTable/BundleCustomTable.wxs | 21 ++++-- 3 files changed, 92 insertions(+), 9 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index cb85281d..9681cb3f 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -6229,6 +6229,9 @@ namespace WixToolset.Core case "BundleCustomData": this.ParseBundleCustomDataElement(child); break; + case "BundleCustomDataRef": + this.ParseBundleCustomDataRefElement(child); + break; case "BundleExtension": this.ParseBundleExtensionElement(child); break; diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 5154a72f..2b274474 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -280,6 +280,9 @@ namespace WixToolset.Core case "BundleCustomData": this.ParseBundleCustomDataElement(child); break; + case "BundleCustomDataRef": + this.ParseBundleCustomDataRefElement(child); + break; case "BundleExtension": this.ParseBundleExtensionElement(child); break; @@ -784,6 +787,7 @@ namespace WixToolset.Core WixBundleCustomDataType? customDataType = null; string extensionId = null; var attributeDefinitions = new List(); + var foundAttributeDefinitions = false; foreach (var attrib in node.Attributes()) { @@ -831,9 +835,9 @@ namespace WixToolset.Core } var hasExtensionId = null != extensionId; - if (hasExtensionId && !customDataType.HasValue) + if (!customDataType.HasValue) { - customDataType = WixBundleCustomDataType.BundleExtension; + customDataType = hasExtensionId ? WixBundleCustomDataType.BundleExtension : WixBundleCustomDataType.BootstrapperApplication; } if (!customDataType.HasValue) @@ -860,6 +864,8 @@ namespace WixToolset.Core switch (child.Name.LocalName) { case "BundleAttributeDefinition": + foundAttributeDefinitions = true; + var attributeDefinition = this.ParseBundleAttributeDefinitionElement(child, childSourceLineNumbers, customDataId); if (attributeDefinition != null) { @@ -894,6 +900,73 @@ namespace WixToolset.Core }); } } + else if (!foundAttributeDefinitions) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "BundleAttributeDefinition")); + } + } + + /// + /// Parses a BundleCustomDataRef element. + /// + /// Element to parse. + private void ParseBundleCustomDataRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string customDataId = null; + var foundChild = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == customDataId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + foreach (var child in node.Elements()) + { + foundChild = true; + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "BundleElement": + this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!foundChild) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName)); + } } /// diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs index 38d207ca..8482a57e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs @@ -5,7 +5,7 @@ - + @@ -17,16 +17,23 @@ < > - - 1 - 2 - - + + + + + + + 1 + 2 + + + + one two @@ -39,7 +46,7 @@ 1 2 - + -- cgit v1.2.3-55-g6feb From 461350c09839f1e59fb3dafe1a67e74bf152f803 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 22 Jun 2020 19:35:34 -0400 Subject: If no Media or MediaTemplate is present, default to a MediaTemplate. --- .../Bind/AssignMediaCommand.cs | 12 ++++++++ src/WixToolset.Core/Compiler.cs | 9 ------ src/WixToolset.Core/Linker.cs | 1 - .../WixToolsetTest.CoreIntegration/CabFixture.cs | 36 ++++++++++++++++++++++ .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 4 +-- .../TestData/Assembly/Package.wxs | 1 - .../TestData/BadIf/Package.wxs | 1 - .../TestData/ComplexExampleExtension/Package.wxs | 1 - .../TestData/Components/Package.wxs | 1 - .../TestData/ErrorsInUI/Package.wxs | 1 - .../TestData/ExampleExtension/Package.wxs | 1 - .../TestData/ForEach/Package.wxs | 1 - .../TestData/IncludePath/Package.wxs | 1 - .../TestData/InstanceTransform/Package.wxs | 1 - .../TestData/ManualUpgrade/Package.wxs | 2 -- .../TestData/MultiFileCompressed/Package.wxs | 3 +- .../MultiFileCompressed/PackageComponents.wxs | 4 +-- .../TestData/OverridableActions/Package.wxs | 1 - .../TestData/PatchFamilyFilter/Package.wxs | 1 - .../ProductWithComponentGroupRef/Product.wxs | 1 - .../TestData/ProgId/Package.wxs | 1 - .../TestData/SetProperty/Package.wxs | 1 - .../TestData/SimpleMerge/Package.wxs | 3 +- .../TestData/SingleFile/Package.wxs | 1 - .../TestData/Variables/Package.wxs | 1 - .../TestData/Wixipl/Package.wxs | 1 - .../TestData/WixlibWithBinaries/Package.wxs | 1 - 27 files changed, 55 insertions(+), 37 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs index b75956b4..773b3225 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs @@ -59,6 +59,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind throw new WixException(ErrorMessages.MediaTableCollision(null)); } + // If neither tuple is authored, default to a media template. + if (SectionType.Product == this.Section.Type && mediaTemplateTuples.Count == 0 && mediaTuples.Count == 0) + { + var mediaTemplate = new WixMediaTemplateTuple() + { + CabinetTemplate = "cab{0}.cab", + }; + + this.Section.AddTuple(mediaTemplate); + mediaTemplateTuples.Add(mediaTemplate); + } + // When building merge module, all the files go to "#MergeModule.CABinet". if (SectionType.Module == this.Section.Type) { diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 9681cb3f..ae7f7624 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -5870,7 +5870,6 @@ namespace WixToolset.Core } } - if (!this.Core.EncounteredError) { var patchAttributes = PatchAttributeType.None; @@ -5957,8 +5956,6 @@ namespace WixToolset.Core } } - this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); - // If this component does not have a companion file this file is a possible keypath. possibleKeyPath = null; if (null == companionFile) @@ -7677,12 +7674,6 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); } - if (CompilerConstants.IntegerNotSet == diskId) - { - this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "DiskId", "Directory")); - diskId = CompilerConstants.IllegalInteger; - } - foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index fb1b2488..cbdb46d0 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -140,7 +140,6 @@ namespace WixToolset.Core #endif // First find the entry section and while processing all sections load all the symbols from all of the sections. - // sections.FindEntrySectionAndLoadSymbols(false, this, expectedOutputType, out entrySection, out allSymbols); var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, sections, this.Context.ExpectedOutputType); find.Execute(); diff --git a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs index 79471554..5aef148e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs @@ -49,6 +49,42 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Sequence number of file from merge module is 0 but should be 1.")] + public void CabinetFilesSequencedCorrectlyUsingMergeModule() + { + var folder = TestData.Get(@"TestData\SimpleMerge"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, ".data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + Assert.True(File.Exists(cabPath)); + + var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); + var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); + + Assert.Equal(new[] { 1 }, fileRows.Select(f => f.Sequence).ToArray()); + Assert.Equal(new[] { "test.txt" }, fileRows.Select(f => f.Name).ToArray()); + + var files = Query.GetCabinetFiles(cabPath); + Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); + } + } + private class FileRow { public FileRow(string row) diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index 69258ae4..fbfebc5b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -164,7 +164,6 @@ namespace WixToolsetTest.CoreIntegration "build", Path.Combine(folder, "Package.wxs"), Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel", "-loc", Path.Combine(folder, "Package.en-us.wxl"), "-bindpath", Path.Combine(folder, "data"), "-intermediateFolder", intermediateFolder, @@ -174,7 +173,8 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example2.cab"))); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs index d9a2a34e..dbce4c71 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs index 73577ce5..c1cf55c2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs index 0e8e9795..f3dd9a02 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs index 6da3dcbe..28d564e2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs index a4c01d7e..59eeb027 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs index bff5f609..f20f5f73 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs index 4bc7e2a4..3bd14fbb 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs index 8deab961..59c8b2b3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs @@ -5,7 +5,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs index 9c529668..e55b3ec6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs index 38125b57..025aaaa3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs @@ -12,8 +12,6 @@ - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs index 0b743c81..44b8c2b5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs @@ -6,7 +6,8 @@ - + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs index 82797ebe..1a040fa3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs @@ -3,10 +3,10 @@ - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs index db773d17..cc873a62 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs @@ -5,7 +5,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs index 2657797f..4baeb85b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs @@ -5,7 +5,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs index e0e5345a..b2f22b7d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs index d4b53cd6..388a271e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs index eb907569..879fad35 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs index 303e2ba8..b04c5d1a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs @@ -4,7 +4,6 @@ - @@ -16,7 +15,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs index 6da3dcbe..28d564e2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs index 57c24f57..8d49c30e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs @@ -17,7 +17,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs index 15807698..00a80fca 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs @@ -4,7 +4,6 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs index 85dcb695..4c36f3cc 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs @@ -4,7 +4,6 @@ - -- cgit v1.2.3-55-g6feb From 457c144720964a7f50b1d184e6b19faa930e970e Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 17 Jun 2020 12:10:28 -0700 Subject: Introduce Value attribute on MultiString/MultiStringValue elements --- src/WixToolset.Core/Compiler_2.cs | 43 +++++++- .../MsiQueryFixture.cs | 65 ------------ .../RegistryFixture.cs | 114 +++++++++++++++++++++ .../TestData/Registry/RegistryValueMultiString.wxs | 15 +++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 5 files changed, 168 insertions(+), 70 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index 9e2ddb5b..84961f9b 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -1964,18 +1964,15 @@ namespace WixToolset.Core { switch (child.Name.LocalName) { + case "MultiString": case "MultiStringValue": if (RegistryValueType.MultiString != valueType && null != value) { this.Core.Write(ErrorMessages.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type")); } - else if (null == value) - { - value = Common.GetInnerText(child); - } else { - value = String.Concat(value, "[~]", Common.GetInnerText(child)); + value = this.ParseRegistryMultiStringElement(child, value); } break; case "Permission": @@ -2069,6 +2066,42 @@ namespace WixToolset.Core return keyPath; } + private string ParseRegistryMultiStringElement(XElement node, string value) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string multiStringValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + multiStringValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + if (multiStringValue == null) + { + multiStringValue = Common.GetInnerText(node); + } + + if (multiStringValue == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + return (null == value) ? multiStringValue : String.Concat(value, "[~]", multiStringValue); + } + /// /// Parses a RemoveRegistryKey element. /// diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 70d6612e..c78b0c29 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -804,71 +804,6 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact] - public void PopulatesRegistryTableFromRegistryValue() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "RegistryValue.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - Assert.Equal(new[] - { - "Registry:reg04OIwIchl.9ZTjisTT6NzGSsQSM\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMiscComponent", - "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent", - }, results); - } - } - - [Fact] - public void PopulatesRegistryTableFromRemoveRegistryKey() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "RemoveRegistryKey.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - Assert.Equal(new[] - { - "Registry:RemoveAKeyName\t2\tAKeyName\t-\t\tRemoveRegistryKeyComp", - }, results); - } - } - [Fact] public void PopulatesReserveCostTable() { diff --git a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs new file mode 100644 index 00000000..2a1e2a49 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs @@ -0,0 +1,114 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class RegistryFixture + { + [Fact] + public void PopulatesRegistryTableFromRegistryValue() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RegistryValue.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + Assert.Equal(new[] + { + "Registry:reg04OIwIchl.9ZTjisTT6NzGSsQSM\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMiscComponent", + "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent", + }, results); + } + } + + [Fact] + public void PopulatesRegistryTableFromRegistryValueMultiString() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RegistryValueMultiString.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + Assert.Equal(new[] + { + "Registry:regitq_Wx9LfvJuNSc2un6gIHAzr4A\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMultiStringComponent", + "Registry:regmeTJMpOD41igfxhTcUVZ7kNG1Mo\t2\tPath\\To\\Key\t\ta[~]b[~]c\tMultiStringComponent", + }, results); + } + } + + [Fact] + public void PopulatesRegistryTableFromRemoveRegistryKey() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RemoveRegistryKey.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + Assert.Equal(new[] + { + "Registry:RemoveAKeyName\t2\tAKeyName\t-\t\tRemoveRegistryKeyComp", + }, results); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs new file mode 100644 index 00000000..d5c680ee --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 7ede6655..c86a691f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -74,6 +74,7 @@ + -- cgit v1.2.3-55-g6feb From 099a5bc83bfde2a713a94d47e613dba16fdcee2a Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 17 Jun 2020 12:11:17 -0700 Subject: Introduce Message attribute to remove Error inner text --- src/WixToolset.Core/Compiler.cs | 11 ++++++++++- .../TestData/ErrorsInUI/PackageComponents.wxs | 6 ++---- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index ae7f7624..d6c96b28 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -5358,6 +5358,7 @@ namespace WixToolset.Core { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var id = CompilerConstants.IntegerNotSet; + string message = null; foreach (var attrib in node.Attributes()) { @@ -5368,6 +5369,9 @@ namespace WixToolset.Core case "Id": id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); break; + case "Message": + message = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; default: this.Core.UnexpectedAttribute(node, attrib); break; @@ -5385,13 +5389,18 @@ namespace WixToolset.Core id = CompilerConstants.IllegalInteger; } + if (String.IsNullOrEmpty(message)) + { + message = Common.GetInnerText(node); + } + this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { this.Core.AddTuple(new ErrorTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, id)) { - Message = Common.GetInnerText(node) + Message = message }); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs index c9c65fc7..88a4ac81 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs @@ -2,10 +2,8 @@ - - Category 55 Emergency Doomsday Crisis - - + + -- cgit v1.2.3-55-g6feb From ed0fb39537c0cfb13922537a26f9d895180d42d8 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 23 Jun 2020 00:56:36 -0700 Subject: Remove obsolete inner text handling --- src/WixToolset.Core/Common.cs | 6 +- src/WixToolset.Core/Compiler.cs | 188 --------------------- src/WixToolset.Core/CompilerCore.cs | 20 --- src/WixToolset.Core/Compiler_2.cs | 113 +------------ src/WixToolset.Core/Compiler_Bundle.cs | 5 - src/WixToolset.Core/Compiler_EmbeddedUI.cs | 6 - src/WixToolset.Core/Compiler_UI.cs | 43 ----- .../BundleCustomTable/BundleCustomTable.wxs | 24 +-- .../CustomAction/UnscheduledCustomAction.wxs | 2 +- .../TestData/CustomTable/CustomTable.wxs | 16 +- .../TestData/CustomTable/CustomTableWithFile.wxs | 8 +- .../PackageComponents.wxs | 15 +- 12 files changed, 37 insertions(+), 409 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs index e6f30e0c..183d7abd 100644 --- a/src/WixToolset.Core/Common.cs +++ b/src/WixToolset.Core/Common.cs @@ -690,10 +690,8 @@ namespace WixToolset.Core /// /// Gets the text of an XElement. /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// A delegate that receives error messages. - /// The attribute's YesNoType value. + /// Element to get text. + /// The element's text. internal static string GetInnerText(XElement node) { var text = node.Nodes().Where(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType).Cast().FirstOrDefault(); diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 5bd923dd..28290569 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -2319,14 +2319,6 @@ namespace WixToolset.Core case "Class": this.ParseClassElement(child, id.Id, YesNoType.NotSet, null, null, null, null); break; - case "Condition": - if (null != condition) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); - } - condition = this.ParseConditionElement(child, node.Name.LocalName, null, null); - break; case "CopyFile": this.ParseCopyFileElement(child, id.Id, null); break; @@ -3382,17 +3374,12 @@ namespace WixToolset.Core win64 = true; } - // get the inner text if any exists - var innerText = this.Core.GetTrimmedInnerText(node); - // if we have an in-lined Script CustomAction ensure no source or target attributes were provided if (inlineScript) { if (String.IsNullOrEmpty(scriptFile)) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ScriptFile", "Script")); - - target = innerText; } } else if (CustomActionTargetType.VBScript == targetType) // non-inline vbscript @@ -3428,10 +3415,6 @@ namespace WixToolset.Core { this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property", "Error")); } - else if (!String.IsNullOrEmpty(innerText)) // inner text cannot be specified with non-script CAs - { - this.Core.Write(ErrorMessages.CustomActionIllegalInnerText(sourceLineNumbers, node.Name.LocalName, innerText, "Script")); - } if (!inlineScript && !String.IsNullOrEmpty(scriptFile)) { @@ -4053,11 +4036,6 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Column")); } - if (String.IsNullOrEmpty(data)) - { - data = Common.GetInnerText(child); - } - if (!this.Core.EncounteredError) { this.Core.AddTuple(new WixCustomTableCellTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, tableId, rowId, columnName)) @@ -4864,9 +4842,6 @@ namespace WixToolset.Core case "Component": this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id.Id, null, CompilerConstants.IntegerNotSet, null, null); break; - case "Condition": - this.ParseConditionElement(child, node.Name.LocalName, id.Id, null); - break; case "Feature": this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id.Id, ref childDisplay); break; @@ -5368,11 +5343,6 @@ namespace WixToolset.Core id = CompilerConstants.IllegalInteger; } - if (String.IsNullOrEmpty(message)) - { - message = Common.GetInnerText(node); - } - this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) @@ -6232,9 +6202,6 @@ namespace WixToolset.Core case "ComponentGroup": this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); break; - case "Condition": - this.ParseConditionElement(child, node.Name.LocalName, null, null); - break; case "Container": this.ParseContainerElement(child); break; @@ -6418,161 +6385,6 @@ namespace WixToolset.Core } } - /// - /// Parses a condition element. - /// - /// Element to parse. - /// LocalName of the parent element. - /// Id of the parent element. - /// Dialog of the parent element if its a Control. - /// The condition if one was found. - private string ParseConditionElement(XElement node, string parentElementLocalName, string id, string dialog) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string action = null; - string condition = null; - var level = CompilerConstants.IntegerNotSet; - string message = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - if ("Control" == parentElementLocalName) - { - action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (action) - { - case "default": - action = "Default"; - break; - case "disable": - action = "Disable"; - break; - case "enable": - action = "Enable"; - break; - case "hide": - action = "Hide"; - break; - case "show": - action = "Show"; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "default", "disable", "enable", "hide", "show")); - break; - } - } - else - { - this.Core.UnexpectedAttribute(node, attrib); - } - break; - case "Level": - if ("Feature" == parentElementLocalName) - { - level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - } - else - { - this.Core.UnexpectedAttribute(node, attrib); - } - break; - case "Message": - if ("Fragment" == parentElementLocalName || "Product" == parentElementLocalName) - { - message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - } - else - { - this.Core.UnexpectedAttribute(node, attrib); - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // get the condition from the inner text of the element - condition = this.Core.GetConditionInnerText(node); - - this.Core.ParseForExtensionElements(node); - - // the condition should not be empty - if (null == condition || 0 == condition.Length) - { - condition = null; - this.Core.Write(ErrorMessages.ConditionExpected(sourceLineNumbers, node.Name.LocalName)); - } - - switch (parentElementLocalName) - { - case "Control": - if (null == action) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddTuple(new ControlConditionTuple(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = id, - Action = action, - Condition = condition, - }); - } - break; - case "Feature": - if (CompilerConstants.IntegerNotSet == level) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); - level = CompilerConstants.IllegalInteger; - } - - if (!this.Core.EncounteredError) - { - this.Core.AddTuple(new ConditionTuple(sourceLineNumbers) - { - FeatureRef = id, - Level = level, - Condition = condition - }); - } - break; - case "Fragment": - case "Product": - if (null == message) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Message")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddTuple(new LaunchConditionTuple(sourceLineNumbers) - { - Condition = condition, - Description = message - }); - } - break; - } - - return condition; - } - /// /// Parses a IniFile element. /// diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index 93f9276c..5d0edaf1 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -321,26 +321,6 @@ namespace WixToolset.Core return Common.IsValidModuleOrBundleVersion(version); } - /// - /// Get an element's inner text and trims any extra whitespace. - /// - /// The element with inner text to be trimmed. - /// The node's inner text trimmed. - public string GetTrimmedInnerText(XElement element) - { - return this.parseHelper.GetTrimmedInnerText(element); - } - - /// - /// Gets element's inner text and ensure's it is safe for use in a condition by trimming any extra whitespace. - /// - /// The element to ensure inner text is a condition. - /// The value converted into a safe condition. - public string GetConditionInnerText(XElement element) - { - return this.parseHelper.GetConditionInnerText(element); - } - /// /// Creates a version 3 name-based UUID. /// diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index df4c037d..18a0366e 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -186,9 +186,6 @@ namespace WixToolset.Core case "ComponentGroup": this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); break; - case "Condition": - this.ParseConditionElement(child, node.Name.LocalName, null, null); - break; case "CustomAction": this.ParseCustomActionElement(child); break; @@ -228,6 +225,9 @@ namespace WixToolset.Core case "InstanceTransforms": this.ParseInstanceTransformsElement(child); break; + case "Launch": + this.ParseLaunchElement(child); + break; case "MajorUpgrade": this.ParseMajorUpgradeElement(child, contextValues); break; @@ -1235,31 +1235,7 @@ namespace WixToolset.Core id = this.Core.CreateIdentifier("pme", objectId, tableName, sddl); } - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Condition": - if (null != condition) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); - } - - condition = this.ParseConditionElement(child, node.Name.LocalName, null, null); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } + this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) { @@ -1537,20 +1513,6 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); } - var innerText = this.Core.GetTrimmedInnerText(node); - if (null != value) - { - // cannot specify both the value attribute and inner text - if (!String.IsNullOrEmpty(innerText)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithInnerText(sourceLineNumbers, node.Name.LocalName, "Value")); - } - } - else // value attribute not specified, use inner text if any. - { - value = innerText; - } - if ("ErrorDialog" == id.Id) { this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.Dialog, value); @@ -2090,11 +2052,6 @@ namespace WixToolset.Core } } - if (multiStringValue == null) - { - multiStringValue = Common.GetInnerText(node); - } - if (multiStringValue == null) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); @@ -2683,12 +2640,6 @@ namespace WixToolset.Core } } - // Get the condition from the inner text of the element. - if (condition == null) - { - condition = this.Core.GetConditionInnerText(child); - } - if (customAction && "Custom" == actionName) { this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); @@ -3152,11 +3103,6 @@ namespace WixToolset.Core } } - if (privilege == null) - { - privilege = Common.GetInnerText(node); - } - if (privilege == null) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); @@ -3518,11 +3464,6 @@ namespace WixToolset.Core } } - if (argument == null) - { - argument = this.Core.GetTrimmedInnerText(node); - } - if (argument == null) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); @@ -3860,11 +3801,6 @@ namespace WixToolset.Core } } - if (condition == null) - { - condition = this.Core.GetConditionInnerText(node); - } - if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); @@ -3957,29 +3893,6 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); break; } - //if (0 < sequenceValue.Length) - //{ - // var sequenceType = Wix.Enums.ParseSequenceType(sequenceValue); - // switch (sequenceType) - // { - // case Wix.SequenceType.execute: - // sequences = new string[] { "InstallExecuteSequence" }; - // break; - // case Wix.SequenceType.ui: - // sequences = new string[] { "InstallUISequence" }; - // break; - // case Wix.SequenceType.first: - // firstSequence = true; - // // default puts it in both sequence which is what we want - // break; - // case Wix.SequenceType.both: - // // default so no work necessary. - // break; - // default: - // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); - // break; - // } - //} break; case "Value": value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); @@ -3995,11 +3908,6 @@ namespace WixToolset.Core } } - if (condition == null) - { - condition = this.Core.GetConditionInnerText(node); - } - if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); @@ -4528,19 +4436,6 @@ namespace WixToolset.Core id = this.Core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant()); } - var innerText = this.Core.GetTrimmedInnerText(node); - if (!String.IsNullOrEmpty(innerText)) - { - if (String.IsNullOrEmpty(value)) - { - value = innerText; - } - else // cannot specify both the value attribute and inner text - { - this.Core.Write(ErrorMessages.IllegalAttributeWithInnerText(sourceLineNumbers, node.Name.LocalName, "Value")); - } - } - if (String.IsNullOrEmpty(value)) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 2b274474..d88cb7f5 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -1056,11 +1056,6 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); } - if (String.IsNullOrEmpty(value)) - { - value = Common.GetInnerText(child); - } - if (!this.Core.EncounteredError) { this.Core.AddTuple(new WixBundleCustomDataCellTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, customDataId, elementId, attributeName)) diff --git a/src/WixToolset.Core/Compiler_EmbeddedUI.cs b/src/WixToolset.Core/Compiler_EmbeddedUI.cs index 1827a200..847ee2a8 100644 --- a/src/WixToolset.Core/Compiler_EmbeddedUI.cs +++ b/src/WixToolset.Core/Compiler_EmbeddedUI.cs @@ -80,12 +80,6 @@ namespace WixToolset.Core } } - if (condition == null) - { - // Get the condition from the inner text of the element. - condition = this.Core.GetConditionInnerText(node); - } - if (null == id) { id = this.Core.CreateIdentifier("mec", source, type.ToString()); diff --git a/src/WixToolset.Core/Compiler_UI.cs b/src/WixToolset.Core/Compiler_UI.cs index 5c2400e4..5066ff1a 100644 --- a/src/WixToolset.Core/Compiler_UI.cs +++ b/src/WixToolset.Core/Compiler_UI.cs @@ -700,11 +700,6 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); } - if (null == message) - { - message = Common.GetInnerText(node); - } - this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) @@ -751,11 +746,6 @@ namespace WixToolset.Core } } - if (null == text) - { - text = Common.GetInnerText(node); - } - if (null == id) { id = this.Core.CreateIdentifier("txt", text); @@ -1270,26 +1260,6 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "16", "32", "48")); break; } - //if (0 < iconSizeValue.Length) - //{ - // var iconsSizeType = Wix.Control.ParseIconSizeType(iconSizeValue); - // switch (iconsSizeType) - // { - // case Wix.Control.IconSizeType.Item16: - // this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); - // break; - // case Wix.Control.IconSizeType.Item32: - // this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); - // break; - // case Wix.Control.IconSizeType.Item48: - // this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); - // this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); - // break; - // default: - // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "16", "32", "48")); - // break; - // } - //} } else { @@ -1411,9 +1381,6 @@ namespace WixToolset.Core case "ComboBox": this.ParseControlGroupElement(child, TupleDefinitionType.ComboBox, "ListItem"); break; - case "Condition": - this.ParseConditionElement(child, node.Name.LocalName, controlId.Id, dialog); - break; case "ListBox": this.ParseControlGroupElement(child, TupleDefinitionType.ListBox, "ListItem"); break; @@ -1456,11 +1423,6 @@ namespace WixToolset.Core } } - if (null == text) - { - text = Common.GetInnerText(child); - } - if (!String.IsNullOrEmpty(text) && null != sourceFile) { this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "SourceFile", "Text")); @@ -1728,11 +1690,6 @@ namespace WixToolset.Core } } - if (null == condition) - { - condition = this.Core.GetConditionInnerText(node); - } - if (null == control) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Control")); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs index 8482a57e..db755171 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs @@ -10,12 +10,12 @@ - one - two + + - < - > + + @@ -28,23 +28,23 @@ - 1 - 2 + + - one - two + + - < - > + + - 1 - 2 + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs index 2846d16e..0784824a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs @@ -27,7 +27,7 @@ - Progess2Text + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs index 51aee5f2..7f4a43e5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs @@ -9,12 +9,12 @@ - Row1 - test.txt + + - Row2 - test.txt + + @@ -22,12 +22,12 @@ - RowA - test.txt + + - RowB - test.txt + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs index ad5ed233..08a9c470 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs @@ -9,12 +9,12 @@ - Row1 - file1.txt + + - SourceDir\file2.txt - Row2 + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs index 1101d901..c6deb864 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs @@ -4,16 +4,13 @@ - - Installed - Installed - + - + @@ -29,8 +26,8 @@ Installed AND PATCH - Installed AND PATCH - NOT Installed + + -- cgit v1.2.3-55-g6feb From 25602a3e613f09794599d24e0c796d3295a22197 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Tue, 23 Jun 2020 23:26:22 -0400 Subject: Reference Media if DiskId is specified. --- src/WixToolset.Core/Bind/FileFacade.cs | 2 +- src/WixToolset.Core/Compiler.cs | 10 +++++----- .../TestData/MultiFileCompressed/PackageComponents.wxs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Bind/FileFacade.cs b/src/WixToolset.Core/Bind/FileFacade.cs index f0ce14ca..511f4aab 100644 --- a/src/WixToolset.Core/Bind/FileFacade.cs +++ b/src/WixToolset.Core/Bind/FileFacade.cs @@ -56,7 +56,7 @@ namespace WixToolset.Core.Bind public int DiskId { - get => this.FileRow == null ? this.FileTuple.DiskId ?? 0 : this.FileRow.DiskId; + get => this.FileRow == null ? this.FileTuple.DiskId ?? 1 : this.FileRow.DiskId; set { if (this.FileRow == null) diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index e2a5721e..56f6322a 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -5728,11 +5728,6 @@ namespace WixToolset.Core id = this.Core.CreateIdentifier("fil", directoryId, name ?? shortName); } - if (!this.compilingModule && CompilerConstants.IntegerNotSet == diskId) - { - diskId = 1; // default to first Media - } - if (null != defaultVersion && null != companionFile) { this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DefaultVersion", "CompanionFile", companionFile)); @@ -5914,6 +5909,11 @@ namespace WixToolset.Core } } + if (CompilerConstants.IntegerNotSet != diskId) + { + this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); + } + // If this component does not have a companion file this file is a possible keypath. possibleKeyPath = null; if (null == companionFile) diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs index 1a040fa3..82797ebe 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs @@ -3,10 +3,10 @@ - + - + -- cgit v1.2.3-55-g6feb From 83be85cdfca822028df65558bb3ac7f22d4de228 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 1 Jul 2020 19:35:48 +1000 Subject: Add failing test for CustomAction cycle. --- .../CustomActionFixture.cs | 41 ++++++++++++++++++++++ .../TestData/CustomAction/CustomActionCycle.wxs | 19 ++++++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 61 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs new file mode 100644 index 00000000..814b5a26 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs @@ -0,0 +1,41 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class CustomActionFixture + { + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesCustomActionTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomAction", "CustomActionCycle.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + var warnings = result.Messages.Where(m => m.Level == MessageLevel.Warning).ToArray(); + Assert.False(result.ExitCode == 0 && warnings.Length == 0); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs new file mode 100644 index 00000000..56c07eb9 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index c86a691f..44ff5bca 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -33,6 +33,7 @@ + -- cgit v1.2.3-55-g6feb From 220bea1948c19132d0e5277021b967993293c5a3 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 8 Jul 2020 13:09:59 -0700 Subject: Tests for CopyFile element Validates wixtoolset/issues#5867 --- .../CopyFileFixture.cs | 48 ++++++++++++++++++++++ .../TestData/CopyFile/CopyFile.wxs | 15 +++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 3 files changed, 64 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs new file mode 100644 index 00000000..c6fa602b --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs @@ -0,0 +1,48 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class CopyFileFixture + { + [Fact] + public void CanBuildCopyFile() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CopyFile", "CopyFile.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + var copyFileSymbol = section.Symbols.OfType().Single(); + Assert.Equal("MoveText", copyFileSymbol.Id.Id); + Assert.True(copyFileSymbol.Delete); + Assert.Equal("OtherFolder", copyFileSymbol.DestFolder); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs new file mode 100644 index 00000000..66208806 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 44ff5bca..87ace0b9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -33,6 +33,7 @@ + -- cgit v1.2.3-55-g6feb From c37f29a156a84e27e6b38a7841e2ddcde015b071 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 17 Jul 2020 10:42:30 -0700 Subject: Fix parsing of bind variables with default values --- src/WixToolset.Core/Common.cs | 4 +-- .../BindVariablesFixture.cs | 38 ++++++++++++++++++++++ .../TestData/BindVariables/DefaultedVariable.wxs | 6 ++++ .../TestData/BindVariables/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 2 ++ 5 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs index 1a748a13..6efc7571 100644 --- a/src/WixToolset.Core/Common.cs +++ b/src/WixToolset.Core/Common.cs @@ -788,9 +788,9 @@ namespace WixToolset.Core string scope = null; string defaultValue = null; - var secondDot = value.IndexOf('.', firstDot + 1, closeParen - firstDot); var equalsDefaultValue = value.IndexOf('=', firstDot + 1, closeParen - firstDot); var end = equalsDefaultValue == -1 ? closeParen : equalsDefaultValue; + var secondDot = value.IndexOf('.', firstDot + 1, end - firstDot); if (secondDot == -1) { @@ -814,7 +814,7 @@ namespace WixToolset.Core if (equalsDefaultValue != -1 && equalsDefaultValue < closeParen) { - defaultValue = value.Substring(equalsDefaultValue + 1, end - equalsDefaultValue - 1); + defaultValue = value.Substring(equalsDefaultValue + 1, closeParen - equalsDefaultValue - 1); } parsedVariable = new ParsedWixVariable diff --git a/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs new file mode 100644 index 00000000..3e9c7aa4 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs @@ -0,0 +1,38 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class BindVariablesFixture + { + [Fact] + public void CanBuildWithDefaultValue() + { + var folder = TestData.Get(@"TestData\BindVariables"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DefaultedVariable.wxs"), + "-bf", + "-intermediateFolder", intermediateFolder, + "-bindpath", folder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs new file mode 100644 index 00000000..c3528a67 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt new file mode 100644 index 00000000..3b862323 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt @@ -0,0 +1 @@ +This is test.txt diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 87ace0b9..15078b8a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -23,6 +23,8 @@ + + -- cgit v1.2.3-55-g6feb From 6b21265e139513c1a242d8677b154fcc0e1dc7ef Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 17 Jul 2020 15:22:14 -0700 Subject: Ensure named bindpaths are not found in unnamed bindpaths Fixes wixtoolset/issues#6200 --- src/WixToolset.Core/Bind/FileResolver.cs | 62 ++++++++++++---------- .../BindVariablesFixture.cs | 32 ++++++++++- .../TestData/WixlibWithBinaries/data/alpha/foo.dll | 2 +- .../TestData/WixlibWithBinaries/data/mips/foo.dll | 2 +- .../WixlibWithBinaries/data/powerpc/foo.dll | 2 +- .../WixlibFixture.cs | 30 ----------- 6 files changed, 68 insertions(+), 62 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Bind/FileResolver.cs b/src/WixToolset.Core/Bind/FileResolver.cs index d11fcadc..ba71c6c9 100644 --- a/src/WixToolset.Core/Bind/FileResolver.cs +++ b/src/WixToolset.Core/Bind/FileResolver.cs @@ -115,13 +115,14 @@ namespace WixToolset.Core.Bind } else // not a rooted path so let's try applying all the different source resolution options. { - string bindName = null; + var bindName = String.Empty; var path = source; - string pathWithoutSourceDir = null; + var pathWithoutSourceDir = String.Empty; if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal)) { - int closeParen = source.IndexOf(')', BindPathOpenString.Length); + var closeParen = source.IndexOf(')', BindPathOpenString.Length); + if (-1 != closeParen) { bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length); @@ -138,43 +139,48 @@ namespace WixToolset.Core.Bind foreach (var bindPath in bindPaths) { - if (!String.IsNullOrEmpty(bindName) && !String.IsNullOrEmpty(bindPath.Name)) + if (String.IsNullOrEmpty(bindName)) { - if (String.Equals(bindName, bindPath.Name, StringComparison.OrdinalIgnoreCase) && String.IsNullOrEmpty(resolved)) + if (String.IsNullOrEmpty(bindPath.Name)) { - var filePath = Path.Combine(bindPath.Path, path); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) + if (!String.IsNullOrEmpty(pathWithoutSourceDir)) { - resolved = filePath; + var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); + + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } } - } - } - else - { - if (!String.IsNullOrEmpty(pathWithoutSourceDir)) - { - var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) + if (String.IsNullOrEmpty(resolved)) { - resolved = filePath; + var filePath = Path.Combine(bindPath.Path, path); + + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } } } + } + else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase)) + { + var filePath = Path.Combine(bindPath.Path, path); - if (String.IsNullOrEmpty(resolved)) + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) { - var filePath = Path.Combine(bindPath.Path, path); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) - { - resolved = filePath; - } + resolved = filePath; } } + + if (!String.IsNullOrEmpty(resolved)) + { + break; + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs index 3e9c7aa4..857b84cc 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs @@ -13,7 +13,7 @@ namespace WixToolsetTest.CoreIntegration [Fact] public void CanBuildWithDefaultValue() { - var folder = TestData.Get(@"TestData\BindVariables"); + var folder = TestData.Get(@"TestData", "BindVariables"); using (var fs = new DisposableFileSystem()) { @@ -34,5 +34,35 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); } } + + [Fact] + public void CannotBuildWixlibWithBinariesFromMissingNamedBindPaths() + { + var folder = TestData.Get(@"TestData", "WixlibWithBinaries"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-bf", + "-bindpath", Path.Combine(folder, "data"), + // Use names that aren't excluded in default .gitignores. + "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", + "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", + "-bindpath", $"{Path.Combine(folder, "data", "alpha")}", + "-bindpath", $"{Path.Combine(folder, "data", "powerpc")}", + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(103, result.ExitCode); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll index cd0db0e1..fd36c768 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll @@ -1 +1 @@ -This is test.txt. \ No newline at end of file +This is alpha\foo.dll. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll index cd0db0e1..292925c7 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll @@ -1 +1 @@ -This is test.txt. \ No newline at end of file +This is mips\foo.dll. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll index cd0db0e1..663e9d99 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll @@ -1 +1 @@ -This is test.txt. \ No newline at end of file +This is powerpc\foo.dll. diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs index a60169c7..6ae2c0b8 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs @@ -88,36 +88,6 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] - public void CantBuildWixlibWithBinariesFromMissingNamedBindPaths() - { - var folder = TestData.Get(@"TestData\WixlibWithBinaries"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-bf", - "-bindpath", Path.Combine(folder, "data"), - // Use names that aren't excluded in default .gitignores. - "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", - "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", - "-bindpath", $"{Path.Combine(folder, "data", "alpha")}", - "-bindpath", $"{Path.Combine(folder, "data", "powerpc")}", - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.InRange(result.ExitCode, 2, int.MaxValue); - } - } - [Fact] public void CanBuildSingleFileUsingWixlib() { -- cgit v1.2.3-55-g6feb From b62a7a0beb7ceb7987de28ec768c7814cadb83b9 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 21 Jul 2020 14:31:53 -0700 Subject: Support implicit standard directory reference and "3264" platform folders Completes wixtoolset/issues#5798 and wixtoolset/issues#5835 --- .../Bind/AddRequiredStandardDirectories.cs | 95 ++++++++++++++++++++++ .../Bind/BindDatabaseCommand.cs | 9 +- .../Bind/BindSummaryInfoCommand.cs | 31 +++++++ .../Bind/CalculateComponentGuids.cs | 9 +- .../Bind/SequenceActionsCommand.cs | 36 ++++---- src/WixToolset.Core/Compiler.cs | 3 +- .../ExtensibilityServices/PathResolver.cs | 51 +++++++++--- .../Link/ResolveReferencesCommand.cs | 25 +++--- src/WixToolset.Core/Link/SymbolWithSection.cs | 91 +++++++++++++++++++++ src/WixToolset.Core/Linker.cs | 22 +++-- .../DirectoryFixture.cs | 89 ++++++++++++++++++++ .../MsiQueryFixture.cs | 7 +- .../TestData/CustomAction/SimpleCustomAction.wxs | 15 ++++ .../TestData/CustomTable/CustomTable-Expected.wxs | 12 +-- .../TestData/Directory/Empty.wxs | 6 ++ .../ProductWithComponentGroupRef/Product.wxs | 6 +- .../WixToolsetTest.CoreIntegration.csproj | 2 + 17 files changed, 437 insertions(+), 72 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs create mode 100644 src/WixToolset.Core/Link/SymbolWithSection.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs new file mode 100644 index 00000000..4597639b --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs @@ -0,0 +1,95 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + + /// + /// Add referenced standard directory symbols, if not already present. + /// + internal class AddRequiredStandardDirectories + { + internal AddRequiredStandardDirectories(IntermediateSection section, Platform platform) + { + this.Section = section; + this.Platform = platform; + } + + private IntermediateSection Section { get; } + + private Platform Platform { get; } + + public void Execute() + { + var directories = this.Section.Symbols.OfType().ToList(); + var directoriesById = directories.ToDictionary(d => d.Id.Id); + + foreach (var directory in directories) + { + var parentDirectoryId = directory.ParentDirectoryRef; + + if (String.IsNullOrEmpty(parentDirectoryId)) + { + if (directory.Id.Id != "TARGETDIR") + { + directory.ParentDirectoryRef = "TARGETDIR"; + } + } + else + { + this.EnsureStandardDirectoryAdded(directoriesById, parentDirectoryId, directory.SourceLineNumbers); + } + } + + if (!directoriesById.ContainsKey("TARGETDIR") && WindowsInstallerStandard.TryGetStandardDirectory("TARGETDIR", out var targetDir)) + { + directoriesById.Add(targetDir.Id.Id, targetDir); + this.Section.AddSymbol(targetDir); + } + } + + private void EnsureStandardDirectoryAdded(Dictionary directoriesById, string directoryId, SourceLineNumber sourceLineNumbers) + { + if (!directoriesById.ContainsKey(directoryId) && WindowsInstallerStandard.TryGetStandardDirectory(directoryId, out var standardDirectory)) + { + var parentDirectoryId = this.GetStandardDirectoryParent(directoryId); + + var directory = new DirectorySymbol(sourceLineNumbers, standardDirectory.Id) + { + Name = standardDirectory.Name, + ParentDirectoryRef = parentDirectoryId, + }; + + directoriesById.Add(directory.Id.Id, directory); + this.Section.AddSymbol(directory); + + if (!String.IsNullOrEmpty(parentDirectoryId)) + { + this.EnsureStandardDirectoryAdded(directoriesById, parentDirectoryId, sourceLineNumbers); + } + } + } + + private string GetStandardDirectoryParent(string directoryId) + { + switch (directoryId) + { + case "TARGETDIR": + return null; + + case "CommonFiles6432Folder": + case "ProgramFiles6432Folder": + case "System6432Folder": + return WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directoryId, this.Platform); + + default: + return "TARGETDIR"; + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 950fe1c1..93c617d9 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -133,6 +133,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind bool compressed; bool longNames; int installerVersion; + Platform platform; string modularizationSuffix; { var command = new BindSummaryInfoCommand(section); @@ -141,6 +142,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind compressed = command.Compressed; longNames = command.LongNames; installerVersion = command.InstallerVersion; + platform = command.Platform; modularizationSuffix = command.ModularizationSuffix; } @@ -193,6 +195,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind command.Execute(); } + { + var command = new AddRequiredStandardDirectories(section, platform); + command.Execute(); + } + { var command = new CreateSpecialPropertiesCommand(section); command.Execute(); @@ -332,7 +339,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Set generated component guids. { - var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, this.PathResolver, section); + var command = new CalculateComponentGuids(this.Messaging, this.BackendHelper, this.PathResolver, section, platform); command.Execute(); } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs index 82688edf..63691016 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs @@ -32,6 +32,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind public int InstallerVersion { get; private set; } + public Platform Platform { get; private set; } + /// /// Modularization guid, or null if the output is not a module. /// @@ -66,6 +68,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind summaryInformationSymbol.Value = Common.GetValidCodePage(codepage, false, false, summaryInformationSymbol.SourceLineNumbers).ToString(CultureInfo.InvariantCulture); } break; + case SummaryInformationType.PlatformAndLanguage: + this.Platform = GetPlatformFromSummaryInformation(summaryInformationSymbol.Value); + break; + case SummaryInformationType.PackageCode: // PID_REVNUMBER var packageCode = summaryInformationSymbol.Value; @@ -137,5 +143,30 @@ namespace WixToolset.Core.WindowsInstaller.Bind }); } } + + private static Platform GetPlatformFromSummaryInformation(string value) + { + var separatorIndex = value.IndexOf(';'); + var platformValue = separatorIndex > 0 ? value.Substring(0, separatorIndex) : value; + + switch (platformValue) + { + case "x64": + return Platform.X64; + + case "Arm": + return Platform.ARM; + + case "Arm64": + return Platform.ARM64; + + case "Intel64": + return Platform.IA64; + + case "Intel": + default: + return Platform.X86; + } + } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs index a1e3ac83..02336cae 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs @@ -16,12 +16,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class CalculateComponentGuids { - internal CalculateComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section) + internal CalculateComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform) { this.Messaging = messaging; this.BackendHelper = helper; this.PathResolver = pathResolver; this.Section = section; + this.Platform = platform; } private IMessaging Messaging { get; } @@ -32,6 +33,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IntermediateSection Section { get; } + private Platform Platform { get; } + public void Execute() { Dictionary registryKeyRows = null; @@ -42,7 +45,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Find components with generatable guids. foreach (var componentSymbol in this.Section.Symbols.OfType()) { - // Skip components that do not specify generate guid. + // Skip components that do not specify generate guid. if (componentSymbol.ComponentId != "*") { continue; @@ -135,7 +138,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (fileRow.Id.Id == componentSymbol.KeyPath) { // calculate the key file's canonical target path - string directoryPath = this.PathResolver.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentSymbol.DirectoryRef, true); + string directoryPath = this.PathResolver.GetCanonicalDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform); string fileName = Common.GetName(fileRow.Name, false, true).ToLowerInvariant(); path = Path.Combine(directoryPath, fileName); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs index 7f43da9a..93e25878 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs @@ -34,25 +34,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var requiredActionSymbols = new Dictionary(); - // Get the standard actions required based on symbols in the section. - var overridableActionSymbols = this.GetRequiredStandardActions(); - // Index all the action symbols and look for collisions. foreach (var actionSymbol in this.Section.Symbols.OfType()) { if (actionSymbol.Overridable) // overridable action { - if (overridableActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol)) + if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol)) { - this.Messaging.Write(ErrorMessages.OverridableActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - if (null != collidingActionSymbol.SourceLineNumbers) + if (collidingActionSymbol.Overridable) { - this.Messaging.Write(ErrorMessages.OverridableActionCollision2(collidingActionSymbol.SourceLineNumbers)); + this.Messaging.Write(ErrorMessages.OverridableActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + if (null != collidingActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(ErrorMessages.OverridableActionCollision2(collidingActionSymbol.SourceLineNumbers)); + } } } else { - overridableActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); + requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); } } else // unsequenced or sequenced action. @@ -71,7 +71,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol)) + if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol) && !collidingActionSymbol.Overridable) { this.Messaging.Write(ErrorMessages.ActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); if (null != collidingActionSymbol.SourceLineNumbers) @@ -81,13 +81,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else { - requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); + requiredActionSymbols[actionSymbol.Id.Id] = actionSymbol; } } } + // Get the standard actions required based on symbols in the section. + var requiredStandardActions = this.GetRequiredStandardActions(); + // Add the overridable action symbols that are not overridden to the required action symbols. - foreach (var actionSymbol in overridableActionSymbols.Values) + foreach (var actionSymbol in requiredStandardActions.Values) { if (!requiredActionSymbols.ContainsKey(actionSymbol.Id.Id)) { @@ -557,17 +560,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind return set; } - private IEnumerable GetActions(SequenceTable sequence, string[] actionNames) - { - foreach (var action in WindowsInstallerStandard.StandardActions()) - { - if (action.SequenceTable == sequence && actionNames.Contains(action.Action)) - { - yield return action; - } - } - } - /// /// Sequence an action before or after a standard action. /// diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index c504e96f..c641bceb 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -4279,8 +4279,7 @@ namespace WixToolset.Core this.Core.AddInlineDirectoryId(inlineSyntax, id.Id); } } - - if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !("SourceDir".Equals(name, StringComparison.Ordinal) && shortName == null && shortSourceName == null && sourceName == null)) + else if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !("SourceDir".Equals(name, StringComparison.Ordinal) && shortName == null && shortSourceName == null && sourceName == null)) { this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name)); } diff --git a/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs b/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs index 15cd4fc9..72be2bcb 100644 --- a/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs +++ b/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs @@ -12,7 +12,7 @@ namespace WixToolset.Core.ExtensibilityServices internal class PathResolver : IPathResolver { - public string GetDirectoryPath(Dictionary directories, Dictionary componentIdGenSeeds, string directory, bool canonicalize) + public string GetCanonicalDirectoryPath(Dictionary directories, Dictionary componentIdGenSeeds, string directory, Platform platform) { if (!directories.TryGetValue(directory, out var resolvedDirectory)) { @@ -25,19 +25,13 @@ namespace WixToolset.Core.ExtensibilityServices { resolvedDirectory.Path = componentIdGenSeeds[directory]; } - else if (canonicalize && WindowsInstallerStandard.IsStandardDirectory(directory)) + else if (WindowsInstallerStandard.IsStandardDirectory(directory)) { - // when canonicalization is on, standard directories are treated equally - resolvedDirectory.Path = directory; + resolvedDirectory.Path = WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directory, platform); } else { - string name = resolvedDirectory.Name; - - if (canonicalize) - { - name = name?.ToLowerInvariant(); - } + var name = resolvedDirectory.Name?.ToLowerInvariant(); if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) { @@ -45,7 +39,7 @@ namespace WixToolset.Core.ExtensibilityServices } else { - var parentPath = this.GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize); + var parentPath = this.GetCanonicalDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, platform); if (null != resolvedDirectory.Name) { @@ -62,6 +56,39 @@ namespace WixToolset.Core.ExtensibilityServices return resolvedDirectory.Path; } + public string GetDirectoryPath(Dictionary directories, string directory) + { + if (!directories.TryGetValue(directory, out var resolvedDirectory)) + { + throw new WixException(ErrorMessages.ExpectedDirectory(directory)); + } + + if (null == resolvedDirectory.Path) + { + var name = resolvedDirectory.Name; + + if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) + { + resolvedDirectory.Path = name; + } + else + { + var parentPath = this.GetDirectoryPath(directories, resolvedDirectory.DirectoryParent); + + if (null != resolvedDirectory.Name) + { + resolvedDirectory.Path = Path.Combine(parentPath, name); + } + else + { + resolvedDirectory.Path = parentPath; + } + } + } + + return resolvedDirectory.Path; + } + public string GetFileSourcePath(Dictionary directories, string directoryId, string fileName, bool compressed, bool useLongName) { var fileSourcePath = Common.GetName(fileName, true, useLongName); @@ -75,7 +102,7 @@ namespace WixToolset.Core.ExtensibilityServices { // Get the relative path of where we want the file to be layed out as specified // in the Directory table. - var directoryPath = this.GetDirectoryPath(directories, null, directoryId, false); + var directoryPath = this.GetDirectoryPath(directories, directoryId); fileSourcePath = Path.Combine(directoryPath, fileSourcePath); } diff --git a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs index d2be0699..90b61e8b 100644 --- a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs +++ b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs @@ -72,27 +72,22 @@ namespace WixToolset.Core.Link continue; } - if (!this.symbolsWithSections.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbolWithSection)) - { - this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName)); - } - else // see if the symbol (and any of its duplicates) are appropriately accessible. + // See if the symbol (and any of its duplicates) are appropriately accessible. + if (this.symbolsWithSections.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbolWithSection)) { var accessible = this.DetermineAccessibleSymbols(section, symbolWithSection); - if (!accessible.Any()) - { - this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbolWithSection.Access)); - } - else if (1 == accessible.Count) + if (accessible.Count == 1) { var accessibleSymbol = accessible[0]; - this.referencedSymbols.Add(accessibleSymbol); - - if (null != accessibleSymbol.Section) + if (this.referencedSymbols.Add(accessibleSymbol) && null != accessibleSymbol.Section) { this.RecursivelyResolveReferences(accessibleSymbol.Section); } } + else if (accessible.Count == 0) + { + this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbolWithSection.Access)); + } else // display errors for the duplicate symbols. { var accessibleSymbol = accessible[0]; @@ -113,6 +108,10 @@ namespace WixToolset.Core.Link } } } + else + { + this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName)); + } } } diff --git a/src/WixToolset.Core/Link/SymbolWithSection.cs b/src/WixToolset.Core/Link/SymbolWithSection.cs new file mode 100644 index 00000000..c8934d0f --- /dev/null +++ b/src/WixToolset.Core/Link/SymbolWithSection.cs @@ -0,0 +1,91 @@ +// 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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + + /// + /// Symbol with section representing a single unique symbol. + /// + internal class SymbolWithSection + { + private HashSet possibleConflicts; + private HashSet redundants; + + /// + /// Creates a symbol for a symbol. + /// + /// Symbol for the symbol + public SymbolWithSection(IntermediateSection section, IntermediateSymbol symbol) + { + this.Symbol = symbol; + this.Section = section; + this.Name = String.Concat(this.Symbol.Definition.Name, ":", this.Symbol.Id.Id); + } + + /// + /// Gets the accessibility of the symbol which is a direct reflection of the accessibility of the row's accessibility. + /// + /// Accessbility of the symbol. + public AccessModifier Access => this.Symbol.Id.Access; + + /// + /// Gets the name of the symbol. + /// + /// Name of the symbol. + public string Name { get; } + + /// + /// Gets the symbol for this symbol. + /// + /// Symbol for this symbol. + public IntermediateSymbol Symbol { get; } + + /// + /// Gets the section for the symbol. + /// + /// Section for the symbol. + public IntermediateSection Section { get; } + + /// + /// Gets any duplicates of this symbol with sections that are possible conflicts. + /// + public IEnumerable PossiblyConflicts => this.possibleConflicts ?? Enumerable.Empty(); + + /// + /// Gets any duplicates of this symbol with sections that are redundant. + /// + public IEnumerable Redundants => this.redundants ?? Enumerable.Empty(); + + /// + /// Adds a duplicate symbol with sections that is a possible conflict. + /// + /// Symbol with section that is a possible conflict of this symbol. + public void AddPossibleConflict(SymbolWithSection symbolWithSection) + { + if (null == this.possibleConflicts) + { + this.possibleConflicts = new HashSet(); + } + + this.possibleConflicts.Add(symbolWithSection); + } + + /// + /// Adds a duplicate symbol that is redundant. + /// + /// Symbol with section that is redundant of this symbol. + public void AddRedundant(SymbolWithSection symbolWithSection) + { + if (null == this.redundants) + { + this.redundants = new HashSet(); + } + + this.redundants.Add(symbolWithSection); + } + } +} diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index cdefa5c7..1cfd085d 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -149,13 +149,12 @@ namespace WixToolset.Core throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString())); } - // Add the missing standard action symbols. - this.LoadStandardActions(find.EntrySection, find.SymbolsByName); + // Add the missing standard action and directory symbols. + this.LoadStandardSymbols(find.SymbolsByName); // Resolve the symbol references to find the set of sections we care about for linking. // Of course, we start with the entry section (that's how it got its name after all). var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName); - resolve.Execute(); if (this.Messaging.EncounteredError) @@ -732,14 +731,14 @@ namespace WixToolset.Core #endif /// - /// Load the standard action symbols. + /// Load the standard action and directory symbols. /// /// Collection of symbols. - private void LoadStandardActions(IntermediateSection section, IDictionary symbolsByName) + private void LoadStandardSymbols(IDictionary symbolsByName) { foreach (var actionSymbol in WindowsInstallerStandard.StandardActions()) { - var symbolWithSection = new SymbolWithSection(section, actionSymbol); + var symbolWithSection = new SymbolWithSection(null, actionSymbol); // If the action's symbol has not already been defined (i.e. overriden by the user), add it now. if (!symbolsByName.ContainsKey(symbolWithSection.Name)) @@ -747,6 +746,17 @@ namespace WixToolset.Core symbolsByName.Add(symbolWithSection.Name, symbolWithSection); } } + + foreach (var directorySymbol in WindowsInstallerStandard.StandardDirectories()) + { + var symbolWithSection = new SymbolWithSection(null, directorySymbol); + + // If the directory's symbol has not already been defined (i.e. overriden by the user), add it now. + if (!symbolsByName.ContainsKey(symbolWithSection.Name)) + { + symbolsByName.Add(symbolWithSection.Name, symbolWithSection); + } + } } /// diff --git a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs new file mode 100644 index 00000000..83f2f2bb --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs @@ -0,0 +1,89 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class DirectoryFixture + { + [Fact] + public void CanGet32bitProgramFiles6432Folder() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Directory", "Empty.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "INSTALLFOLDER", + "ProgramFiles6432Folder", + "ProgramFilesFolder", + "TARGETDIR" + }, dirSymbols.Select(d => d.Id.Id).ToArray()); + } + } + + [Fact] + public void CanGet64bitProgramFiles6432Folder() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "Directory", "Empty.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "INSTALLFOLDER", + "ProgramFiles6432Folder", + "ProgramFiles64Folder", + "TARGETDIR" + }, dirSymbols.Select(d => d.Id.Id).ToArray()); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 3ab218d1..4ff7f5f6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -365,14 +365,15 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Directory" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Directory:DUPLICATENAMEANDSHORTNAME\tINSTALLFOLDER\tduplicat", - "Directory:INSTALLFOLDER\tProgramFilesFolder\toekcr5lq|MsiPackage", + "Directory:INSTALLFOLDER\tProgramFiles6432Folder\t1egc1laj|MsiPackage", "Directory:NAMEANDSHORTNAME\tINSTALLFOLDER\tSHORTNAM|NameAndShortName", "Directory:NAMEANDSHORTSOURCENAME\tINSTALLFOLDER\tNAMEASSN|NameAndShortSourceName", "Directory:NAMEWITHSHORTVALUE\tINSTALLFOLDER\tSHORTVAL", - "Directory:ProgramFilesFolder\tTARGETDIR\t.", + "Directory:ProgramFiles6432Folder\tProgramFilesFolder\t.", + "Directory:ProgramFilesFolder\tTARGETDIR\tPFiles", "Directory:SHORTNAMEANDLONGSOURCENAME\tINSTALLFOLDER\tSHNALSNM:6ukthv5q|ShortNameAndLongSourceName", "Directory:SHORTNAMEONLY\tINSTALLFOLDER\tSHORTONL", "Directory:SOURCENAME\tINSTALLFOLDER\ts2s5bq-i|NameAndSourceName:dhnqygng|SourceNameWithName", diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs new file mode 100644 index 00000000..72d5e4a5 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs index 68386612..c55f4ed0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs @@ -15,11 +15,13 @@ - - - - - + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs new file mode 100644 index 00000000..50cf6850 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs index b2f22b7d..5b26091a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -11,10 +11,6 @@ - - - - - + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 15078b8a..601a66e7 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -36,6 +36,7 @@ + @@ -48,6 +49,7 @@ + -- cgit v1.2.3-55-g6feb From 581897da13bd8a20eea0c2079262caaa06cde676 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Sun, 26 Jul 2020 23:06:40 -0400 Subject: Fix Control symbol dehydration to MSI output. --- .../Bind/CreateOutputFromIRCommand.cs | 7 ++- src/WixToolset.Core/BindPath.cs | 2 + .../MsiQueryFixture.cs | 69 +++++++++++----------- .../PackageComponents.wxs | 1 + 4 files changed, 43 insertions(+), 36 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 663931b9..de3123ee 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -349,9 +349,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[5] = symbol.Width; row[6] = symbol.Height; row[7] = attributes; - row[8] = text; - row[9] = symbol.NextControlRef; - row[10] = symbol.Help; + row[8] = symbol.Property; + row[9] = text; + row[10] = symbol.NextControlRef; + row[11] = symbol.Help; } private void AddControlEventSymbol(ControlEventSymbol symbol) diff --git a/src/WixToolset.Core/BindPath.cs b/src/WixToolset.Core/BindPath.cs index 85aef97a..f70d5e36 100644 --- a/src/WixToolset.Core/BindPath.cs +++ b/src/WixToolset.Core/BindPath.cs @@ -2,11 +2,13 @@ namespace WixToolset.Core { + using System.Diagnostics; using WixToolset.Extensibility.Data; /// /// Bind path representation. /// + [DebuggerDisplay("Name={Name,nq} Path={Path,nq}")] internal class BindPath : IBindPath { public string Name { get; set; } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 4ff7f5f6..158687cf 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -41,7 +41,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "AppId" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "AppId:{D6040299-B15C-4C94-AE26-0C9B60D14C35}\t\t\t\t\t\t", }, results); @@ -74,7 +74,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "CompLocator" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "AppSearch:SAMPLECOMPFOUND\tSampleCompSearch", "CompLocator:SampleCompSearch\t{4D9A0D20-D0CC-40DE-B580-EAD38B985217}\t1", @@ -108,7 +108,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "AppSearch:SAMPLEDIRFOUND\tSampleDirSearch", "DrLocator:SampleDirSearch\t\tC:\\SampleDir\t", @@ -142,7 +142,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator", "IniLocator" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "AppSearch:SAMPLEFILEFOUND\tSampleFileSearch", "DrLocator:SampleFileSearch\tSampleIniFileSearch\t\t", @@ -177,7 +177,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", "RegLocator:SampleRegSearch\t2\tSampleReg\t\t2", @@ -213,7 +213,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Class" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Class:{3FAED4CC-C473-4B8A-BE8B-303871377A4A}\tLocalServer32\tClassComp\t\tFakeClass3FAE\t\t\tSampleIcon\t0\t\t\tProductFeature\t", }, results); @@ -246,7 +246,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Class", "ProgId", "Registry" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Class:{F12A6F69-117F-471F-AE73-F8E74218F498}\tLocalServer32\tProgIdComp\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tFakeClassF12A\t\t\t\t\t\t\tProductFeature\t", "ProgId:73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\t\t{F12A6F69-117F-471F-AE73-F8E74218F498}\tFakeClassF12A\t\t", @@ -283,14 +283,17 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CheckBox", "Control", "InstallUISequence" }); - Assert.Equal(new[] + var results = Query.QueryDatabase(msiPath, new[] { "CheckBox", "Control", "ControlCondition", "InstallUISequence" }); + WixAssert.CompareLineByLine(new[] { "CheckBox:WIXUI_EXITDIALOGOPTIONALCHECKBOX\t1", - "Control:FirstDialog\tHeader\tText\t0\t13\t90\t13\t3\tFirstDialogHeader\tTitle\t\t", - "Control:FirstDialog\tTitle\tText\t0\t0\t90\t13\t3\tFirstDialogTitle\tHeader\t\t", - "Control:SecondDialog\tOptionalCheckBox\tCheckBox\t0\t13\t100\t40\t2\t[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]\tTitle\t\t", - "Control:SecondDialog\tTitle\tText\t0\t0\t90\t13\t3\tSecondDialogTitle\tOptionalCheckBox\t\t", + "Control:FirstDialog\tHeader\tText\t0\t13\t90\t13\t3\t\tFirstDialogHeader\tTitle\t", + "Control:FirstDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tFirstDialogTitle\tHeader\t", + "Control:SecondDialog\tOptionalCheckBox\tCheckBox\t0\t13\t100\t40\t2\tWIXUI_EXITDIALOGOPTIONALCHECKBOX\t[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]\tTitle\tOptional checkbox|Check this box for fun", + "Control:SecondDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tSecondDialogTitle\tOptionalCheckBox\t", + "ControlCondition:FirstDialog\tHeader\tDisable\tInstalled", + "ControlCondition:FirstDialog\tHeader\tHide\tInstalled", + "ControlCondition:SecondDialog\tOptionalCheckBox\tShow\tWIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT AND NOT Installed", "InstallUISequence:CostFinalize\t\t1000", "InstallUISequence:CostInitialize\t\t800", "InstallUISequence:ExecuteAction\t\t1300", @@ -331,7 +334,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "CreateFolder" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "CreateFolder:INSTALLFOLDER\tNullKeypathComponent", }, results); @@ -409,7 +412,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Environment" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Environment:PATH\t=-*PATH\t[INSTALLFOLDER]; ;[~]\tWixEnvironmentTest", "Environment:WixEnvironmentTest1\t=-WixEnvTest1\t\tWixEnvironmentTest", @@ -478,7 +481,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Feature" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Feature:ChildFeature\tParentFeature\tChildFeatureTitle\t\t2\t1\t\t0", "Feature:ParentFeature\t\tParentFeatureTitle\t\t2\t1\t\t0", @@ -513,7 +516,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Font" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Font:test.txt\tFakeFont", }, results); @@ -546,7 +549,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Font" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Font:TrueTypeFontFile\t", }, results); @@ -579,7 +582,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "InstallExecuteSequence" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "InstallExecuteSequence:CostFinalize\t\t1000", "InstallExecuteSequence:CostInitialize\t\t800", @@ -630,7 +633,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "LockPermissions" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "LockPermissions:INSTALLFOLDER\tCreateFolder\t\tAdministrator\t0", }, results); @@ -664,7 +667,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "MsiAssembly", "MsiAssemblyName" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "MsiAssembly:test.txt\tProductFeature\ttest.dll.manifest\t\t1", "MsiAssemblyName:test.txt\tname\tMyApplication.app", @@ -699,7 +702,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty", "Shortcut" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue", "Shortcut:TheShortcut\tINSTALLFOLDER\td\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", @@ -733,7 +736,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "ReserveCost" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "ReserveCost:TestCost\tReserveCostComp\tINSTALLFOLDER\t100\t200", }, results); @@ -766,7 +769,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "ServiceInstall", "ServiceControl" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "ServiceControl:SampleService\tSampleService\t161\t\t1\ttest.txt", "ServiceInstall:SampleService\tSampleService\t\t16\t4\t0\t\t\t\t\t\ttest.txt\t", @@ -800,7 +803,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "TextStyle:FirstTextStyle\tArial\t2\t\t", }, results); @@ -834,7 +837,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "TextStyle:CustomFont\tTahoma\t8\t\t", }, results); @@ -866,7 +869,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "TypeLib" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "TypeLib:{765BE8EE-BD7F-491E-90D2-C5A972462B50}\t0\tTypeLibComp\t\t\t\tProductFeature\t", }, results); @@ -898,7 +901,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Upgrade:{01120000-00E0-0000-0000-0000000FF1CE}\t12.0.0\t13.0.0\t\t260\t\tBLAHBLAHBLAH", }, results); @@ -931,7 +934,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", @@ -940,12 +943,12 @@ namespace WixToolsetTest.CoreIntegration var prefix = "Property:SecureCustomProperties\t"; var secureProperties = Query.QueryDatabase(msiPath, new[] { "Property" }).Where(p => p.StartsWith(prefix)).Single(); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "RELPRODFOUND", "WIX_DOWNGRADE_DETECTED", "WIX_UPGRADE_DETECTED", - }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p)); + }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p).ToArray()); } } @@ -983,13 +986,13 @@ namespace WixToolsetTest.CoreIntegration Assert.Null(data.Tables["File"]); var results = Query.QueryDatabase(msiPath, new[] { "File" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "File:filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent.243FB739_4D05_472F_9CFB_EF6B1017B6DE\ttest.txt\t17\t\t\t512\t0" }, results); var files = Query.GetCabinetFiles(cabPath); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" }, files.Select(f => f.Name).ToArray()); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs index c6deb864..10c8b2c3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs @@ -10,6 +10,7 @@ -- cgit v1.2.3-55-g6feb From d26157531381aba81d2cac15e424b7e5c738253a Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 2 Aug 2020 08:32:54 -0600 Subject: WIXFEAT:4763 Change "string" variable type to literal and add "formatted". --- .../Bind/SetVariableSearchFacade.cs | 19 ++++- .../Bundles/CreateBurnManifestCommand.cs | 19 ++++- src/WixToolset.Core/Compiler_Bundle.cs | 93 +++++++++++++--------- .../BadInputFixture.cs | 23 ++++++ .../TestData/BadInput/BundleVariable.wxs | 7 ++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 6 files changed, 122 insertions(+), 40 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs index fb6f72dd..e88f26ef 100644 --- a/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs +++ b/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs @@ -21,10 +21,25 @@ namespace WixToolset.Core.Burn base.WriteXml(writer); - if (this.SetVariableSymbol.Type != null) + if (this.SetVariableSymbol.Type != WixBundleVariableType.Unknown) { writer.WriteAttributeString("Value", this.SetVariableSymbol.Value); - writer.WriteAttributeString("Type", this.SetVariableSymbol.Type); + + switch (this.SetVariableSymbol.Type) + { + case WixBundleVariableType.Formatted: + writer.WriteAttributeString("Type", "formatted"); + break; + case WixBundleVariableType.Numeric: + writer.WriteAttributeString("Type", "numeric"); + break; + case WixBundleVariableType.String: + writer.WriteAttributeString("Type", "string"); + break; + case WixBundleVariableType.Version: + writer.WriteAttributeString("Type", "version"); + break; + } } writer.WriteEndElement(); diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 05b15ab6..6eafcdd9 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -131,10 +131,25 @@ namespace WixToolset.Core.Burn.Bundles { writer.WriteStartElement("Variable"); writer.WriteAttributeString("Id", variable.Id.Id); - if (null != variable.Type) + if (variable.Type != WixBundleVariableType.Unknown) { writer.WriteAttributeString("Value", variable.Value); - writer.WriteAttributeString("Type", variable.Type); + + switch (variable.Type) + { + case WixBundleVariableType.Formatted: + writer.WriteAttributeString("Type", "formatted"); + break; + case WixBundleVariableType.Numeric: + writer.WriteAttributeString("Type", "numeric"); + break; + case WixBundleVariableType.String: + writer.WriteAttributeString("Type", "string"); + break; + case WixBundleVariableType.Version: + writer.WriteAttributeString("Type", "version"); + break; + } } writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no"); writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no"); diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index d73db84d..33467dda 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -2141,7 +2141,7 @@ namespace WixToolset.Core case "": break; default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no")); + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "always", "yes", "no")); break; } break; @@ -3043,7 +3043,7 @@ namespace WixToolset.Core string condition = null; string after = null; string value = null; - string type = null; + string typeValue = null; foreach (var attrib in node.Attributes()) { @@ -3067,7 +3067,7 @@ namespace WixToolset.Core value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Type": - type = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: @@ -3081,13 +3081,13 @@ namespace WixToolset.Core } } - type = this.ValidateVariableTypeWithValue(sourceLineNumbers, type, value); + var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); this.Core.ParseForExtensionElements(node); if (id == null) { - id = this.Core.CreateIdentifier("sbv", variable, condition, after, value, type); + id = this.Core.CreateIdentifier("sbv", variable, condition, after, value, type.ToString()); } this.Core.CreateWixSearchSymbol(sourceLineNumbers, node.Name.LocalName, id, variable, condition, after); @@ -3113,7 +3113,7 @@ namespace WixToolset.Core string name = null; var persisted = false; string value = null; - string type = null; + string typeValue = null; foreach (var attrib in node.Attributes()) { @@ -3140,7 +3140,7 @@ namespace WixToolset.Core value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); break; case "Type": - type = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); @@ -3162,7 +3162,7 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix")); } - type = this.ValidateVariableTypeWithValue(sourceLineNumbers, type, value); + var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); this.Core.ParseForExtensionElements(node); @@ -3178,46 +3178,67 @@ namespace WixToolset.Core } } - private string ValidateVariableTypeWithValue(SourceLineNumber sourceLineNumbers, string type, string value) + private WixBundleVariableType ValidateVariableTypeWithValue(SourceLineNumber sourceLineNumbers, XElement node, string typeValue, string value) { - var newType = type; - if (newType == null && value != null) + WixBundleVariableType type; + switch (typeValue) + { + case "formatted": + type = WixBundleVariableType.Formatted; + break; + case "numeric": + type = WixBundleVariableType.Numeric; + break; + case "string": + type = WixBundleVariableType.String; + break; + case "version": + type = WixBundleVariableType.Version; + break; + case null: + type = WixBundleVariableType.Unknown; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "formatted", "numeric", "string", "version")); + return WixBundleVariableType.Unknown; + } + + if (type != WixBundleVariableType.Unknown) { - // Infer the type from the current value... - if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) + if (value == null) { - // Version constructor does not support simple "v#" syntax so check to see if the value is - // non-negative real quick. - if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _)) - { - newType = "version"; - } - else if (Version.TryParse(value.Substring(1), out var _)) - { - newType = "version"; - } + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type")); } - // Not a version, check for numeric. - if (newType == null) + return type; + } + else if (value == null) + { + return type; + } + + // Infer the type from the current value... + if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) + { + // Version constructor does not support simple "v#" syntax so check to see if the value is + // non-negative real quick. + if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _)) { - if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _)) - { - newType = "numeric"; - } - else - { - newType = "string"; - } + return WixBundleVariableType.Version; + } + else if (Version.TryParse(value.Substring(1), out var _)) + { + return WixBundleVariableType.Version; } } - if (value == null && newType != null) + // Not a version, check for numeric. + if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _)) { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type")); + return WixBundleVariableType.Numeric; } - return newType; + return WixBundleVariableType.String; } private class RemotePayload diff --git a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs index d6c7b091..7a630a36 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs @@ -32,5 +32,28 @@ namespace WixToolsetTest.CoreIntegration Assert.InRange(result.ExitCode, 2, Int32.MaxValue); } } + + [Fact] + public void BundleVariableWithBadTypeIsRejected() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleVariable.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(21, result.ExitCode); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs new file mode 100644 index 00000000..293b379f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs @@ -0,0 +1,7 @@ + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 601a66e7..f4aab391 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -22,6 +22,7 @@ + -- cgit v1.2.3-55-g6feb From c237bb3bb00d36c50271a70baac68f49890e35e1 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 24 Aug 2020 17:25:00 -0400 Subject: Update decompiler to use XDocument rather than generated classes. - Use CompareXml for diffing. - Change CustomAction/@ScriptFile to @ScriptSourceFile. --- .../Decompile/Decompiler.cs | 8297 ++++++++------------ .../Decompile/DecompilerCore.cs | 110 - .../Decompile/Names.cs | 158 + src/WixToolset.Core.WindowsInstaller/Melter.cs | 1 - .../CommandLine/DecompileCommand.cs | 2 +- src/WixToolset.Core/Compiler.cs | 6 +- .../CustomTableFixture.cs | 15 +- .../DecompileFixture.cs | 140 +- .../TestData/CustomTable/CustomTable-Expected.wxs | 8 +- 9 files changed, 3467 insertions(+), 5270 deletions(-) delete mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/DecompilerCore.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index 72985c1c..bc9e6de3 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -3,9 +3,7 @@ namespace WixToolset.Core.WindowsInstaller { using System; - using System.Collections; using System.Collections.Generic; - using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Linq; @@ -13,13 +11,13 @@ namespace WixToolset.Core.WindowsInstaller using System.Text.RegularExpressions; using System.Xml.Linq; using WixToolset.Core; + using WixToolset.Core.WindowsInstaller.Decompile; using WixToolset.Data; using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller; using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility; using WixToolset.Extensibility.Services; - using Wix = WixToolset.Data.Serialize; /// /// Decompiles an msi database into WiX source. @@ -42,14 +40,7 @@ namespace WixToolset.Core.WindowsInstaller private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; - - private bool compressed; - private bool shortNames; - private DecompilerCore core; - private string modularizationGuid; - private readonly Hashtable patchTargetFiles; - private readonly Hashtable sequenceElements; - private readonly TableDefinitionCollection tableDefinitions; + private XElement uiElement; /// /// Creates a new decompiler object with a default set of table definitions. @@ -58,7 +49,7 @@ namespace WixToolset.Core.WindowsInstaller { this.Messaging = messaging; this.Extensions = extensions; - this.BaseSourcePath = String.IsNullOrEmpty(baseSourcePath) ? "SourceDir" : baseSourcePath; + this.BaseSourcePath = baseSourcePath ?? "SourceDir"; this.SuppressCustomTables = suppressCustomTables; this.SuppressDroppingEmptyTables = suppressDroppingEmptyTables; this.SuppressUI = suppressUI; @@ -67,9 +58,7 @@ namespace WixToolset.Core.WindowsInstaller this.ExtensionsByTableName = new Dictionary(); this.StandardActions = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id); - this.patchTargetFiles = new Hashtable(); - this.sequenceElements = new Hashtable(); - this.tableDefinitions = new TableDefinitionCollection(); + this.TableDefinitions = new TableDefinitionCollection(); } private IMessaging Messaging { get; } @@ -94,6 +83,37 @@ namespace WixToolset.Core.WindowsInstaller private Dictionary StandardActions { get; } + private bool Compressed { get; set; } + + private XElement RootElement { get; set; } + + private TableDefinitionCollection TableDefinitions { get; } + + private bool ShortNames { get; set; } + + private string ModularizationGuid { get; set; } + + public XElement UIElement + { + get + { + if (null == this.uiElement) + { + this.uiElement = new XElement(Names.UIElement); + this.RootElement.Add(this.uiElement); + } + + return this.uiElement; + } + } + + public Dictionary Singletons { get; } = new Dictionary(); + + public Dictionary IndexedElements { get; } = new Dictionary(); + + public Dictionary PatchTargetFiles { get; } = new Dictionary(); + + /// /// Decompile the database file. /// @@ -109,18 +129,18 @@ namespace WixToolset.Core.WindowsInstaller this.OutputType = output.Type; // collect the table definitions from the output - this.tableDefinitions.Clear(); + this.TableDefinitions.Clear(); foreach (var table in output.Tables) { - this.tableDefinitions.Add(table.Definition); + this.TableDefinitions.Add(table.Definition); } // add any missing standard and wix-specific table definitions foreach (var tableDefinition in WindowsInstallerTableDefinitions.All) { - if (!this.tableDefinitions.Contains(tableDefinition.Name)) + if (!this.TableDefinitions.Contains(tableDefinition.Name)) { - this.tableDefinitions.Add(tableDefinition); + this.TableDefinitions.Add(tableDefinition); } } @@ -132,62 +152,46 @@ namespace WixToolset.Core.WindowsInstaller } #endif - var wixElement = new Wix.Wix(); - Wix.IParentElement rootElement; - switch (this.OutputType) { - case OutputType.Module: - rootElement = new Wix.Module(); - break; - case OutputType.PatchCreation: - rootElement = new Wix.PatchCreation(); - break; - case OutputType.Product: - rootElement = new Wix.Product(); - break; - default: - throw new InvalidOperationException("Unknown output type."); + case OutputType.Module: + this.RootElement = new XElement(Names.ModuleElement); + break; + case OutputType.PatchCreation: + this.RootElement = new XElement(Names.PatchCreationElement); + break; + case OutputType.Product: + this.RootElement = new XElement(Names.ProductElement); + break; + default: + throw new InvalidOperationException("Unknown output type."); } - wixElement.AddChild((Wix.ISchemaElement)rootElement); + + var xWix = new XElement(Names.WixElement, this.RootElement); // try to decompile the database file - try + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) { - this.core = new DecompilerCore(rootElement); - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - this.InitializeDecompile(output.Tables, output.Codepage); - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } + return null; + } - // decompile the tables - this.DecompileTables(output); + this.InitializeDecompile(output.Tables, output.Codepage); - // finalize the decompiler and its extensions - this.FinalizeDecompile(output.Tables); - } - finally + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) { - this.core = null; + return null; } - var document = new XDocument(); - using (var writer = document.CreateWriter()) - { - wixElement.OutputXml(writer); - } + // decompile the tables + this.DecompileTables(output); + + // finalize the decompiler and its extensions + this.FinalizeDecompile(output.Tables); // return the XML document only if decompilation completed successfully + var document = new XDocument(xWix); return this.Messaging.EncounteredError ? null : document; } @@ -211,51 +215,157 @@ namespace WixToolset.Core.WindowsInstaller } #endif + /// + /// Gets the element corresponding to the row it came from. + /// + /// The row corresponding to the element. + /// The indexed element. + public XElement GetIndexedElement(WixToolset.Data.WindowsInstaller.Row row) => this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + + /// + /// Gets the element corresponding to the primary key of the given table. + /// + /// The table corresponding to the element. + /// The primary key corresponding to the element. + /// The indexed element. + public XElement GetIndexedElement(string table, params string[] primaryKey) => this.IndexedElements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))]; + + /// + /// Gets the element corresponding to the primary key of the given table. + /// + /// The table corresponding to the element. + /// The primary key corresponding to the element. + /// The indexed element. + public bool TryGetIndexedElement(WixToolset.Data.WindowsInstaller.Row row, out XElement xElement) => this.TryGetIndexedElement(row.TableDefinition.Name, out xElement, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + + /// + /// Gets the element corresponding to the primary key of the given table. + /// + /// The table corresponding to the element. + /// The primary key corresponding to the element. + /// The indexed element. + public bool TryGetIndexedElement(string table, out XElement xElement, params string[] primaryKey) => this.IndexedElements.TryGetValue(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), out xElement); + + /// + /// Index an element by its corresponding row. + /// + /// The row corresponding to the element. + /// The element to index. + public void IndexElement(WixToolset.Data.WindowsInstaller.Row row, XElement element) + { + this.IndexedElements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element); + } + + /// + /// Index an element by its corresponding row. + /// + /// The row corresponding to the element. + /// The element to index. + public void IndexElement(XElement element, string table, params string[] primaryKey) + { + this.IndexedElements.Add(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), element); + } + + private Dictionary> IndexTableOneToMany(IEnumerable rows, int column = 0) + { + return rows + .ToLookup(row => row.FieldAsString(column), row => this.GetIndexedElement(row)) + .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); + } + + private Dictionary> IndexTableOneToMany(TableIndexedCollection tables, string tableName, int column = 0) => this.IndexTableOneToMany(tables[tableName]?.Rows ?? Enumerable.Empty(), column); + + private Dictionary> IndexTableOneToMany(Table table, int column = 0) => this.IndexTableOneToMany(table?.Rows ?? Enumerable.Empty(), column); + + private void AddChildToParent(string parentName, XElement xChild, Row row, int column) + { + var key = row.FieldAsString(column); + if (this.TryGetIndexedElement(parentName, out var xParent, key)) + { + xParent.Add(xChild); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row.Fields[column].Column.Name, key, parentName)); + } + } + + private static XAttribute XAttributeIfNotNull(string attributeName, Row row, int column) => row.IsColumnNull(column) ? null : new XAttribute(attributeName, row.FieldAsString(column)); + + private static void SetAttributeIfNotNull(XElement xElement, string attributeName, string value) + { + if (!String.IsNullOrEmpty(value)) + { + xElement.SetAttributeValue(attributeName, value); + } + } + + private static void SetAttributeIfNotNull(XElement xElement, string attributeName, int? value) + { + if (value.HasValue) + { + xElement.SetAttributeValue(attributeName, value); + } + } + + /// + /// Convert an Int32 into a DateTime. + /// + /// The Int32 value. + /// The DateTime. + private static DateTime ConvertIntegerToDateTime(int value) + { + var date = value / 65536; + var time = value % 65536; + + return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2); + } + /// /// Set the common control attributes in a control element. /// /// The control attributes. /// The control element. - private static void SetControlAttributes(int attributes, Wix.Control control) + private static void SetControlAttributes(int attributes, XElement xControl) { if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesEnabled)) { - control.Disabled = Wix.YesNoType.yes; + xControl.SetAttributeValue("Disabled", "yes"); } if (WindowsInstallerConstants.MsidbControlAttributesIndirect == (attributes & WindowsInstallerConstants.MsidbControlAttributesIndirect)) { - control.Indirect = Wix.YesNoType.yes; + xControl.SetAttributeValue("Indirect", "yes"); } if (WindowsInstallerConstants.MsidbControlAttributesInteger == (attributes & WindowsInstallerConstants.MsidbControlAttributesInteger)) { - control.Integer = Wix.YesNoType.yes; + xControl.SetAttributeValue("Integer", "yes"); } if (WindowsInstallerConstants.MsidbControlAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbControlAttributesLeftScroll)) { - control.LeftScroll = Wix.YesNoType.yes; + xControl.SetAttributeValue("LeftScroll", "yes"); } if (WindowsInstallerConstants.MsidbControlAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbControlAttributesRightAligned)) { - control.RightAligned = Wix.YesNoType.yes; + xControl.SetAttributeValue("RightAligned", "yes"); } if (WindowsInstallerConstants.MsidbControlAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbControlAttributesRTLRO)) { - control.RightToLeft = Wix.YesNoType.yes; + xControl.SetAttributeValue("RightToLeft", "yes"); } if (WindowsInstallerConstants.MsidbControlAttributesSunken == (attributes & WindowsInstallerConstants.MsidbControlAttributesSunken)) { - control.Sunken = Wix.YesNoType.yes; + xControl.SetAttributeValue("Sunken", "yes"); } if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesVisible)) { - control.Hidden = Wix.YesNoType.yes; + xControl.SetAttributeValue("Hidden", "yes"); } } @@ -265,137 +375,93 @@ namespace WixToolset.Core.WindowsInstaller /// The action from which the element should be created. private void CreateActionElement(WixActionSymbol actionSymbol) { - Wix.ISchemaElement actionElement = null; + XElement xAction; - if (null != this.core.GetIndexedElement("CustomAction", actionSymbol.Action)) // custom action + if (this.TryGetIndexedElement("CustomAction", out var _, actionSymbol.Action)) // custom action { - var custom = new Wix.Custom(); - - custom.Action = actionSymbol.Action; - - if (null != actionSymbol.Condition) - { - custom.Content = actionSymbol.Condition; - } + xAction = new XElement(Names.CustomElement, + new XAttribute("Action", actionSymbol.Action), + String.IsNullOrEmpty(actionSymbol.Condition) ? null : new XAttribute("Condition", actionSymbol.Condition)); switch (actionSymbol.Sequence) { - case (-4): - custom.OnExit = Wix.ExitType.suspend; - break; - case (-3): - custom.OnExit = Wix.ExitType.error; - break; - case (-2): - custom.OnExit = Wix.ExitType.cancel; - break; - case (-1): - custom.OnExit = Wix.ExitType.success; - break; - default: - if (null != actionSymbol.Before) - { - custom.Before = actionSymbol.Before; - } - else if (null != actionSymbol.After) - { - custom.After = actionSymbol.After; - } - else if (actionSymbol.Sequence.HasValue) - { - custom.Sequence = actionSymbol.Sequence.Value; - } - break; + case (-4): + xAction.SetAttributeValue("OnExit", "suspend"); + break; + case (-3): + xAction.SetAttributeValue("OnExit", "error"); + break; + case (-2): + xAction.SetAttributeValue("OnExit", "cancel"); + break; + case (-1): + xAction.SetAttributeValue("OnExit", "success"); + break; + default: + if (null != actionSymbol.Before) + { + xAction.SetAttributeValue("Before", actionSymbol.Before); + } + else if (null != actionSymbol.After) + { + xAction.SetAttributeValue("After", actionSymbol.After); + } + else if (actionSymbol.Sequence.HasValue) + { + xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); + } + break; } - - actionElement = custom; } - else if (null != this.core.GetIndexedElement("Dialog", actionSymbol.Action)) // dialog + else if (this.TryGetIndexedElement("Dialog", out var _, actionSymbol.Action)) // dialog { - var show = new Wix.Show(); - - show.Dialog = actionSymbol.Action; - - if (null != actionSymbol.Condition) - { - show.Content = actionSymbol.Condition; - } + xAction = new XElement(Names.CustomElement, + new XAttribute("Dialog", actionSymbol.Action), + new XAttribute("Condition", actionSymbol.Condition)); switch (actionSymbol.Sequence) { - case (-4): - show.OnExit = Wix.ExitType.suspend; - break; - case (-3): - show.OnExit = Wix.ExitType.error; - break; - case (-2): - show.OnExit = Wix.ExitType.cancel; - break; - case (-1): - show.OnExit = Wix.ExitType.success; - break; - default: - if (null != actionSymbol.Before) - { - show.Before = actionSymbol.Before; - } - else if (null != actionSymbol.After) - { - show.After = actionSymbol.After; - } - else if (actionSymbol.Sequence.HasValue) - { - show.Sequence = actionSymbol.Sequence.Value; - } - break; + case (-4): + xAction.SetAttributeValue("OnExit", "suspend"); + break; + case (-3): + xAction.SetAttributeValue("OnExit", "error"); + break; + case (-2): + xAction.SetAttributeValue("OnExit", "cancel"); + break; + case (-1): + xAction.SetAttributeValue("OnExit", "success"); + break; + default: + SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); + SetAttributeIfNotNull(xAction, "After", actionSymbol.After); + SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); + break; } - - actionElement = show; } else // possibly a standard action without suggested sequence information { - actionElement = this.CreateStandardActionElement(actionSymbol); + xAction = this.CreateStandardActionElement(actionSymbol); } // add the action element to the appropriate sequence element - if (null != actionElement) + if (null != xAction) { var sequenceTable = actionSymbol.SequenceTable.ToString(); - var sequenceElement = (Wix.IParentElement)this.sequenceElements[sequenceTable]; - - if (null == sequenceElement) + if (!this.Singletons.TryGetValue(sequenceTable, out var xSequence)) { - switch (actionSymbol.SequenceTable) - { - case SequenceTable.AdminExecuteSequence: - sequenceElement = new Wix.AdminExecuteSequence(); - break; - case SequenceTable.AdminUISequence: - sequenceElement = new Wix.AdminUISequence(); - break; - case SequenceTable.AdvertiseExecuteSequence: - sequenceElement = new Wix.AdvertiseExecuteSequence(); - break; - case SequenceTable.InstallExecuteSequence: - sequenceElement = new Wix.InstallExecuteSequence(); - break; - case SequenceTable.InstallUISequence: - sequenceElement = new Wix.InstallUISequence(); - break; - default: - throw new InvalidOperationException("Unknown sequence table."); - } + xSequence = new XElement(Names.WxsNamespace + sequenceTable); - this.core.RootElement.AddChild((Wix.ISchemaElement)sequenceElement); - this.sequenceElements.Add(sequenceTable, sequenceElement); + this.RootElement.Add(xSequence); + this.Singletons.Add(sequenceTable, xSequence); } try { - sequenceElement.AddChild(actionElement); + xSequence.Add(xAction); } - catch (System.ArgumentException) // action/dialog is not valid for this sequence + catch (ArgumentException) // action/dialog is not valid for this sequence { this.Messaging.Write(WarningMessages.IllegalActionInSequence(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); } @@ -407,294 +473,129 @@ namespace WixToolset.Core.WindowsInstaller /// /// The action row from which the element should be created. /// The created element. - private Wix.ISchemaElement CreateStandardActionElement(WixActionSymbol actionSymbol) + private XElement CreateStandardActionElement(WixActionSymbol actionSymbol) { - Wix.ActionSequenceType actionElement = null; + XElement xStandardAction = null; switch (actionSymbol.Action) { - case "AllocateRegistrySpace": - actionElement = new Wix.AllocateRegistrySpace(); - break; - case "AppSearch": - this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var appSearchActionRow); - - if (null != actionSymbol.Before || null != actionSymbol.After || (null != appSearchActionRow && actionSymbol.Sequence != appSearchActionRow.Sequence)) - { - var appSearch = new Wix.AppSearch(); - - if (null != actionSymbol.Condition) - { - appSearch.Content = actionSymbol.Condition; - } - - if (null != actionSymbol.Before) - { - appSearch.Before = actionSymbol.Before; - } - else if (null != actionSymbol.After) - { - appSearch.After = actionSymbol.After; - } - else if (actionSymbol.Sequence.HasValue) - { - appSearch.Sequence = actionSymbol.Sequence.Value; - } - - return appSearch; - } - break; - case "BindImage": - actionElement = new Wix.BindImage(); - break; - case "CCPSearch": - var ccpSearch = new Wix.CCPSearch(); - Decompiler.SequenceRelativeAction(actionSymbol, ccpSearch); - return ccpSearch; - case "CostFinalize": - actionElement = new Wix.CostFinalize(); - break; - case "CostInitialize": - actionElement = new Wix.CostInitialize(); - break; - case "CreateFolders": - actionElement = new Wix.CreateFolders(); - break; - case "CreateShortcuts": - actionElement = new Wix.CreateShortcuts(); - break; - case "DeleteServices": - actionElement = new Wix.DeleteServices(); - break; - case "DisableRollback": - var disableRollback = new Wix.DisableRollback(); - Decompiler.SequenceRelativeAction(actionSymbol, disableRollback); - return disableRollback; - case "DuplicateFiles": - actionElement = new Wix.DuplicateFiles(); - break; - case "ExecuteAction": - actionElement = new Wix.ExecuteAction(); - break; - case "FileCost": - actionElement = new Wix.FileCost(); - break; - case "FindRelatedProducts": - var findRelatedProducts = new Wix.FindRelatedProducts(); - Decompiler.SequenceRelativeAction(actionSymbol, findRelatedProducts); - return findRelatedProducts; - case "ForceReboot": - var forceReboot = new Wix.ForceReboot(); - Decompiler.SequenceRelativeAction(actionSymbol, forceReboot); - return forceReboot; - case "InstallAdminPackage": - actionElement = new Wix.InstallAdminPackage(); - break; - case "InstallExecute": - var installExecute = new Wix.InstallExecute(); - Decompiler.SequenceRelativeAction(actionSymbol, installExecute); - return installExecute; - case "InstallExecuteAgain": - var installExecuteAgain = new Wix.InstallExecuteAgain(); - Decompiler.SequenceRelativeAction(actionSymbol, installExecuteAgain); - return installExecuteAgain; - case "InstallFiles": - actionElement = new Wix.InstallFiles(); - break; - case "InstallFinalize": - actionElement = new Wix.InstallFinalize(); - break; - case "InstallInitialize": - actionElement = new Wix.InstallInitialize(); - break; - case "InstallODBC": - actionElement = new Wix.InstallODBC(); - break; - case "InstallServices": - actionElement = new Wix.InstallServices(); - break; - case "InstallValidate": - actionElement = new Wix.InstallValidate(); - break; - case "IsolateComponents": - actionElement = new Wix.IsolateComponents(); - break; - case "LaunchConditions": - var launchConditions = new Wix.LaunchConditions(); - Decompiler.SequenceRelativeAction(actionSymbol, launchConditions); - return launchConditions; - case "MigrateFeatureStates": - actionElement = new Wix.MigrateFeatureStates(); - break; - case "MoveFiles": - actionElement = new Wix.MoveFiles(); - break; - case "MsiPublishAssemblies": - actionElement = new Wix.MsiPublishAssemblies(); - break; - case "MsiUnpublishAssemblies": - actionElement = new Wix.MsiUnpublishAssemblies(); - break; - case "PatchFiles": - actionElement = new Wix.PatchFiles(); - break; - case "ProcessComponents": - actionElement = new Wix.ProcessComponents(); - break; - case "PublishComponents": - actionElement = new Wix.PublishComponents(); - break; - case "PublishFeatures": - actionElement = new Wix.PublishFeatures(); - break; - case "PublishProduct": - actionElement = new Wix.PublishProduct(); - break; - case "RegisterClassInfo": - actionElement = new Wix.RegisterClassInfo(); - break; - case "RegisterComPlus": - actionElement = new Wix.RegisterComPlus(); - break; - case "RegisterExtensionInfo": - actionElement = new Wix.RegisterExtensionInfo(); - break; - case "RegisterFonts": - actionElement = new Wix.RegisterFonts(); - break; - case "RegisterMIMEInfo": - actionElement = new Wix.RegisterMIMEInfo(); - break; - case "RegisterProduct": - actionElement = new Wix.RegisterProduct(); - break; - case "RegisterProgIdInfo": - actionElement = new Wix.RegisterProgIdInfo(); - break; - case "RegisterTypeLibraries": - actionElement = new Wix.RegisterTypeLibraries(); - break; - case "RegisterUser": - actionElement = new Wix.RegisterUser(); - break; - case "RemoveDuplicateFiles": - actionElement = new Wix.RemoveDuplicateFiles(); - break; - case "RemoveEnvironmentStrings": - actionElement = new Wix.RemoveEnvironmentStrings(); - break; - case "RemoveExistingProducts": - var removeExistingProducts = new Wix.RemoveExistingProducts(); - Decompiler.SequenceRelativeAction(actionSymbol, removeExistingProducts); - return removeExistingProducts; - case "RemoveFiles": - actionElement = new Wix.RemoveFiles(); - break; - case "RemoveFolders": - actionElement = new Wix.RemoveFolders(); - break; - case "RemoveIniValues": - actionElement = new Wix.RemoveIniValues(); - break; - case "RemoveODBC": - actionElement = new Wix.RemoveODBC(); - break; - case "RemoveRegistryValues": - actionElement = new Wix.RemoveRegistryValues(); - break; - case "RemoveShortcuts": - actionElement = new Wix.RemoveShortcuts(); - break; - case "ResolveSource": - var resolveSource = new Wix.ResolveSource(); - Decompiler.SequenceRelativeAction(actionSymbol, resolveSource); - return resolveSource; - case "RMCCPSearch": - var rmccpSearch = new Wix.RMCCPSearch(); - Decompiler.SequenceRelativeAction(actionSymbol, rmccpSearch); - return rmccpSearch; - case "ScheduleReboot": - var scheduleReboot = new Wix.ScheduleReboot(); - Decompiler.SequenceRelativeAction(actionSymbol, scheduleReboot); - return scheduleReboot; - case "SelfRegModules": - actionElement = new Wix.SelfRegModules(); - break; - case "SelfUnregModules": - actionElement = new Wix.SelfUnregModules(); - break; - case "SetODBCFolders": - actionElement = new Wix.SetODBCFolders(); - break; - case "StartServices": - actionElement = new Wix.StartServices(); - break; - case "StopServices": - actionElement = new Wix.StopServices(); - break; - case "UnpublishComponents": - actionElement = new Wix.UnpublishComponents(); - break; - case "UnpublishFeatures": - actionElement = new Wix.UnpublishFeatures(); - break; - case "UnregisterClassInfo": - actionElement = new Wix.UnregisterClassInfo(); - break; - case "UnregisterComPlus": - actionElement = new Wix.UnregisterComPlus(); - break; - case "UnregisterExtensionInfo": - actionElement = new Wix.UnregisterExtensionInfo(); - break; - case "UnregisterFonts": - actionElement = new Wix.UnregisterFonts(); - break; - case "UnregisterMIMEInfo": - actionElement = new Wix.UnregisterMIMEInfo(); - break; - case "UnregisterProgIdInfo": - actionElement = new Wix.UnregisterProgIdInfo(); - break; - case "UnregisterTypeLibraries": - actionElement = new Wix.UnregisterTypeLibraries(); - break; - case "ValidateProductID": - actionElement = new Wix.ValidateProductID(); - break; - case "WriteEnvironmentStrings": - actionElement = new Wix.WriteEnvironmentStrings(); - break; - case "WriteIniValues": - actionElement = new Wix.WriteIniValues(); - break; - case "WriteRegistryValues": - actionElement = new Wix.WriteRegistryValues(); - break; - default: - this.Messaging.Write(WarningMessages.UnknownAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - return null; + case "AllocateRegistrySpace": + case "BindImage": + case "CostFinalize": + case "CostInitialize": + case "CreateFolders": + case "CreateShortcuts": + case "DeleteServices": + case "DuplicateFiles": + case "ExecuteAction": + case "FileCost": + case "InstallAdminPackage": + case "InstallFiles": + case "InstallFinalize": + case "InstallInitialize": + case "InstallODBC": + case "InstallServices": + case "InstallValidate": + case "IsolateComponents": + case "MigrateFeatureStates": + case "MoveFiles": + case "MsiPublishAssemblies": + case "MsiUnpublishAssemblies": + case "PatchFiles": + case "ProcessComponents": + case "PublishComponents": + case "PublishFeatures": + case "PublishProduct": + case "RegisterClassInfo": + case "RegisterComPlus": + case "RegisterExtensionInfo": + case "RegisterFonts": + case "RegisterMIMEInfo": + case "RegisterProduct": + case "RegisterProgIdInfo": + case "RegisterTypeLibraries": + case "RegisterUser": + case "RemoveDuplicateFiles": + case "RemoveEnvironmentStrings": + case "RemoveFiles": + case "RemoveFolders": + case "RemoveIniValues": + case "RemoveODBC": + case "RemoveRegistryValues": + case "RemoveShortcuts": + case "SelfRegModules": + case "SelfUnregModules": + case "SetODBCFolders": + case "StartServices": + case "StopServices": + case "UnpublishComponents": + case "UnpublishFeatures": + case "UnregisterClassInfo": + case "UnregisterComPlus": + case "UnregisterExtensionInfo": + case "UnregisterFonts": + case "UnregisterMIMEInfo": + case "UnregisterProgIdInfo": + case "UnregisterTypeLibraries": + case "ValidateProductID": + case "WriteEnvironmentStrings": + case "WriteIniValues": + case "WriteRegistryValues": + xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); + break; + + case "AppSearch": + this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var appSearchActionRow); + + if (null != actionSymbol.Before || null != actionSymbol.After || (null != appSearchActionRow && actionSymbol.Sequence != appSearchActionRow.Sequence)) + { + xStandardAction = new XElement(Names.AppSearchElement); + + SetAttributeIfNotNull(xStandardAction, "Condition", actionSymbol.Condition); + SetAttributeIfNotNull(xStandardAction, "Before", actionSymbol.Before); + SetAttributeIfNotNull(xStandardAction, "After", actionSymbol.After); + SetAttributeIfNotNull(xStandardAction, "Sequence", actionSymbol.Sequence); + + return xStandardAction; + } + break; + + case "CCPSearch": + case "DisableRollback": + case "FindRelatedProducts": + case "ForceReboot": + case "InstallExecute": + case "InstallExecuteAgain": + case "LaunchConditions": + case "RemoveExistingProducts": + case "ResolveSource": + case "RMCCPSearch": + case "ScheduleReboot": + xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); + Decompiler.SequenceRelativeAction(actionSymbol, xStandardAction); + return xStandardAction; + + default: + this.Messaging.Write(WarningMessages.UnknownAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + return null; } - if (actionElement != null) + if (xStandardAction != null) { - this.SequenceStandardAction(actionSymbol, actionElement); + this.SequenceStandardAction(actionSymbol, xStandardAction); } - return actionElement; + return xStandardAction; } /// - /// Applies the condition and sequence to a standard action element based on the action row data. + /// Applies the condition and sequence to a standard action element based on the action symbol data. /// /// Action data from the database. - /// Element to be sequenced. - private void SequenceStandardAction(WixActionSymbol actionSymbol, Wix.ActionSequenceType actionElement) + /// Element to be sequenced. + private void SequenceStandardAction(WixActionSymbol actionSymbol, XElement xAction) { - if (null != actionSymbol.Condition) - { - actionElement.Content = actionSymbol.Condition; - } + xAction.SetAttributeValue("Condition", actionSymbol.Condition); if ((null != actionSymbol.Before || null != actionSymbol.After) && 0 == actionSymbol.Sequence) { @@ -702,7 +603,7 @@ namespace WixToolset.Core.WindowsInstaller } else if (actionSymbol.Sequence.HasValue) { - actionElement.Sequence = actionSymbol.Sequence.Value; + xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); } } @@ -710,26 +611,13 @@ namespace WixToolset.Core.WindowsInstaller /// Applies the condition and relative sequence to an action element based on the action row data. /// /// Action data from the database. - /// Element to be sequenced. - private static void SequenceRelativeAction(WixActionSymbol actionSymbol, Wix.ActionModuleSequenceType actionElement) + /// Element to be sequenced. + private static void SequenceRelativeAction(WixActionSymbol actionSymbol, XElement xAction) { - if (null != actionSymbol.Condition) - { - actionElement.Content = actionSymbol.Condition; - } - - if (null != actionSymbol.Before) - { - actionElement.Before = actionSymbol.Before; - } - else if (null != actionSymbol.After) - { - actionElement.After = actionSymbol.After; - } - else if (actionSymbol.Sequence.HasValue) - { - actionElement.Sequence = actionSymbol.Sequence.Value; - } + SetAttributeIfNotNull(xAction, "Condition", actionSymbol.Condition); + SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); + SetAttributeIfNotNull(xAction, "After", actionSymbol.After); + SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); } /// @@ -737,24 +625,19 @@ namespace WixToolset.Core.WindowsInstaller /// /// The identifier of the property. /// The property element. - private Wix.Property EnsureProperty(string id) + private XElement EnsureProperty(string id) { - var property = (Wix.Property)this.core.GetIndexedElement("Property", id); + XElement xProperty; - if (null == property) + if (!this.TryGetIndexedElement("Property", out xProperty, id)) { - property = new Wix.Property(); - property.Id = id; + xProperty = new XElement(Names.PropertyElement, new XAttribute("Id", id)); - // create a dummy row for indexing - var row = this.tableDefinitions["Property"].CreateRow(null); - row[0] = id; - - this.core.RootElement.AddChild(property); - this.core.IndexElement(row, property); + this.RootElement.Add(xProperty); + this.IndexElement(xProperty, "Property", id); } - return property; + return xProperty; } /// @@ -809,51 +692,37 @@ namespace WixToolset.Core.WindowsInstaller var checkBoxTable = tables["CheckBox"]; var controlTable = tables["Control"]; - var checkBoxes = new Hashtable(); - var checkBoxProperties = new Hashtable(); - - // index the CheckBox table - if (null != checkBoxTable) - { - foreach (var row in checkBoxTable.Rows) - { - checkBoxes.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row); - checkBoxProperties.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), false); - } - } + var checkBoxes = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + var checkBoxProperties = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row => false); // enumerate through the Control table, adding CheckBox values where appropriate if (null != controlTable) { foreach (var row in controlTable.Rows) { - var control = (Wix.Control)this.core.GetIndexedElement(row); + var xControl = this.GetIndexedElement(row); - if ("CheckBox" == Convert.ToString(row[2]) && null != row[8]) + if ("CheckBox" == row.FieldAsString(2)) { - var checkBoxRow = (Row)checkBoxes[row[8]]; - - if (null == checkBoxRow) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", Convert.ToString(row[8]), "CheckBox")); - } - else + var property = row.FieldAsString(8); + if (!String.IsNullOrEmpty(property) && checkBoxes.TryGetValue(property, out var checkBoxRow)) { // if we've seen this property already, create a reference to it - if (Convert.ToBoolean(checkBoxProperties[row[8]])) + if (checkBoxProperties.TryGetValue(property, out var seen) && seen) { - control.CheckBoxPropertyRef = Convert.ToString(row[8]); + xControl.SetAttributeValue("CheckBoxPropertyRef", property); } else { - control.Property = Convert.ToString(row[8]); - checkBoxProperties[row[8]] = true; + xControl.SetAttributeValue("Property", property); + checkBoxProperties[property] = true; } - if (null != checkBoxRow[1]) - { - control.CheckBoxValue = Convert.ToString(checkBoxRow[1]); - } + xControl.SetAttributeValue("CheckBoxValue", checkBoxRow.FieldAsString(1)); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", row.FieldAsString(8), "CheckBox")); } } } @@ -879,60 +748,52 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in componentTable.Rows) { - var attributes = Convert.ToInt32(row[3]); + var attributes = row.FieldAsInteger(3); + var keyPath = row.FieldAsString(5); - if (null == row[5]) + if (String.IsNullOrEmpty(keyPath)) { - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0])); - - component.KeyPath = Wix.YesNoType.yes; + var xComponent = this.GetIndexedElement("Component", row.FieldAsString(0)); + xComponent.SetAttributeValue("KeyPath", "yes"); } else if (WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath == (attributes & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) { - object registryObject = this.core.GetIndexedElement("Registry", Convert.ToString(row[5])); - - if (null != registryObject) + if (this.TryGetIndexedElement("Registry", out var xRegistry, keyPath)) { - var registryValue = registryObject as Wix.RegistryValue; - - if (null != registryValue) + if (xRegistry.Name.LocalName == "RegistryValue") { - registryValue.KeyPath = Wix.YesNoType.yes; + xRegistry.SetAttributeValue("KeyPath", "yes"); } else { - this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", Convert.ToString(row[5]))); + this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", keyPath)); } } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "Registry")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "Registry")); } } else if (WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource == (attributes & WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource)) { - var odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement("ODBCDataSource", Convert.ToString(row[5])); - - if (null != odbcDataSource) + if (this.TryGetIndexedElement("ODBCDataSource", out var xOdbcDataSource, keyPath)) { - odbcDataSource.KeyPath = Wix.YesNoType.yes; + xOdbcDataSource.SetAttributeValue("KeyPath", "yes"); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "ODBCDataSource")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "ODBCDataSource")); } } else { - var file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[5])); - - if (null != file) + if (this.TryGetIndexedElement("File", out var xFile, keyPath)) { - file.KeyPath = Wix.YesNoType.yes; + xFile.SetAttributeValue("KeyPath", "yes"); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "File")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "File")); } } } @@ -943,12 +804,10 @@ namespace WixToolset.Core.WindowsInstaller { foreach (FileRow fileRow in fileTable.Rows) { - var component = (Wix.Component)this.core.GetIndexedElement("Component", fileRow.Component); - var file = (Wix.File)this.core.GetIndexedElement(fileRow); - - if (null != component) + if (this.TryGetIndexedElement("Component", out var xComponent, fileRow.Component) + && this.TryGetIndexedElement(fileRow, out var xFile)) { - component.AddChild(file); + xComponent.Add(xFile); } else { @@ -962,16 +821,14 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in odbcDataSourceTable.Rows) { - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - var odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement(row); - - if (null != component) + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(1)) + && this.TryGetIndexedElement(row, out var xOdbcDataSource)) { - component.AddChild(odbcDataSource); + xComponent.Add(xOdbcDataSource); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); } } } @@ -981,16 +838,14 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in registryTable.Rows) { - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[5])); - var registryElement = this.core.GetIndexedElement(row); - - if (null != component) + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(5)) + && this.TryGetIndexedElement(row, out var xRegistry)) { - component.AddChild(registryElement); + xComponent.Add(xRegistry); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[5]), "Component")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(5), "Component")); } } } @@ -1012,92 +867,82 @@ namespace WixToolset.Core.WindowsInstaller return; } - var controlTable = tables["Control"]; - var dialogTable = tables["Dialog"]; - - var addedControls = new Hashtable(); - var controlRows = new Hashtable(); + var addedControls = new HashSet(); - // index the rows in the control rows (because we need the Control_Next value) - if (null != controlTable) - { - foreach (var row in controlTable.Rows) - { - controlRows.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row); - } - } + var controlTable = tables["Control"]; + var controlRows = controlTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + var dialogTable = tables["Dialog"]; if (null != dialogTable) { - foreach (var row in dialogTable.Rows) + foreach (var dialogRow in dialogTable.Rows) { - var dialog = (Wix.Dialog)this.core.GetIndexedElement(row); - var dialogId = Convert.ToString(row[0]); + var xDialog = this.GetIndexedElement(dialogRow); + var dialogId = dialogRow.FieldAsString(0); - var control = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[7])); - if (null == control) + if (!this.TryGetIndexedElement("Control", out var xControl, dialogId, dialogRow.FieldAsString(7))) { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", Convert.ToString(row[7]), "Control")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", dialogRow.FieldAsString(7), "Control")); } // add tabbable controls - while (null != control) + while (null != xControl) { - var controlRow = (Row)controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, control.Id)]; + var controlId = xControl.Attribute("Id"); + var controlRow = controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, controlId)]; - control.TabSkip = Wix.YesNoType.no; - dialog.AddChild(control); - addedControls.Add(control, null); + xControl.SetAttributeValue("TabSkip", "no"); - if (null != controlRow[10]) + xDialog.Add(xControl); + addedControls.Add(xControl); + + var controlNext = controlRow.FieldAsString(10); + if (!String.IsNullOrEmpty(controlNext)) { - control = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(controlRow[10])); - if (null != control) + if (this.TryGetIndexedElement("Control", out xControl, dialogId, controlNext)) { // looped back to the first control in the dialog - if (addedControls.Contains(control)) + if (addedControls.Contains(xControl)) { - control = null; + xControl = null; } } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", Convert.ToString(controlRow[10]), "Control")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", controlNext, "Control")); } } else { - control = null; + xControl = null; } } // set default control - if (null != row[8]) + var controlDefault = dialogRow.FieldAsString(8); + if (!String.IsNullOrEmpty(controlDefault)) { - var defaultControl = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[8])); - - if (null != defaultControl) + if (this.TryGetIndexedElement("Control", out var xDefaultControl, dialogId, controlDefault)) { - defaultControl.Default = Wix.YesNoType.yes; + xDefaultControl.SetAttributeValue("Default", "yes"); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(row[8]), "Control")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(dialogRow[8]), "Control")); } } // set cancel control - if (null != row[9]) + var controlCancel = dialogRow.FieldAsString(8); + if (!String.IsNullOrEmpty(controlCancel)) { - var cancelControl = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[9])); - - if (null != cancelControl) + if (this.TryGetIndexedElement("Control", out var xCancelControl, dialogId, controlCancel)) { - cancelControl.Cancel = Wix.YesNoType.yes; + xCancelControl.SetAttributeValue("Cancel", "yes"); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(row[9]), "Control")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(dialogRow[9]), "Control")); } } } @@ -1106,21 +951,20 @@ namespace WixToolset.Core.WindowsInstaller // add the non-tabbable controls to the dialog if (null != controlTable) { - foreach (var row in controlTable.Rows) + foreach (var controlRow in controlTable.Rows) { - var control = (Wix.Control)this.core.GetIndexedElement(row); - var dialog = (Wix.Dialog)this.core.GetIndexedElement("Dialog", Convert.ToString(row[0])); - - if (null == dialog) + var dialogId = controlRow.FieldAsString(0); + if (!this.TryGetIndexedElement("Dialog", out var xDialog, dialogId)) { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Dialog")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Dialog")); continue; } - if (!addedControls.Contains(control)) + var xControl = this.GetIndexedElement(controlRow); + if (!addedControls.Contains(xControl)) { - control.TabSkip = Wix.YesNoType.yes; - dialog.AddChild(control); + xControl.SetAttributeValue("TabSkip", "yes"); + xDialog.Add(xControl); } } } @@ -1137,53 +981,53 @@ namespace WixToolset.Core.WindowsInstaller private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables) { var duplicateFileTable = tables["DuplicateFile"]; - var moveFileTable = tables["MoveFile"]; - if (null != duplicateFileTable) { foreach (var row in duplicateFileTable.Rows) { - var copyFile = (Wix.CopyFile)this.core.GetIndexedElement(row); - - if (null != row[4]) + var xCopyFile = this.GetIndexedElement(row); + var destination = row.FieldAsString(4); + if (!String.IsNullOrEmpty(destination)) { - if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[4]))) + if (this.TryGetIndexedElement("Directory", out var _, destination)) { - copyFile.DestinationDirectory = Convert.ToString(row[4]); + xCopyFile.SetAttributeValue("DestinationDirectory", destination); } else { - copyFile.DestinationProperty = Convert.ToString(row[4]); + xCopyFile.SetAttributeValue("DestinationProperty", destination); } } } } + var moveFileTable = tables["MoveFile"]; if (null != moveFileTable) { foreach (var row in moveFileTable.Rows) { - var copyFile = (Wix.CopyFile)this.core.GetIndexedElement(row); - - if (null != row[4]) + var xCopyFile = this.GetIndexedElement(row); + var source = row.FieldAsString(4); + if (!String.IsNullOrEmpty(source)) { - if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[4]))) + if (this.TryGetIndexedElement("Directory", out var _, source)) { - copyFile.SourceDirectory = Convert.ToString(row[4]); + xCopyFile.SetAttributeValue("SourceDirectory", source); } else { - copyFile.SourceProperty = Convert.ToString(row[4]); + xCopyFile.SetAttributeValue("SourceProperty", source); } } - if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[5]))) + var destination = row.FieldAsString(5); + if (this.TryGetIndexedElement("Directory", out var _, destination)) { - copyFile.DestinationDirectory = Convert.ToString(row[5]); + xCopyFile.SetAttributeValue("DestinationDirectory", destination); } else { - copyFile.DestinationProperty = Convert.ToString(row[5]); + xCopyFile.SetAttributeValue("DestinationProperty", destination); } } } @@ -1195,22 +1039,17 @@ namespace WixToolset.Core.WindowsInstaller /// The collection of all tables. private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables) { - var externalFilesTable = tables["ExternalFiles"]; var familyFileRangesTable = tables["FamilyFileRanges"]; - var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"]; - - var usedProtectRanges = new Hashtable(); - if (null != familyFileRangesTable) { foreach (var row in familyFileRangesTable.Rows) { - var protectRange = new Wix.ProtectRange(); + var xProtectRange = new XElement(Names.ProtectRangeElement); - if (null != row[2] && null != row[3]) + if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) { - var retainOffsets = (Convert.ToString(row[2])).Split(','); - var retainLengths = (Convert.ToString(row[3])).Split(','); + var retainOffsets = row.FieldAsString(2).Split(','); + var retainLengths = row.FieldAsString(3).Split(','); if (retainOffsets.Length == retainLengths.Length) { @@ -1218,20 +1057,20 @@ namespace WixToolset.Core.WindowsInstaller { if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal)) { - protectRange.Offset = Convert.ToInt32(retainOffsets[i].Substring(2), 16); + xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i].Substring(2), 16)); } else { - protectRange.Offset = Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture); + xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture)); } if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal)) { - protectRange.Length = Convert.ToInt32(retainLengths[i].Substring(2), 16); + xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i].Substring(2), 16)); } else { - protectRange.Length = Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture); + xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture)); } } } @@ -1240,79 +1079,59 @@ namespace WixToolset.Core.WindowsInstaller // TODO: warn } } - else if (null != row[2] || null != row[3]) + else if (!row.IsColumnNull(2) || !row.IsColumnNull(3)) { // TODO: warn about mismatch between columns } - this.core.IndexElement(row, protectRange); + this.IndexElement(row, xProtectRange); } } + var usedProtectRanges = new HashSet(); + var externalFilesTable = tables["ExternalFiles"]; if (null != externalFilesTable) { foreach (var row in externalFilesTable.Rows) { - var externalFile = (Wix.ExternalFile)this.core.GetIndexedElement(row); - - var protectRange = (Wix.ProtectRange)this.core.GetIndexedElement("FamilyFileRanges", Convert.ToString(row[0]), Convert.ToString(row[1])); - if (null != protectRange) + if (this.TryGetIndexedElement(row, out var xExternalFile) + && this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, row.FieldAsString(0), row.FieldAsString(0))) { - externalFile.AddChild(protectRange); - usedProtectRanges[protectRange] = null; + xExternalFile.Add(xProtectRange); + usedProtectRanges.Add(xProtectRange); } } } + var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"]; if (null != targetFiles_OptionalDataTable) { var targetImagesTable = tables["TargetImages"]; - var upgradedImagesTable = tables["UpgradedImages"]; - - var targetImageRows = new Hashtable(); - var upgradedImagesRows = new Hashtable(); - - // index the TargetImages table - if (null != targetImagesTable) - { - foreach (var row in targetImagesTable.Rows) - { - targetImageRows.Add(row[0], row); - } - } + var targetImageRows = targetImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); - // index the UpgradedImages table - if (null != upgradedImagesTable) - { - foreach (var row in upgradedImagesTable.Rows) - { - upgradedImagesRows.Add(row[0], row); - } - } + var upgradedImagesTable = tables["UpgradedImages"]; + var upgradedImagesRows = upgradedImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); foreach (var row in targetFiles_OptionalDataTable.Rows) { - var targetFile = (Wix.TargetFile)this.patchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)]; + var xTargetFile = this.PatchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)]; - var targetImageRow = (Row)targetImageRows[row[0]]; - if (null == targetImageRow) + if (!targetImageRows.TryGetValue(row.FieldAsString(0), out var targetImageRow)) { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", Convert.ToString(row[0]), "TargetImages")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); continue; } - var upgradedImagesRow = (Row)upgradedImagesRows[targetImageRow[3]]; - if (null == upgradedImagesRow) + if (!upgradedImagesRows.TryGetValue(row.FieldAsString(3), out var upgradedImagesRow)) { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[3]), "UpgradedImages")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", row.FieldAsString(3), "UpgradedImages")); continue; } - var protectRange = (Wix.ProtectRange)this.core.GetIndexedElement("FamilyFileRanges", Convert.ToString(upgradedImagesRow[4]), Convert.ToString(row[1])); - if (null != protectRange) + if (this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, upgradedImagesRow.FieldAsString(4), row.FieldAsString(1))) { - targetFile.AddChild(protectRange); - usedProtectRanges[protectRange] = null; + xTargetFile.Add(xProtectRange); + usedProtectRanges.Add(xProtectRange); } } } @@ -1321,25 +1140,14 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in familyFileRangesTable.Rows) { - var protectRange = (Wix.ProtectRange)this.core.GetIndexedElement(row); + var xProtectRange = this.GetIndexedElement(row); - if (!usedProtectRanges.Contains(protectRange)) + if (!usedProtectRanges.Contains(xProtectRange)) { - var protectFile = new Wix.ProtectFile(); - - protectFile.File = Convert.ToString(row[1]); + var xProtectFile = new XElement(Names.ProtectFileElement, new XAttribute("File", row.FieldAsString(1))); + xProtectFile.Add(xProtectRange); - protectFile.AddChild(protectRange); - - var family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[0])); - if (null != family) - { - family.AddChild(protectFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, familyFileRangesTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[0]), "ImageFamilies")); - } + this.AddChildToParent("ImageFamilies", xProtectFile, row, 0); } } } @@ -1357,11 +1165,6 @@ namespace WixToolset.Core.WindowsInstaller private void FinalizeFeatureComponentsTable(TableIndexedCollection tables) { var classTable = tables["Class"]; - var extensionTable = tables["Extension"]; - var msiAssemblyTable = tables["MsiAssembly"]; - var publishComponentTable = tables["PublishComponent"]; - var typeLibTable = tables["TypeLib"]; - if (null != classTable) { foreach (var row in classTable.Rows) @@ -1370,6 +1173,7 @@ namespace WixToolset.Core.WindowsInstaller } } + var extensionTable = tables["Extension"]; if (null != extensionTable) { foreach (var row in extensionTable.Rows) @@ -1378,6 +1182,7 @@ namespace WixToolset.Core.WindowsInstaller } } + var msiAssemblyTable = tables["MsiAssembly"]; if (null != msiAssemblyTable) { foreach (var row in msiAssemblyTable.Rows) @@ -1386,6 +1191,7 @@ namespace WixToolset.Core.WindowsInstaller } } + var publishComponentTable = tables["PublishComponent"]; if (null != publishComponentTable) { foreach (var row in publishComponentTable.Rows) @@ -1394,6 +1200,7 @@ namespace WixToolset.Core.WindowsInstaller } } + var typeLibTable = tables["TypeLib"]; if (null != typeLibTable) { foreach (var row in typeLibTable.Rows) @@ -1412,132 +1219,89 @@ namespace WixToolset.Core.WindowsInstaller /// private void FinalizeFileTable(TableIndexedCollection tables) { - var fileTable = tables["File"]; - var mediaTable = tables["Media"]; - var msiAssemblyTable = tables["MsiAssembly"]; - var typeLibTable = tables["TypeLib"]; - // index the media table by media id - RowDictionary mediaRows; - if (null != mediaTable) - { - mediaRows = new RowDictionary(mediaTable); - } + var mediaTable = tables["Media"]; + var mediaRows = new RowDictionary(mediaTable); // set the disk identifiers and sources for files - if (null != fileTable) + foreach (var fileRow in tables["File"]?.Rows.Cast() ?? Enumerable.Empty()) { - foreach (FileRow fileRow in fileTable.Rows) - { - var file = (Wix.File)this.core.GetIndexedElement("File", fileRow.File); + var xFile = this.GetIndexedElement("File", fileRow.File); - // Don't bother processing files that are orphaned (and won't show up in the output anyway) - if (null != file.ParentElement) + // Don't bother processing files that are orphaned (and won't show up in the output anyway) + if (null != xFile.Parent) + { + // set the diskid + if (null != mediaTable) { - // set the diskid - if (null != mediaTable) + foreach (MediaRow mediaRow in mediaTable.Rows) { - foreach (MediaRow mediaRow in mediaTable.Rows) + if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1) { - if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1) - { - file.DiskId = Convert.ToString(mediaRow.DiskId); - break; - } + xFile.SetAttributeValue("DiskId", mediaRow.DiskId); + break; } } + } - // set the source (done here because it requires information from the Directory table) - if (OutputType.Module == this.OutputType) - { - file.Source = String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, file.Id, '.', this.modularizationGuid.Substring(1, 36).Replace('-', '_')); - } - else if (Wix.YesNoDefaultType.yes == file.Compressed || (Wix.YesNoDefaultType.no != file.Compressed && this.compressed) || (OutputType.Product == this.OutputType && this.TreatProductAsModule)) + var fileId = xFile?.Attribute("Id")?.Value; + var fileCompressed = xFile?.Attribute("Compressed")?.Value; + var fileShortName = xFile?.Attribute("ShortName")?.Value; + var fileName = xFile?.Attribute("Name")?.Value; + + // set the source (done here because it requires information from the Directory table) + if (OutputType.Module == this.OutputType) + { + xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId, '.', this.ModularizationGuid.Substring(1, 36).Replace('-', '_'))); + } + else if (fileCompressed == "yes" || (fileCompressed != "no" && this.Compressed) || (OutputType.Product == this.OutputType && this.TreatProductAsModule)) + { + xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId)); + } + else // uncompressed + { + var name = (!this.ShortNames && !String.IsNullOrEmpty(fileName)) ? fileName : fileShortName ?? fileName; + + if (this.Compressed) // uncompressed at the root of the source image { - file.Source = String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, file.Id); + xFile.SetAttributeValue("Source", String.Concat("SourceDir", Path.DirectorySeparatorChar, name)); } - else // uncompressed + else { - var fileName = (null != file.ShortName ? file.ShortName : file.Name); - - if (!this.shortNames && null != file.Name) - { - fileName = file.Name; - } - - if (this.compressed) // uncompressed at the root of the source image - { - file.Source = String.Concat("SourceDir", Path.DirectorySeparatorChar, fileName); - } - else - { - var sourcePath = this.GetSourcePath(file); - - file.Source = Path.Combine(sourcePath, fileName); - } + var sourcePath = this.GetSourcePath(xFile); + xFile.SetAttributeValue("Source", Path.Combine(sourcePath, name)); } } } } // set the file assemblies and manifests - if (null != msiAssemblyTable) + foreach (var row in tables["MsiAssembly"]?.Rows ?? Enumerable.Empty()) { - foreach (var row in msiAssemblyTable.Rows) + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) { - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0])); - - if (null == component) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[0]), "Component")); - } - else + foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) { - foreach (Wix.ISchemaElement element in component.Children) - { - var file = element as Wix.File; - - if (null != file && Wix.YesNoType.yes == file.KeyPath) - { - if (null != row[2]) - { - file.AssemblyManifest = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - file.AssemblyApplication = Convert.ToString(row[3]); - } - - if (null == row[4] || 0 == Convert.ToInt32(row[4])) - { - file.Assembly = Wix.File.AssemblyType.net; - } - else - { - file.Assembly = Wix.File.AssemblyType.win32; - } - } - } + xFile.SetAttributeValue("AssemblyManifest", row.FieldAsString(2)); + xFile.SetAttributeValue("AssemblyApplication", row.FieldAsString(3)); + xFile.SetAttributeValue("Assembly", row.FieldAsInteger(4) == 0 ? ".net" : "win32"); } } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); + } } // nest the TypeLib elements - if (null != typeLibTable) + foreach (var row in tables["TypeLib"]?.Rows ?? Enumerable.Empty()) { - foreach (var row in typeLibTable.Rows) - { - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2])); - var typeLib = (Wix.TypeLib)this.core.GetIndexedElement(row); + var xComponent = this.GetIndexedElement("Component", row.FieldAsString(2)); + var xTypeLib = this.GetIndexedElement(row); - foreach (Wix.ISchemaElement element in component.Children) - { - if (element is Wix.File file && Wix.YesNoType.yes == file.KeyPath) - { - file.AddChild(typeLib); - } - } + foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) + { + xFile.Add(xTypeLib); } } } @@ -1553,61 +1317,41 @@ namespace WixToolset.Core.WindowsInstaller /// private void FinalizeMIMETable(TableIndexedCollection tables) { - var extensionTable = tables["Extension"]; - var mimeTable = tables["MIME"]; - - var comExtensions = new Hashtable(); - - if (null != extensionTable) + var extensionRows = tables["Extension"]?.Rows ?? Enumerable.Empty(); + foreach (var row in extensionRows) { - foreach (var row in extensionTable.Rows) + // set the default MIME element for this extension + var mimeRef = row.FieldAsString(3); + if (null != mimeRef) { - var extension = (Wix.Extension)this.core.GetIndexedElement(row); - - // index the extension - if (!comExtensions.Contains(row[0])) + if (this.TryGetIndexedElement("MIME", out var xMime, mimeRef)) { - comExtensions.Add(row[0], new ArrayList()); + xMime.SetAttributeValue("Default", "yes"); } - ((ArrayList)comExtensions[row[0]]).Add(extension); - - // set the default MIME element for this extension - if (null != row[3]) + else { - var mime = (Wix.MIME)this.core.GetIndexedElement("MIME", Convert.ToString(row[3])); - - if (null != mime) - { - mime.Default = Wix.YesNoType.yes; - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", Convert.ToString(row[3]), "MIME")); - } + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); } } } - if (null != mimeTable) - { - foreach (var row in mimeTable.Rows) - { - var mime = (Wix.MIME)this.core.GetIndexedElement(row); + var extensionsByExtensionId = this.IndexTableOneToMany(extensionRows); - if (comExtensions.Contains(row[1])) - { - var extensionElements = (ArrayList)comExtensions[row[1]]; + foreach (var row in tables["MIME"]?.Rows ?? Enumerable.Empty()) + { + var xMime = this.GetIndexedElement(row); - foreach (Wix.Extension extension in extensionElements) - { - extension.AddChild(mime); - } - } - else + if (extensionsByExtensionId.TryGetValue(row.FieldAsString(1), out var xExtensions)) + { + foreach (var extension in xExtensions) { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", Convert.ToString(row[1]), "Extension")); + extension.Add(xMime); } } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(1), "Extension")); + } } } @@ -1623,125 +1367,80 @@ namespace WixToolset.Core.WindowsInstaller /// private void FinalizeProgIdTable(TableIndexedCollection tables) { - var classTable = tables["Class"]; - var progIdTable = tables["ProgId"]; - var extensionTable = tables["Extension"]; - var componentTable = tables["Component"]; + // add the default ProgIds for each class (and index the class table) + var classRows = tables["Class"]?.Rows?.Where(row => row.FieldAsString(3) != null) ?? Enumerable.Empty(); - var addedProgIds = new Hashtable(); - var classes = new Hashtable(); - var components = new Hashtable(); + var classesByCLSID = this.IndexTableOneToMany(classRows); - // add the default ProgIds for each class (and index the class table) - if (null != classTable) + var addedProgIds = new Dictionary(); + + foreach (var row in classRows) { - foreach (var row in classTable.Rows) - { - var wixClass = (Wix.Class)this.core.GetIndexedElement(row); + var clsid = row.FieldAsString(0); + var xClass = this.GetIndexedElement(row); - if (null != row[3]) + if (this.TryGetIndexedElement("ProgId", out var xProgId, row.FieldAsString(3))) + { + if (addedProgIds.TryGetValue(xProgId, out var progid)) { - var progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[3])); - - if (null != progId) - { - if (addedProgIds.Contains(progId)) - { - this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, Convert.ToString(row[0]), Convert.ToString(row[3]), Convert.ToString(addedProgIds[progId]))); - } - else - { - wixClass.AddChild(progId); - addedProgIds.Add(progId, wixClass.Id); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", Convert.ToString(row[3]), "ProgId")); - } + this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, row.FieldAsString(0), row.FieldAsString(3), progid)); } - - // index the Class elements for nesting of ProgId elements (which don't use the full Class primary key) - if (!classes.Contains(wixClass.Id)) + else { - classes.Add(wixClass.Id, new ArrayList()); + xClass.Add(xProgId); + addedProgIds.Add(xProgId, clsid); } - ((ArrayList)classes[wixClass.Id]).Add(wixClass); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", row.FieldAsString(3), "ProgId")); } } // add the remaining non-default ProgId entries for each class - if (null != progIdTable) + foreach (var row in tables["ProgId"]?.Rows ?? Enumerable.Empty()) { - foreach (var row in progIdTable.Rows) - { - var progId = (Wix.ProgId)this.core.GetIndexedElement(row); + var clsid = row.FieldAsString(2); + var xProgId = this.GetIndexedElement(row); - if (!addedProgIds.Contains(progId) && null != row[2] && null == progId.ParentElement) + if (!addedProgIds.ContainsKey(xProgId) && null != clsid && null == xProgId.Parent) + { + if (classesByCLSID.TryGetValue(clsid, out var xClasses)) { - var classElements = (ArrayList)classes[row[2]]; - - if (null != classElements) - { - foreach (Wix.Class wixClass in classElements) - { - wixClass.AddChild(progId); - addedProgIds.Add(progId, wixClass.Id); - } - } - else + foreach (var xClass in xClasses) { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", Convert.ToString(row[2]), "Class")); + xClass.Add(xProgId); + addedProgIds.Add(xProgId, clsid); } } - } - } - - if (null != componentTable) - { - foreach (var row in componentTable.Rows) - { - var wixComponent = (Wix.Component)this.core.GetIndexedElement(row); - - // index the Class elements for nesting of ProgId elements (which don't use the full Class primary key) - if (!components.Contains(wixComponent.Id)) + else { - components.Add(wixComponent.Id, new ArrayList()); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", row.FieldAsString(2), "Class")); } - ((ArrayList)components[wixComponent.Id]).Add(wixComponent); } } // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension - if (null != extensionTable) - { - foreach (var row in extensionTable.Rows) - { - // ignore the extension if it isn't associated with a progId - if (null == row[2]) - { - continue; - } + var componentsById = this.IndexTableOneToMany(tables, "Component"); - var progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[2])); + foreach (var row in tables["Extension"]?.Rows?.Where(row => row.FieldAsString(2) != null) ?? Enumerable.Empty()) + { + var xProgId = this.GetIndexedElement("ProgId", row.FieldAsString(2)); - // Haven't added the progId yet and it doesn't have a parent progId - if (!addedProgIds.Contains(progId) && null == progId.ParentElement) + // Haven't added the progId yet and it doesn't have a parent progId + if (!addedProgIds.ContainsKey(xProgId) && null == xProgId.Parent) + { + if (componentsById.TryGetValue(row.FieldAsString(1), out var xComponents)) { - var componentElements = (ArrayList)components[row[1]]; - - if (null != componentElements) - { - foreach (Wix.Component wixComponent in componentElements) - { - wixComponent.AddChild(progId); - } - } - else + foreach (var xComponent in xComponents) { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); + xComponent.Add(xProgId); } } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); + } } } } @@ -1755,25 +1454,18 @@ namespace WixToolset.Core.WindowsInstaller /// private void FinalizePropertyTable(TableIndexedCollection tables) { - var propertyTable = tables["Property"]; - var customActionTable = tables["CustomAction"]; - - if (null != propertyTable && null != customActionTable) + foreach (var row in tables["CustomAction"]?.Rows ?? Enumerable.Empty()) { - foreach (var row in customActionTable.Rows) + // If no other fields on the property are set we must have created it in the backend. + var bits = row.FieldAsInteger(1); + if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (bits & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget) + && WindowsInstallerConstants.MsidbCustomActionTypeInScript == (bits & WindowsInstallerConstants.MsidbCustomActionTypeInScript) + && this.TryGetIndexedElement("Property", out var xProperty, row.FieldAsString(0)) + && String.IsNullOrEmpty(xProperty.Attribute("Value")?.Value) + && xProperty.Attribute("Secure")?.Value != "yes" + && xProperty.Attribute("SuppressModularization")?.Value != "yes") { - var bits = Convert.ToInt32(row[1]); - if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (bits & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget) && - WindowsInstallerConstants.MsidbCustomActionTypeInScript == (bits & WindowsInstallerConstants.MsidbCustomActionTypeInScript)) - { - var property = (Wix.Property)this.core.GetIndexedElement("Property", Convert.ToString(row[0])); - - // If no other fields on the property are set we must have created it during link - if (null != property && null == property.Value && Wix.YesNoType.yes != property.Secure && Wix.YesNoType.yes != property.SuppressModularization) - { - this.core.RootElement.RemoveChild(property); - } - } + xProperty.Remove(); } } } @@ -1787,126 +1479,79 @@ namespace WixToolset.Core.WindowsInstaller /// private void FinalizeRemoveFileTable(TableIndexedCollection tables) { - var removeFileTable = tables["RemoveFile"]; - - if (null != removeFileTable) + foreach (var row in tables["RemoveFile"]?.Rows ?? Enumerable.Empty()) { - foreach (var row in removeFileTable.Rows) - { - var isDirectory = false; - var property = Convert.ToString(row[3]); - - // determine if the property is actually authored as a directory - if (null != this.core.GetIndexedElement("Directory", property)) - { - isDirectory = true; - } - - var element = this.core.GetIndexedElement(row); - - var removeFile = element as Wix.RemoveFile; - if (null != removeFile) - { - if (isDirectory) - { - removeFile.Directory = property; - } - else - { - removeFile.Property = property; - } - } - else - { - var removeFolder = (Wix.RemoveFolder)element; + var xRemove = this.GetIndexedElement(row); + var property = row.FieldAsString(3); - if (isDirectory) - { - removeFolder.Directory = property; - } - else - { - removeFolder.Property = property; - } - } + if (this.TryGetIndexedElement("Directory", out var _, property)) + { + xRemove.SetAttributeValue("Directory", property); + } + else + { + xRemove.SetAttributeValue("Property", property); } } } /// - /// Finalize the LockPermissions table. + /// Finalize the LockPermissions or MsiLockPermissionsEx table. /// /// The collection of all tables. + /// Which table to finalize. /// /// Nests the Permission elements below their parent elements. There are no declared foreign /// keys for the parents of the LockPermissions table. /// - private void FinalizeLockPermissionsTable(TableIndexedCollection tables) + private void FinalizePermissionsTable(TableIndexedCollection tables, string tableName) { - var createFolderTable = tables["CreateFolder"]; - var lockPermissionsTable = tables["LockPermissions"]; - - var createFolders = new Hashtable(); + var createFoldersById = this.IndexTableOneToMany(tables, tableName); - // index the CreateFolder table because the foreign key to this table from the - // LockPermissions table is only part of the primary key of this table - if (null != createFolderTable) + foreach (var row in tables[tableName]?.Rows ?? Enumerable.Empty()) { - foreach (var row in createFolderTable.Rows) - { - var createFolder = (Wix.CreateFolder)this.core.GetIndexedElement(row); - var directoryId = Convert.ToString(row[0]); + var id = row.FieldAsString(0); + var table = row.FieldAsString(1); + var xPermission = this.GetIndexedElement(row); - if (!createFolders.Contains(directoryId)) + if ("CreateFolder" == table) + { + if (createFoldersById.TryGetValue(id, out var xCreateFolders)) + { + foreach (var xCreateFolder in xCreateFolders) + { + xCreateFolder.Add(xPermission); + } + } + else { - createFolders.Add(directoryId, new ArrayList()); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); } - ((ArrayList)createFolders[directoryId]).Add(createFolder); } - } - - if (null != lockPermissionsTable) - { - foreach (var row in lockPermissionsTable.Rows) + else { - var id = Convert.ToString(row[0]); - var table = Convert.ToString(row[1]); - - var permission = (Wix.Permission)this.core.GetIndexedElement(row); - - if ("CreateFolder" == table) + if (this.TryGetIndexedElement(table, out var xParent, id)) { - var createFolderElements = (ArrayList)createFolders[id]; - - if (null != createFolderElements) - { - foreach (Wix.CreateFolder createFolder in createFolderElements) - { - createFolder.AddChild(permission); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "LockPermissions", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } + xParent.Add(xPermission); } else { - var parentElement = (Wix.IParentElement)this.core.GetIndexedElement(table, id); - - if (null != parentElement) - { - parentElement.AddChild(permission); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "LockPermissions", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); } } } } + /// + /// Finalize the LockPermissions table. + /// + /// The collection of all tables. + /// + /// Nests the Permission elements below their parent elements. There are no declared foreign + /// keys for the parents of the LockPermissions table. + /// + private void FinalizeLockPermissionsTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "LockPermissions"); + /// /// Finalize the MsiLockPermissionsEx table. /// @@ -1915,164 +1560,58 @@ namespace WixToolset.Core.WindowsInstaller /// Nests the PermissionEx elements below their parent elements. There are no declared foreign /// keys for the parents of the MsiLockPermissionsEx table. /// - private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) + private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "MsiLockPermissionsEx"); + + private static Dictionary> IndexTable(Table table, int keyColumn, int? dataColumn) { - var createFolderTable = tables["CreateFolder"]; - var msiLockPermissionsExTable = tables["MsiLockPermissionsEx"]; + if (table == null) + { + return new Dictionary>(); + } - var createFolders = new Hashtable(); + return table.Rows + .ToLookup(row => row.FieldAsString(keyColumn), row => dataColumn.HasValue ? row.FieldAsString(dataColumn.Value) : null) + .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); + } - // index the CreateFolder table because the foreign key to this table from the - // MsiLockPermissionsEx table is only part of the primary key of this table - if (null != createFolderTable) + private static XElement FindComplianceDrive(XElement xSearch) + { + var xComplianceDrive = xSearch.Element(Names.ComplianceDriveElement); + if (null == xComplianceDrive) { - foreach (var row in createFolderTable.Rows) - { - var createFolder = (Wix.CreateFolder)this.core.GetIndexedElement(row); - var directoryId = Convert.ToString(row[0]); - - if (!createFolders.Contains(directoryId)) - { - createFolders.Add(directoryId, new ArrayList()); - } - ((ArrayList)createFolders[directoryId]).Add(createFolder); - } + xComplianceDrive = new XElement(Names.ComplianceDriveElement); + xSearch.Add(xComplianceDrive); } - if (null != msiLockPermissionsExTable) - { - foreach (var row in msiLockPermissionsExTable.Rows) - { - var id = Convert.ToString(row[1]); - var table = Convert.ToString(row[2]); + return xComplianceDrive; + } - var permissionEx = (Wix.PermissionEx)this.core.GetIndexedElement(row); + /// + /// Finalize the search tables. + /// + /// The collection of all tables. + /// Does all the complex linking required for the search tables. + private void FinalizeSearchTables(TableIndexedCollection tables) + { + var appSearches = IndexTable(tables["AppSearch"], keyColumn: 1, dataColumn: 0); + var ccpSearches = IndexTable(tables["CCPSearch"], keyColumn: 0, dataColumn: null); + var drLocators = tables["DrLocator"]?.Rows.ToDictionary(row => this.GetIndexedElement(row), row => row); - if ("CreateFolder" == table) - { - var createFolderElements = (ArrayList)createFolders[id]; - - if (null != createFolderElements) - { - foreach (Wix.CreateFolder createFolder in createFolderElements) - { - createFolder.AddChild(permissionEx); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiLockPermissionsEx", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } - } - else - { - var parentElement = (Wix.IParentElement)this.core.GetIndexedElement(table, id); - - if (null != parentElement) - { - parentElement.AddChild(permissionEx); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiLockPermissionsEx", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } - } - } - } - } - - /// - /// Finalize the search tables. - /// - /// The collection of all tables. - /// Does all the complex linking required for the search tables. - private void FinalizeSearchTables(TableIndexedCollection tables) - { - var appSearchTable = tables["AppSearch"]; - var ccpSearchTable = tables["CCPSearch"]; - var drLocatorTable = tables["DrLocator"]; - - var appSearches = new Hashtable(); - var ccpSearches = new Hashtable(); - var drLocators = new Hashtable(); - var locators = new Hashtable(); - var usedSearchElements = new Hashtable(); - var unusedSearchElements = new Dictionary(); - - Wix.ComplianceCheck complianceCheck = null; - - // index the AppSearch table by signatures - if (null != appSearchTable) - { - foreach (var row in appSearchTable.Rows) - { - var property = Convert.ToString(row[0]); - var signature = Convert.ToString(row[1]); - - if (!appSearches.Contains(signature)) - { - appSearches.Add(signature, new StringCollection()); - } - - ((StringCollection)appSearches[signature]).Add(property); - } - } - - // index the CCPSearch table by signatures - if (null != ccpSearchTable) - { - foreach (var row in ccpSearchTable.Rows) - { - var signature = Convert.ToString(row[0]); - - if (!ccpSearches.Contains(signature)) - { - ccpSearches.Add(signature, new StringCollection()); - } - - ((StringCollection)ccpSearches[signature]).Add(null); - - if (null == complianceCheck && !appSearches.Contains(signature)) - { - complianceCheck = new Wix.ComplianceCheck(); - this.core.RootElement.AddChild(complianceCheck); - } - } - } - - // index the directory searches by their search elements (to get back the original row) - if (null != drLocatorTable) + var xComplianceCheck = new XElement(Names.ComplianceCheckElement); + if (ccpSearches.Keys.Any(ccpSignature => !appSearches.ContainsKey(ccpSignature))) { - foreach (var row in drLocatorTable.Rows) - { - drLocators.Add(this.core.GetIndexedElement(row), row); - } + this.RootElement.Add(xComplianceCheck); } // index the locator tables by their signatures - var locatorTableNames = new string[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" }; - foreach (var locatorTableName in locatorTableNames) - { - var locatorTable = tables[locatorTableName]; - - if (null != locatorTable) - { - foreach (var row in locatorTable.Rows) - { - var signature = Convert.ToString(row[0]); - - if (!locators.Contains(signature)) - { - locators.Add(signature, new ArrayList()); - } - - ((ArrayList)locators[signature]).Add(row); - } - } - } + var locators = + new[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" } + .SelectMany(table => tables[table]?.Rows ?? Enumerable.Empty()) + .ToLookup(row => row.FieldAsString(0), row => row) + .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef) - foreach (ArrayList locatorRows in locators.Values) + foreach (var locatorRows in locators.Values) { var firstDrLocator = -1; @@ -2097,205 +1636,142 @@ namespace WixToolset.Core.WindowsInstaller } } - foreach (string signature in locators.Keys) + var xUsedSearches = new HashSet(); + var xUnusedSearches = new Dictionary(); + + foreach (var signature in locators.Keys) { - var locatorRows = (ArrayList)locators[signature]; - var signatureSearchElements = new ArrayList(); + var locatorRows = locators[signature]; + var xSignatureSearches = new List(); - foreach (Row locatorRow in locatorRows) + foreach (var locatorRow in locatorRows) { var used = true; - var searchElement = this.core.GetIndexedElement(locatorRow); + var xSearch = this.GetIndexedElement(locatorRow); - if ("Signature" == locatorRow.TableDefinition.Name && 0 < signatureSearchElements.Count) + if ("Signature" == locatorRow.TableDefinition.Name && 0 < xSignatureSearches.Count) { - foreach (Wix.IParentElement searchParentElement in signatureSearchElements) + foreach (var xSearchParent in xSignatureSearches) { - if (!usedSearchElements.Contains(searchElement)) + if (!xUsedSearches.Contains(xSearch)) { - searchParentElement.AddChild(searchElement); - usedSearchElements[searchElement] = null; + xSearchParent.Add(xSearch); + xUsedSearches.Add(xSearch); } else { - var fileSearchRef = new Wix.FileSearchRef(); + var xFileSearchRef = new XElement(Names.FileSearchRefElement, + new XAttribute("Id", signature)); - fileSearchRef.Id = signature; - - searchParentElement.AddChild(fileSearchRef); + xSearchParent.Add(xFileSearchRef); } } } - else if ("DrLocator" == locatorRow.TableDefinition.Name && null != locatorRow[1]) + else if ("DrLocator" == locatorRow.TableDefinition.Name && !locatorRow.IsColumnNull(1)) { - var drSearchElement = (Wix.DirectorySearch)searchElement; - var parentSignature = Convert.ToString(locatorRow[1]); + var parentSignature = locatorRow.FieldAsString(1); if ("CCP_DRIVE" == parentSignature) { - if (appSearches.Contains(signature)) + if (appSearches.ContainsKey(signature) + && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) { - var appSearchPropertyIds = (StringCollection)appSearches[signature]; - foreach (var propertyId in appSearchPropertyIds) { - var property = this.EnsureProperty(propertyId); - Wix.ComplianceDrive complianceDrive = null; - - if (ccpSearches.Contains(signature)) - { - property.ComplianceCheck = Wix.YesNoType.yes; - } + var xProperty = this.EnsureProperty(propertyId); - foreach (Wix.ISchemaElement element in property.Children) + if (ccpSearches.ContainsKey(signature)) { - complianceDrive = element as Wix.ComplianceDrive; - if (null != complianceDrive) - { - break; - } + xProperty.SetAttributeValue("ComplianceCheck", "yes"); } - if (null == complianceDrive) - { - complianceDrive = new Wix.ComplianceDrive(); - property.AddChild(complianceDrive); - } + var xComplianceDrive = FindComplianceDrive(xProperty); - if (!usedSearchElements.Contains(searchElement)) + if (!xUsedSearches.Contains(xSearch)) { - complianceDrive.AddChild(searchElement); - usedSearchElements[searchElement] = null; + xComplianceDrive.Add(xSearch); + xUsedSearches.Add(xSearch); } else { - var directorySearchRef = new Wix.DirectorySearchRef(); - - directorySearchRef.Id = signature; - - if (null != locatorRow[1]) - { - directorySearchRef.Parent = Convert.ToString(locatorRow[1]); - } - - if (null != locatorRow[2]) - { - directorySearchRef.Path = Convert.ToString(locatorRow[2]); - } + var directorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", signature), + XAttributeIfNotNull("Parent", locatorRow, 1), + XAttributeIfNotNull("Path", locatorRow, 2)); - complianceDrive.AddChild(directorySearchRef); - signatureSearchElements.Add(directorySearchRef); + xComplianceDrive.Add(directorySearchRef); + xSignatureSearches.Add(directorySearchRef); } } } - else if (ccpSearches.Contains(signature)) + else if (ccpSearches.ContainsKey(signature)) { - Wix.ComplianceDrive complianceDrive = null; - - foreach (Wix.ISchemaElement element in complianceCheck.Children) - { - complianceDrive = element as Wix.ComplianceDrive; - if (null != complianceDrive) - { - break; - } - } - - if (null == complianceDrive) - { - complianceDrive = new Wix.ComplianceDrive(); - complianceCheck.AddChild(complianceDrive); - } + var xComplianceDrive = FindComplianceDrive(xComplianceCheck); - if (!usedSearchElements.Contains(searchElement)) + if (!xUsedSearches.Contains(xSearch)) { - complianceDrive.AddChild(searchElement); - usedSearchElements[searchElement] = null; + xComplianceDrive.Add(xSearch); + xUsedSearches.Add(xSearch); } else { - var directorySearchRef = new Wix.DirectorySearchRef(); - - directorySearchRef.Id = signature; - - if (null != locatorRow[1]) - { - directorySearchRef.Parent = Convert.ToString(locatorRow[1]); - } - - if (null != locatorRow[2]) - { - directorySearchRef.Path = Convert.ToString(locatorRow[2]); - } + var directorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", signature), + XAttributeIfNotNull("Parent", locatorRow, 1), + XAttributeIfNotNull("Path", locatorRow, 2)); - complianceDrive.AddChild(directorySearchRef); - signatureSearchElements.Add(directorySearchRef); + xComplianceDrive.Add(directorySearchRef); + xSignatureSearches.Add(directorySearchRef); } } } else { var usedDrLocator = false; - var parentLocatorRows = (ArrayList)locators[parentSignature]; - if (null != parentLocatorRows) + if (locators.TryGetValue(parentSignature, out var parentLocatorRows)) { - foreach (Row parentLocatorRow in parentLocatorRows) + foreach (var parentLocatorRow in parentLocatorRows) { if ("DrLocator" == parentLocatorRow.TableDefinition.Name) { - var parentSearchElement = (Wix.IParentElement)this.core.GetIndexedElement(parentLocatorRow); + var xParentSearch = this.GetIndexedElement(parentLocatorRow); - if (parentSearchElement.Children.GetEnumerator().MoveNext()) + if (xParentSearch.HasElements) { - var parentDrLocatorRow = (Row)drLocators[parentSearchElement]; - var directorySeachRef = new Wix.DirectorySearchRef(); - - directorySeachRef.Id = parentSignature; - - if (null != parentDrLocatorRow[1]) - { - directorySeachRef.Parent = Convert.ToString(parentDrLocatorRow[1]); - } - - if (null != parentDrLocatorRow[2]) - { - directorySeachRef.Path = Convert.ToString(parentDrLocatorRow[2]); - } - - parentSearchElement = directorySeachRef; - unusedSearchElements.Add(directorySeachRef.Id, directorySeachRef); + var parentDrLocatorRow = drLocators[xParentSearch]; + var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", parentSignature), + XAttributeIfNotNull("Parent", parentDrLocatorRow, 1), + XAttributeIfNotNull("Path", parentDrLocatorRow, 2)); + + xParentSearch = xDirectorySearchRef; + xUnusedSearches.Add(parentSignature, xDirectorySearchRef); } - if (!usedSearchElements.Contains(searchElement)) + if (!xUsedSearches.Contains(xSearch)) { - parentSearchElement.AddChild(searchElement); - usedSearchElements[searchElement] = null; + xParentSearch.Add(xSearch); + xUsedSearches.Add(xSearch); usedDrLocator = true; } else { - var directorySearchRef = new Wix.DirectorySearchRef(); - - directorySearchRef.Id = signature; - - directorySearchRef.Parent = parentSignature; - - if (null != locatorRow[2]) - { - directorySearchRef.Path = Convert.ToString(locatorRow[2]); - } + var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", signature), + new XAttribute("Parent", parentSignature), + XAttributeIfNotNull("Path", locatorRow, 2)); - parentSearchElement.AddChild(searchElement); + xParentSearch.Add(xSearch); usedDrLocator = true; } } else if ("RegLocator" == parentLocatorRow.TableDefinition.Name) { - var parentSearchElement = (Wix.IParentElement)this.core.GetIndexedElement(parentLocatorRow); + var xParentSearch = this.GetIndexedElement(parentLocatorRow); - parentSearchElement.AddChild(searchElement); - usedSearchElements[searchElement] = null; + xParentSearch.Add(xSearch); + xUsedSearches.Add(xSearch); usedDrLocator = true; } } @@ -2303,7 +1779,7 @@ namespace WixToolset.Core.WindowsInstaller // keep track of unused DrLocator rows if (!usedDrLocator) { - unusedSearchElements.Add(drSearchElement.Id, drSearchElement); + xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); } } else @@ -2312,32 +1788,30 @@ namespace WixToolset.Core.WindowsInstaller } } } - else if (appSearches.Contains(signature)) + else if (appSearches.ContainsKey(signature) + && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) { - var appSearchPropertyIds = (StringCollection)appSearches[signature]; - foreach (var propertyId in appSearchPropertyIds) { - var property = this.EnsureProperty(propertyId); + var xProperty = this.EnsureProperty(propertyId); - if (ccpSearches.Contains(signature)) + if (ccpSearches.ContainsKey(signature)) { - property.ComplianceCheck = Wix.YesNoType.yes; + xProperty.SetAttributeValue("ComplianceCheck", "yes"); } - if (!usedSearchElements.Contains(searchElement)) + if (!xUsedSearches.Contains(xSearch)) { - property.AddChild(searchElement); - usedSearchElements[searchElement] = null; + xProperty.Add(xSearch); + xUsedSearches.Add(xSearch); } else if ("RegLocator" == locatorRow.TableDefinition.Name) { - var registrySearchRef = new Wix.RegistrySearchRef(); - - registrySearchRef.Id = signature; + var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, + new XAttribute("Id", signature)); - property.AddChild(registrySearchRef); - signatureSearchElements.Add(registrySearchRef); + xProperty.Add(xRegistrySearchRef); + xSignatureSearches.Add(xRegistrySearchRef); } else { @@ -2345,21 +1819,20 @@ namespace WixToolset.Core.WindowsInstaller } } } - else if (ccpSearches.Contains(signature)) + else if (ccpSearches.ContainsKey(signature)) { - if (!usedSearchElements.Contains(searchElement)) + if (!xUsedSearches.Contains(xSearch)) { - complianceCheck.AddChild(searchElement); - usedSearchElements[searchElement] = null; + xComplianceCheck.Add(xSearch); + xUsedSearches.Add(xSearch); } else if ("RegLocator" == locatorRow.TableDefinition.Name) { - var registrySearchRef = new Wix.RegistrySearchRef(); + var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, + new XAttribute("Id", signature)); - registrySearchRef.Id = signature; - - complianceCheck.AddChild(registrySearchRef); - signatureSearchElements.Add(registrySearchRef); + xComplianceCheck.Add(xRegistrySearchRef); + xSignatureSearches.Add(xRegistrySearchRef); } else { @@ -2368,13 +1841,9 @@ namespace WixToolset.Core.WindowsInstaller } else { - if (searchElement is Wix.DirectorySearch directorySearch) - { - unusedSearchElements.Add(directorySearch.Id, directorySearch); - } - else if (searchElement is Wix.RegistrySearch registrySearch) + if (xSearch.Name.LocalName == "DirectorySearch" || xSearch.Name.LocalName == "RegistrySearch") { - unusedSearchElements.Add(registrySearch.Id, registrySearch); + xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); } else { @@ -2386,51 +1855,44 @@ namespace WixToolset.Core.WindowsInstaller // keep track of the search elements for this signature so that nested searches go in the proper parents if (used) { - signatureSearchElements.Add(searchElement); + xSignatureSearches.Add(xSearch); } } } // Iterate through the unused elements through a sorted list of their ids so the output is deterministic. - var unusedSearchElementKeys = unusedSearchElements.Keys.ToList(); - unusedSearchElementKeys.Sort(); - foreach (var unusedSearchElementKey in unusedSearchElementKeys) + foreach (var unusedSearch in xUnusedSearches.OrderBy(kvp => kvp.Key)) { - var unusedSearchElement = unusedSearchElements[unusedSearchElementKey]; var used = false; - Wix.DirectorySearch leafDirectorySearch = null; - var parentElement = unusedSearchElement; + XElement xLeafDirectorySearch = null; + var xUnusedSearch = unusedSearch.Value; + var xParent = xUnusedSearch; var updatedLeaf = true; while (updatedLeaf) { updatedLeaf = false; - foreach (var schemaElement in parentElement.Children) + + var xDirectorySearch = xParent.Element(Names.DirectorySearchElement); + if (xDirectorySearch != null) { - if (schemaElement is Wix.DirectorySearch directorySearch) - { - parentElement = leafDirectorySearch = directorySearch; - updatedLeaf = true; - break; - } + xParent = xLeafDirectorySearch = xDirectorySearch; + updatedLeaf = true; } } - if (leafDirectorySearch != null) + if (xLeafDirectorySearch != null) { - var appSearchProperties = (StringCollection)appSearches[leafDirectorySearch.Id]; - - var unusedSearchSchemaElement = unusedSearchElement as Wix.ISchemaElement; - if (null != appSearchProperties) + var leafDirectorySearchId = xLeafDirectorySearch.Attribute("Id").Value; + if (appSearches.TryGetValue(leafDirectorySearchId, out var appSearchPropertyIds)) { - var property = this.EnsureProperty(appSearchProperties[0]); - - property.AddChild(unusedSearchSchemaElement); + var xProperty = this.EnsureProperty(appSearchPropertyIds[0]); + xProperty.Add(xUnusedSearch); used = true; } - else if (ccpSearches.Contains(leafDirectorySearch.Id)) + else if (ccpSearches.ContainsKey(leafDirectorySearchId)) { - complianceCheck.AddChild(unusedSearchSchemaElement); + xComplianceCheck.Add(xUnusedSearch); used = true; } else @@ -2464,18 +1926,19 @@ namespace WixToolset.Core.WindowsInstaller foreach (var row in shortcutTable.Rows) { - var shortcut = (Wix.Shortcut)this.core.GetIndexedElement(row); - var target = Convert.ToString(row[4]); - var feature = this.core.GetIndexedElement("Feature", target); - if (feature == null) + var xShortcut = this.GetIndexedElement(row); + + var target = row.FieldAsString(4); + + if (this.TryGetIndexedElement("Feature", out var _, target)) { - // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element - shortcut.Target = target; + xShortcut.SetAttributeValue("Advertise", "yes"); + this.SetPrimaryFeature(row, 4, 3); } else { - shortcut.Advertise = Wix.YesNoType.yes; - this.SetPrimaryFeature(row, 4, 3); + // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element + xShortcut.SetAttributeValue("Target", target); } } } @@ -2520,12 +1983,12 @@ namespace WixToolset.Core.WindowsInstaller actionSymbol.Action = action; - if (null != row[1]) + if (!row.IsColumnNull(1)) { - actionSymbol.Condition = Convert.ToString(row[1]); + actionSymbol.Condition = row.FieldAsString(1); } - actionSymbol.Sequence = Convert.ToInt32(row[2]); + actionSymbol.Sequence = row.FieldAsInteger(2); actionSymbol.SequenceTable = sequenceTable; @@ -2654,30 +2117,30 @@ namespace WixToolset.Core.WindowsInstaller actionRow.Action = row.FieldAsString(0); - if (null != row[1]) + if (!row.IsColumnNull(1)) { - actionRow.Sequence = Convert.ToInt32(row[1]); + actionRow.Sequence = row.FieldAsInteger(1); } - if (null != row[2] && null != row[3]) + if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) { - switch (Convert.ToInt32(row[3])) + switch (row.FieldAsInteger(3)) { - case 0: - actionRow.Before = Convert.ToString(row[2]); - break; - case 1: - actionRow.After = Convert.ToString(row[2]); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3])); - break; + case 0: + actionRow.Before = row.FieldAsString(2); + break; + case 1: + actionRow.After = row.FieldAsString(2); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3])); + break; } } - if (null != row[4]) + if (!row.IsColumnNull(4)) { - actionRow.Condition = Convert.ToString(row[4]); + actionRow.Condition = row.FieldAsString(4); } actionRow.SequenceTable = sequenceTable; @@ -2707,7 +2170,6 @@ namespace WixToolset.Core.WindowsInstaller var upgradeTable = tables["Upgrade"]; string downgradeErrorMessage = null; string disallowUpgradeErrorMessage = null; - var majorUpgrade = new Wix.MajorUpgrade(); // find the DowngradePreventedCondition launch condition message if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count) @@ -2727,65 +2189,63 @@ namespace WixToolset.Core.WindowsInstaller if (null != upgradeTable && 0 < upgradeTable.Rows.Count) { - var hasMajorUpgrade = false; + XElement xMajorUpgrade = null; - foreach (var row in upgradeTable.Rows) + foreach (UpgradeRow upgradeRow in upgradeTable.Rows) { - var upgradeRow = (UpgradeRow)row; - if (Common.UpgradeDetectedProperty == upgradeRow.ActionProperty) { - hasMajorUpgrade = true; var attr = upgradeRow.Attributes; var removeFeatures = upgradeRow.Remove; + xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) { - majorUpgrade.AllowSameVersionUpgrades = Wix.YesNoType.yes; + xMajorUpgrade.SetAttributeValue("AllowSameVersionUpgrades", "yes"); } if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures != (attr & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) { - majorUpgrade.MigrateFeatures = Wix.YesNoType.no; + xMajorUpgrade.SetAttributeValue("MigrateFeatures", "no"); } if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) { - majorUpgrade.IgnoreRemoveFailure = Wix.YesNoType.yes; + xMajorUpgrade.SetAttributeValue("IgnoreRemoveFailure", "yes"); } if (!String.IsNullOrEmpty(removeFeatures)) { - majorUpgrade.RemoveFeatures = removeFeatures; + xMajorUpgrade.SetAttributeValue("RemoveFeatures", removeFeatures); } } else if (Common.DowngradeDetectedProperty == upgradeRow.ActionProperty) { - hasMajorUpgrade = true; - majorUpgrade.DowngradeErrorMessage = downgradeErrorMessage; + xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); + xMajorUpgrade.SetAttributeValue("DowngradeErrorMessage", downgradeErrorMessage); } } - if (hasMajorUpgrade) + if (xMajorUpgrade != null) { if (String.IsNullOrEmpty(downgradeErrorMessage)) { - majorUpgrade.AllowDowngrades = Wix.YesNoType.yes; + xMajorUpgrade.SetAttributeValue("AllowDowngrades", "yes"); } if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage)) { - majorUpgrade.Disallow = Wix.YesNoType.yes; - majorUpgrade.DisallowUpgradeErrorMessage = disallowUpgradeErrorMessage; + xMajorUpgrade.SetAttributeValue("Disallow", "yes"); + xMajorUpgrade.SetAttributeValue("DisallowUpgradeErrorMessage", disallowUpgradeErrorMessage); } var scheduledType = DetermineMajorUpgradeScheduling(tables); - if (Wix.MajorUpgrade.ScheduleType.afterInstallValidate != scheduledType) + if (scheduledType != "afterInstallValidate") { - majorUpgrade.Schedule = scheduledType; + xMajorUpgrade.SetAttributeValue("Schedule", scheduledType); } - this.core.RootElement.AddChild(majorUpgrade); + this.RootElement.Add(xMajorUpgrade); } } } @@ -2801,43 +2261,25 @@ namespace WixToolset.Core.WindowsInstaller /// private void FinalizeVerbTable(TableIndexedCollection tables) { - var extensionTable = tables["Extension"]; - var verbTable = tables["Verb"]; - - var extensionElements = new Hashtable(); - - if (null != extensionTable) - { - foreach (var row in extensionTable.Rows) - { - var extension = (Wix.Extension)this.core.GetIndexedElement(row); - - if (!extensionElements.Contains(row[0])) - { - extensionElements.Add(row[0], new ArrayList()); - } - - ((ArrayList)extensionElements[row[0]]).Add(extension); - } - } + var xExtensions = this.IndexTableOneToMany(tables["Extension"]); + var verbTable = tables["Verb"]; if (null != verbTable) { foreach (var row in verbTable.Rows) { - var verb = (Wix.Verb)this.core.GetIndexedElement(row); - - var extensionsArray = (ArrayList)extensionElements[row[0]]; - if (null != extensionsArray) + if (xExtensions.TryGetValue(row.FieldAsString(0), out var xVerbExtensions)) { - foreach (Wix.Extension extension in extensionsArray) + var xVerb = this.GetIndexedElement(row); + + foreach (var xVerbExtension in xVerbExtensions) { - extension.AddChild(verb); + xVerbExtension.Add(xVerb); } } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", Convert.ToString(row[0]), "Extension")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(0), "Extension")); } } } @@ -2846,33 +2288,38 @@ namespace WixToolset.Core.WindowsInstaller /// /// Get the path to a file in the source image. /// - /// The file. + /// The file. /// The path to the file in the source image. - private string GetSourcePath(Wix.File file) + private string GetSourcePath(XElement xFile) { var sourcePath = new StringBuilder(); - var component = (Wix.Component)file.ParentElement; + var component = xFile.Parent; - for (var directory = (Wix.Directory)component.ParentElement; null != directory; directory = directory.ParentElement as Wix.Directory) + for (var xDirectory = component.Parent; null != xDirectory && xDirectory.Name.LocalName == "Directory"; xDirectory = xDirectory.Parent) { string name; - if (!this.shortNames && null != directory.SourceName) + var dirSourceName = xDirectory.Attribute("SourceName")?.Value; + var dirShortSourceName = xDirectory.Attribute("ShortSourceName")?.Value; + var dirShortName = xDirectory.Attribute("ShortName")?.Value; + var dirName = xDirectory.Attribute("Name")?.Value; + + if (!this.ShortNames && null != dirSourceName) { - name = directory.SourceName; + name = dirSourceName; } - else if (null != directory.ShortSourceName) + else if (null != dirShortSourceName) { - name = directory.ShortSourceName; + name = dirShortSourceName; } - else if (!this.shortNames || null == directory.ShortName) + else if (!this.ShortNames || null == dirShortName) { - name = directory.Name; + name = dirName; } else { - name = directory.ShortName; + name = dirShortName; } if (0 == sourcePath.Length) @@ -2895,11 +2342,11 @@ namespace WixToolset.Core.WindowsInstaller /// The name of the table to resolve. /// The unsorted table names. /// The sorted table names. - private void ResolveTableDependencies(string tableName, SortedList unsortedTableNames, StringCollection sortedTableNames) + private void ResolveTableDependencies(string tableName, List unsortedTableNames, HashSet sortedTableNames) { unsortedTableNames.Remove(tableName); - foreach (var columnDefinition in this.tableDefinitions[tableName].Columns) + foreach (var columnDefinition in this.TableDefinitions[tableName].Columns) { // no dependency to resolve because this column doesn't reference another table if (null == columnDefinition.KeyTable) @@ -2917,7 +2364,7 @@ namespace WixToolset.Core.WindowsInstaller { continue; // dependent table has already been sorted } - else if (!this.tableDefinitions.Contains(keyTable)) + else if (!this.TableDefinitions.Contains(keyTable)) { this.Messaging.Write(ErrorMessages.MissingTableDefinition(keyTable)); } @@ -2941,24 +2388,18 @@ namespace WixToolset.Core.WindowsInstaller /// Get the names of the tables to process in the order they should be processed, according to their dependencies. /// /// A StringCollection containing the ordered table names. - private StringCollection GetSortedTableNames() + private HashSet GetOrderedTableNames() { - var sortedTableNames = new StringCollection(); - var unsortedTableNames = new SortedList(); - - // index the table names - foreach (var tableDefinition in this.tableDefinitions) - { - unsortedTableNames.Add(tableDefinition.Name, tableDefinition.Name); - } + var orderedTableNames = new HashSet(); + var unsortedTableNames = new List(this.TableDefinitions.Select(t => t.Name)); // resolve the dependencies for each table while (0 < unsortedTableNames.Count) { - this.ResolveTableDependencies(Convert.ToString(unsortedTableNames.GetByIndex(0)), unsortedTableNames, sortedTableNames); + this.ResolveTableDependencies(unsortedTableNames[0], unsortedTableNames, orderedTableNames); } - return sortedTableNames; + return orderedTableNames; } /// @@ -2968,26 +2409,17 @@ namespace WixToolset.Core.WindowsInstaller private void InitializeDecompile(TableIndexedCollection tables, int codepage) { // reset all the state information - this.compressed = false; - this.patchTargetFiles.Clear(); - this.sequenceElements.Clear(); - this.shortNames = false; + this.Compressed = false; + this.ShortNames = false; + + this.Singletons.Clear(); + this.IndexedElements.Clear(); + this.PatchTargetFiles.Clear(); // set the codepage if its not neutral (0) if (0 != codepage) { - switch (this.OutputType) - { - case OutputType.Module: - ((Wix.Module)this.core.RootElement).Codepage = codepage.ToString(CultureInfo.InvariantCulture); - break; - case OutputType.PatchCreation: - ((Wix.PatchCreation)this.core.RootElement).Codepage = codepage.ToString(CultureInfo.InvariantCulture); - break; - case OutputType.Product: - ((Wix.Product)this.core.RootElement).Codepage = codepage.ToString(CultureInfo.InvariantCulture); - break; - } + this.RootElement.SetAttributeValue("Codepage", codepage); } // index the rows from the extension libraries @@ -3011,15 +2443,15 @@ namespace WixToolset.Core.WindowsInstaller // the Actions table needs to be handled specially if ("WixAction" == table.Name) { - primaryKey = Convert.ToString(row[1]); + primaryKey = row.FieldAsString(1); if (OutputType.Module == this.outputType) { - tableName = String.Concat("Module", Convert.ToString(row[0])); + tableName = String.Concat("Module", row.FieldAsString(0)); } else { - tableName = Convert.ToString(row[0]); + tableName = row.FieldAsString(0); } } else @@ -3077,9 +2509,8 @@ namespace WixToolset.Core.WindowsInstaller /// The output being decompiled. private void DecompileTables(WindowsInstallerData output) { - var sortedTableNames = this.GetSortedTableNames(); - - foreach (var tableName in sortedTableNames) + var orderedTableNames = this.GetOrderedTableNames(); + foreach (var tableName in orderedTableNames) { var table = output.Tables[tableName]; @@ -3094,328 +2525,326 @@ namespace WixToolset.Core.WindowsInstaller // empty tables may be kept with EnsureTable if the user set the proper option if (0 == table.Rows.Count && this.SuppressDroppingEmptyTables) { - var ensureTable = new Wix.EnsureTable(); - ensureTable.Id = table.Name; - this.core.RootElement.AddChild(ensureTable); + this.RootElement.Add(new XElement(Names.EnsureTableElement, new XAttribute("Id", table.Name))); } switch (table.Name) { - case "_SummaryInformation": - this.Decompile_SummaryInformationTable(table); - break; - case "AdminExecuteSequence": - case "AdminUISequence": - case "AdvtExecuteSequence": - case "InstallExecuteSequence": - case "InstallUISequence": - case "ModuleAdminExecuteSequence": - case "ModuleAdminUISequence": - case "ModuleAdvtExecuteSequence": - case "ModuleInstallExecuteSequence": - case "ModuleInstallUISequence": - // handled in FinalizeSequenceTables - break; - case "ActionText": - this.DecompileActionTextTable(table); - break; - case "AdvtUISequence": - this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); - break; - case "AppId": - this.DecompileAppIdTable(table); - break; - case "AppSearch": - // handled in FinalizeSearchTables - break; - case "BBControl": - this.DecompileBBControlTable(table); - break; - case "Billboard": - this.DecompileBillboardTable(table); - break; - case "Binary": - this.DecompileBinaryTable(table); - break; - case "BindImage": - this.DecompileBindImageTable(table); - break; - case "CCPSearch": - // handled in FinalizeSearchTables - break; - case "CheckBox": - // handled in FinalizeCheckBoxTable - break; - case "Class": - this.DecompileClassTable(table); - break; - case "ComboBox": - this.DecompileComboBoxTable(table); - break; - case "Control": - this.DecompileControlTable(table); - break; - case "ControlCondition": - this.DecompileControlConditionTable(table); - break; - case "ControlEvent": - this.DecompileControlEventTable(table); - break; - case "CreateFolder": - this.DecompileCreateFolderTable(table); - break; - case "CustomAction": - this.DecompileCustomActionTable(table); - break; - case "CompLocator": - this.DecompileCompLocatorTable(table); - break; - case "Complus": - this.DecompileComplusTable(table); - break; - case "Component": - this.DecompileComponentTable(table); - break; - case "Condition": - this.DecompileConditionTable(table); - break; - case "Dialog": - this.DecompileDialogTable(table); - break; - case "Directory": - this.DecompileDirectoryTable(table); - break; - case "DrLocator": - this.DecompileDrLocatorTable(table); - break; - case "DuplicateFile": - this.DecompileDuplicateFileTable(table); - break; - case "Environment": - this.DecompileEnvironmentTable(table); - break; - case "Error": - this.DecompileErrorTable(table); - break; - case "EventMapping": - this.DecompileEventMappingTable(table); - break; - case "Extension": - this.DecompileExtensionTable(table); - break; - case "ExternalFiles": - this.DecompileExternalFilesTable(table); - break; - case "FamilyFileRanges": - // handled in FinalizeFamilyFileRangesTable - break; - case "Feature": - this.DecompileFeatureTable(table); - break; - case "FeatureComponents": - this.DecompileFeatureComponentsTable(table); - break; - case "File": - this.DecompileFileTable(table); - break; - case "FileSFPCatalog": - this.DecompileFileSFPCatalogTable(table); - break; - case "Font": - this.DecompileFontTable(table); - break; - case "Icon": - this.DecompileIconTable(table); - break; - case "ImageFamilies": - this.DecompileImageFamiliesTable(table); - break; - case "IniFile": - this.DecompileIniFileTable(table); - break; - case "IniLocator": - this.DecompileIniLocatorTable(table); - break; - case "IsolatedComponent": - this.DecompileIsolatedComponentTable(table); - break; - case "LaunchCondition": - this.DecompileLaunchConditionTable(table); - break; - case "ListBox": - this.DecompileListBoxTable(table); - break; - case "ListView": - this.DecompileListViewTable(table); - break; - case "LockPermissions": - this.DecompileLockPermissionsTable(table); - break; - case "Media": - this.DecompileMediaTable(table); - break; - case "MIME": - this.DecompileMIMETable(table); - break; - case "ModuleAdvtUISequence": - this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); - break; - case "ModuleComponents": - // handled by DecompileComponentTable (since the ModuleComponents table - // rows are created by nesting components under the Module element) - break; - case "ModuleConfiguration": - this.DecompileModuleConfigurationTable(table); - break; - case "ModuleDependency": - this.DecompileModuleDependencyTable(table); - break; - case "ModuleExclusion": - this.DecompileModuleExclusionTable(table); - break; - case "ModuleIgnoreTable": - this.DecompileModuleIgnoreTableTable(table); - break; - case "ModuleSignature": - this.DecompileModuleSignatureTable(table); - break; - case "ModuleSubstitution": - this.DecompileModuleSubstitutionTable(table); - break; - case "MoveFile": - this.DecompileMoveFileTable(table); - break; - case "MsiAssembly": - // handled in FinalizeFileTable - break; - case "MsiDigitalCertificate": - this.DecompileMsiDigitalCertificateTable(table); - break; - case "MsiDigitalSignature": - this.DecompileMsiDigitalSignatureTable(table); - break; - case "MsiEmbeddedChainer": - this.DecompileMsiEmbeddedChainerTable(table); - break; - case "MsiEmbeddedUI": - this.DecompileMsiEmbeddedUITable(table); - break; - case "MsiLockPermissionsEx": - this.DecompileMsiLockPermissionsExTable(table); - break; - case "MsiPackageCertificate": - this.DecompileMsiPackageCertificateTable(table); - break; - case "MsiPatchCertificate": - this.DecompileMsiPatchCertificateTable(table); - break; - case "MsiShortcutProperty": - this.DecompileMsiShortcutPropertyTable(table); - break; - case "ODBCAttribute": - this.DecompileODBCAttributeTable(table); - break; - case "ODBCDataSource": - this.DecompileODBCDataSourceTable(table); - break; - case "ODBCDriver": - this.DecompileODBCDriverTable(table); - break; - case "ODBCSourceAttribute": - this.DecompileODBCSourceAttributeTable(table); - break; - case "ODBCTranslator": - this.DecompileODBCTranslatorTable(table); - break; - case "PatchMetadata": - this.DecompilePatchMetadataTable(table); - break; - case "PatchSequence": - this.DecompilePatchSequenceTable(table); - break; - case "ProgId": - this.DecompileProgIdTable(table); - break; - case "Properties": - this.DecompilePropertiesTable(table); - break; - case "Property": - this.DecompilePropertyTable(table); - break; - case "PublishComponent": - this.DecompilePublishComponentTable(table); - break; - case "RadioButton": - this.DecompileRadioButtonTable(table); - break; - case "Registry": - this.DecompileRegistryTable(table); - break; - case "RegLocator": - this.DecompileRegLocatorTable(table); - break; - case "RemoveFile": - this.DecompileRemoveFileTable(table); - break; - case "RemoveIniFile": - this.DecompileRemoveIniFileTable(table); - break; - case "RemoveRegistry": - this.DecompileRemoveRegistryTable(table); - break; - case "ReserveCost": - this.DecompileReserveCostTable(table); - break; - case "SelfReg": - this.DecompileSelfRegTable(table); - break; - case "ServiceControl": - this.DecompileServiceControlTable(table); - break; - case "ServiceInstall": - this.DecompileServiceInstallTable(table); - break; - case "SFPCatalog": - this.DecompileSFPCatalogTable(table); - break; - case "Shortcut": - this.DecompileShortcutTable(table); - break; - case "Signature": - this.DecompileSignatureTable(table); - break; - case "TargetFiles_OptionalData": - this.DecompileTargetFiles_OptionalDataTable(table); - break; - case "TargetImages": - this.DecompileTargetImagesTable(table); - break; - case "TextStyle": - this.DecompileTextStyleTable(table); - break; - case "TypeLib": - this.DecompileTypeLibTable(table); - break; - case "Upgrade": - this.DecompileUpgradeTable(table); - break; - case "UpgradedFiles_OptionalData": - this.DecompileUpgradedFiles_OptionalDataTable(table); - break; - case "UpgradedFilesToIgnore": - this.DecompileUpgradedFilesToIgnoreTable(table); - break; - case "UpgradedImages": - this.DecompileUpgradedImagesTable(table); - break; - case "UIText": - this.DecompileUITextTable(table); - break; - case "Verb": - this.DecompileVerbTable(table); - break; + case "_SummaryInformation": + this.Decompile_SummaryInformationTable(table); + break; + case "AdminExecuteSequence": + case "AdminUISequence": + case "AdvtExecuteSequence": + case "InstallExecuteSequence": + case "InstallUISequence": + case "ModuleAdminExecuteSequence": + case "ModuleAdminUISequence": + case "ModuleAdvtExecuteSequence": + case "ModuleInstallExecuteSequence": + case "ModuleInstallUISequence": + // handled in FinalizeSequenceTables + break; + case "ActionText": + this.DecompileActionTextTable(table); + break; + case "AdvtUISequence": + this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); + break; + case "AppId": + this.DecompileAppIdTable(table); + break; + case "AppSearch": + // handled in FinalizeSearchTables + break; + case "BBControl": + this.DecompileBBControlTable(table); + break; + case "Billboard": + this.DecompileBillboardTable(table); + break; + case "Binary": + this.DecompileBinaryTable(table); + break; + case "BindImage": + this.DecompileBindImageTable(table); + break; + case "CCPSearch": + // handled in FinalizeSearchTables + break; + case "CheckBox": + // handled in FinalizeCheckBoxTable + break; + case "Class": + this.DecompileClassTable(table); + break; + case "ComboBox": + this.DecompileComboBoxTable(table); + break; + case "Control": + this.DecompileControlTable(table); + break; + case "ControlCondition": + this.DecompileControlConditionTable(table); + break; + case "ControlEvent": + this.DecompileControlEventTable(table); + break; + case "CreateFolder": + this.DecompileCreateFolderTable(table); + break; + case "CustomAction": + this.DecompileCustomActionTable(table); + break; + case "CompLocator": + this.DecompileCompLocatorTable(table); + break; + case "Complus": + this.DecompileComplusTable(table); + break; + case "Component": + this.DecompileComponentTable(table); + break; + case "Condition": + this.DecompileConditionTable(table); + break; + case "Dialog": + this.DecompileDialogTable(table); + break; + case "Directory": + this.DecompileDirectoryTable(table); + break; + case "DrLocator": + this.DecompileDrLocatorTable(table); + break; + case "DuplicateFile": + this.DecompileDuplicateFileTable(table); + break; + case "Environment": + this.DecompileEnvironmentTable(table); + break; + case "Error": + this.DecompileErrorTable(table); + break; + case "EventMapping": + this.DecompileEventMappingTable(table); + break; + case "Extension": + this.DecompileExtensionTable(table); + break; + case "ExternalFiles": + this.DecompileExternalFilesTable(table); + break; + case "FamilyFileRanges": + // handled in FinalizeFamilyFileRangesTable + break; + case "Feature": + this.DecompileFeatureTable(table); + break; + case "FeatureComponents": + this.DecompileFeatureComponentsTable(table); + break; + case "File": + this.DecompileFileTable(table); + break; + case "FileSFPCatalog": + this.DecompileFileSFPCatalogTable(table); + break; + case "Font": + this.DecompileFontTable(table); + break; + case "Icon": + this.DecompileIconTable(table); + break; + case "ImageFamilies": + this.DecompileImageFamiliesTable(table); + break; + case "IniFile": + this.DecompileIniFileTable(table); + break; + case "IniLocator": + this.DecompileIniLocatorTable(table); + break; + case "IsolatedComponent": + this.DecompileIsolatedComponentTable(table); + break; + case "LaunchCondition": + this.DecompileLaunchConditionTable(table); + break; + case "ListBox": + this.DecompileListBoxTable(table); + break; + case "ListView": + this.DecompileListViewTable(table); + break; + case "LockPermissions": + this.DecompileLockPermissionsTable(table); + break; + case "Media": + this.DecompileMediaTable(table); + break; + case "MIME": + this.DecompileMIMETable(table); + break; + case "ModuleAdvtUISequence": + this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); + break; + case "ModuleComponents": + // handled by DecompileComponentTable (since the ModuleComponents table + // rows are created by nesting components under the Module element) + break; + case "ModuleConfiguration": + this.DecompileModuleConfigurationTable(table); + break; + case "ModuleDependency": + this.DecompileModuleDependencyTable(table); + break; + case "ModuleExclusion": + this.DecompileModuleExclusionTable(table); + break; + case "ModuleIgnoreTable": + this.DecompileModuleIgnoreTableTable(table); + break; + case "ModuleSignature": + this.DecompileModuleSignatureTable(table); + break; + case "ModuleSubstitution": + this.DecompileModuleSubstitutionTable(table); + break; + case "MoveFile": + this.DecompileMoveFileTable(table); + break; + case "MsiAssembly": + // handled in FinalizeFileTable + break; + case "MsiDigitalCertificate": + this.DecompileMsiDigitalCertificateTable(table); + break; + case "MsiDigitalSignature": + this.DecompileMsiDigitalSignatureTable(table); + break; + case "MsiEmbeddedChainer": + this.DecompileMsiEmbeddedChainerTable(table); + break; + case "MsiEmbeddedUI": + this.DecompileMsiEmbeddedUITable(table); + break; + case "MsiLockPermissionsEx": + this.DecompileMsiLockPermissionsExTable(table); + break; + case "MsiPackageCertificate": + this.DecompileMsiPackageCertificateTable(table); + break; + case "MsiPatchCertificate": + this.DecompileMsiPatchCertificateTable(table); + break; + case "MsiShortcutProperty": + this.DecompileMsiShortcutPropertyTable(table); + break; + case "ODBCAttribute": + this.DecompileODBCAttributeTable(table); + break; + case "ODBCDataSource": + this.DecompileODBCDataSourceTable(table); + break; + case "ODBCDriver": + this.DecompileODBCDriverTable(table); + break; + case "ODBCSourceAttribute": + this.DecompileODBCSourceAttributeTable(table); + break; + case "ODBCTranslator": + this.DecompileODBCTranslatorTable(table); + break; + case "PatchMetadata": + this.DecompilePatchMetadataTable(table); + break; + case "PatchSequence": + this.DecompilePatchSequenceTable(table); + break; + case "ProgId": + this.DecompileProgIdTable(table); + break; + case "Properties": + this.DecompilePropertiesTable(table); + break; + case "Property": + this.DecompilePropertyTable(table); + break; + case "PublishComponent": + this.DecompilePublishComponentTable(table); + break; + case "RadioButton": + this.DecompileRadioButtonTable(table); + break; + case "Registry": + this.DecompileRegistryTable(table); + break; + case "RegLocator": + this.DecompileRegLocatorTable(table); + break; + case "RemoveFile": + this.DecompileRemoveFileTable(table); + break; + case "RemoveIniFile": + this.DecompileRemoveIniFileTable(table); + break; + case "RemoveRegistry": + this.DecompileRemoveRegistryTable(table); + break; + case "ReserveCost": + this.DecompileReserveCostTable(table); + break; + case "SelfReg": + this.DecompileSelfRegTable(table); + break; + case "ServiceControl": + this.DecompileServiceControlTable(table); + break; + case "ServiceInstall": + this.DecompileServiceInstallTable(table); + break; + case "SFPCatalog": + this.DecompileSFPCatalogTable(table); + break; + case "Shortcut": + this.DecompileShortcutTable(table); + break; + case "Signature": + this.DecompileSignatureTable(table); + break; + case "TargetFiles_OptionalData": + this.DecompileTargetFiles_OptionalDataTable(table); + break; + case "TargetImages": + this.DecompileTargetImagesTable(table); + break; + case "TextStyle": + this.DecompileTextStyleTable(table); + break; + case "TypeLib": + this.DecompileTypeLibTable(table); + break; + case "Upgrade": + this.DecompileUpgradeTable(table); + break; + case "UpgradedFiles_OptionalData": + this.DecompileUpgradedFiles_OptionalDataTable(table); + break; + case "UpgradedFilesToIgnore": + this.DecompileUpgradedFilesToIgnoreTable(table); + break; + case "UpgradedImages": + this.DecompileUpgradedImagesTable(table); + break; + case "UIText": + this.DecompileUITextTable(table); + break; + case "Verb": + this.DecompileVerbTable(table); + break; - default: + default: #if TODO_DECOMPILER_EXTENSIONS if (this.ExtensionsByTableName.TryGetValue(table.Name, out var extension) { @@ -3423,11 +2852,11 @@ namespace WixToolset.Core.WindowsInstaller } else #endif - if (!this.SuppressCustomTables) - { - this.DecompileCustomTable(table); - } - break; + if (!this.SuppressCustomTables) + { + this.DecompileCustomTable(table); + } + break; } } } @@ -3442,87 +2871,87 @@ namespace WixToolset.Core.WindowsInstaller { switch (tableName) { - case "ActionText": - case "BBControl": - case "Billboard": - case "CheckBox": - case "Control": - case "ControlCondition": - case "ControlEvent": - case "Dialog": - case "Error": - case "EventMapping": - case "RadioButton": - case "TextStyle": - case "UIText": - return !this.SuppressUI; - case "ModuleAdminExecuteSequence": - case "ModuleAdminUISequence": - case "ModuleAdvtExecuteSequence": - case "ModuleAdvtUISequence": - case "ModuleComponents": - case "ModuleConfiguration": - case "ModuleDependency": - case "ModuleIgnoreTable": - case "ModuleInstallExecuteSequence": - case "ModuleInstallUISequence": - case "ModuleExclusion": - case "ModuleSignature": - case "ModuleSubstitution": - if (OutputType.Module != output.Type) - { - this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName)); - return false; - } - else - { - return true; - } - case "ExternalFiles": - case "FamilyFileRanges": - case "ImageFamilies": - case "PatchMetadata": - case "PatchSequence": - case "Properties": - case "TargetFiles_OptionalData": - case "TargetImages": - case "UpgradedFiles_OptionalData": - case "UpgradedFilesToIgnore": - case "UpgradedImages": - if (OutputType.PatchCreation != output.Type) - { - this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName)); + case "ActionText": + case "BBControl": + case "Billboard": + case "CheckBox": + case "Control": + case "ControlCondition": + case "ControlEvent": + case "Dialog": + case "Error": + case "EventMapping": + case "RadioButton": + case "TextStyle": + case "UIText": + return !this.SuppressUI; + case "ModuleAdminExecuteSequence": + case "ModuleAdminUISequence": + case "ModuleAdvtExecuteSequence": + case "ModuleAdvtUISequence": + case "ModuleComponents": + case "ModuleConfiguration": + case "ModuleDependency": + case "ModuleIgnoreTable": + case "ModuleInstallExecuteSequence": + case "ModuleInstallUISequence": + case "ModuleExclusion": + case "ModuleSignature": + case "ModuleSubstitution": + if (OutputType.Module != output.Type) + { + this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + case "ExternalFiles": + case "FamilyFileRanges": + case "ImageFamilies": + case "PatchMetadata": + case "PatchSequence": + case "Properties": + case "TargetFiles_OptionalData": + case "TargetImages": + case "UpgradedFiles_OptionalData": + case "UpgradedFilesToIgnore": + case "UpgradedImages": + if (OutputType.PatchCreation != output.Type) + { + this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + case "MsiPatchHeaders": + case "MsiPatchMetadata": + case "MsiPatchOldAssemblyName": + case "MsiPatchOldAssemblyFile": + case "MsiPatchSequence": + case "Patch": + case "PatchPackage": + this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName)); return false; - } - else - { + case "_SummaryInformation": return true; - } - case "MsiPatchHeaders": - case "MsiPatchMetadata": - case "MsiPatchOldAssemblyName": - case "MsiPatchOldAssemblyFile": - case "MsiPatchSequence": - case "Patch": - case "PatchPackage": - this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName)); - return false; - case "_SummaryInformation": - return true; - case "_Validation": - case "MsiAssemblyName": - case "MsiFileHash": - return false; - default: // all other tables are allowed in any output except for a patch creation package - if (OutputType.PatchCreation == output.Type) - { - this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName)); + case "_Validation": + case "MsiAssemblyName": + case "MsiFileHash": return false; - } - else - { - return true; - } + default: // all other tables are allowed in any output except for a patch creation package + if (OutputType.PatchCreation == output.Type) + { + this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } } } @@ -3534,200 +2963,177 @@ namespace WixToolset.Core.WindowsInstaller { if (OutputType.Module == this.OutputType || OutputType.Product == this.OutputType) { - var package = new Wix.Package(); + var xPackage = new XElement(Names.PackageElement); foreach (var row in table.Rows) { - var value = Convert.ToString(row[1]); + var value = row.FieldAsString(1); - if (null != value && 0 < value.Length) + if (!String.IsNullOrEmpty(value)) { - switch (Convert.ToInt32(row[0])) + switch (row.FieldAsInteger(0)) { - case 1: - if ("1252" != value) - { - package.SummaryCodepage = value; - } - break; - case 3: - package.Description = value; - break; - case 4: - package.Manufacturer = value; - break; - case 5: - if ("Installer" != value) - { - package.Keywords = value; - } - break; - case 6: - if (!value.StartsWith("This installer database contains the logic and data required to install ")) - { - package.Comments = value; - } - break; - case 7: - var template = value.Split(';'); - if (0 < template.Length && 0 < template[template.Length - 1].Length) - { - package.Languages = template[template.Length - 1]; - } - - if (1 < template.Length && null != template[0] && 0 < template[0].Length) - { - switch (template[0]) + case 1: + if ("1252" != value) { - case "Intel": - package.Platform = WixToolset.Data.Serialize.Package.PlatformType.x86; - break; - case "Intel64": - package.Platform = WixToolset.Data.Serialize.Package.PlatformType.ia64; - break; - case "x64": - package.Platform = WixToolset.Data.Serialize.Package.PlatformType.x64; - break; + xPackage.SetAttributeValue("SummaryCodepage", value); + } + break; + case 3: + xPackage.SetAttributeValue("Description", value); + break; + case 4: + xPackage.SetAttributeValue("Manufacturer", value); + break; + case 5: + if ("Installer" != value) + { + xPackage.SetAttributeValue("Keywords", value); + } + break; + case 6: + if (!value.StartsWith("This installer database contains the logic and data required to install ")) + { + xPackage.SetAttributeValue("Comments", value); + } + break; + case 7: + var template = value.Split(';'); + if (0 < template.Length && 0 < template[template.Length - 1].Length) + { + xPackage.SetAttributeValue("Languages", template[template.Length - 1]); } - } - break; - case 9: - if (OutputType.Module == this.OutputType) - { - this.modularizationGuid = value; - package.Id = value; - } - break; - case 14: - package.InstallerVersion = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); - break; - case 15: - var wordCount = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); - if (0x1 == (wordCount & 0x1)) - { - this.shortNames = true; - package.ShortNames = Wix.YesNoType.yes; - } - if (0x2 == (wordCount & 0x2)) - { - this.compressed = true; + if (1 < template.Length && null != template[0] && 0 < template[0].Length) + { + switch (template[0]) + { + case "Intel": + xPackage.SetAttributeValue("Platform", "x86"); + break; + case "Intel64": + xPackage.SetAttributeValue("Platform", "ia64"); + break; + case "x64": + xPackage.SetAttributeValue("Platform", "x64"); + break; + case "Arm": + xPackage.SetAttributeValue("Platform", "arm"); + break; + case "Arm64": + xPackage.SetAttributeValue("Platform", "arm64"); + break; + } + } + break; + case 9: + if (OutputType.Module == this.OutputType) + { + this.ModularizationGuid = value; + xPackage.SetAttributeValue("Id", value); + } + break; + case 14: + xPackage.SetAttributeValue("InstallerVersion", row.FieldAsInteger(1)); + break; + case 15: + var wordCount = row.FieldAsInteger(1); + if (0x1 == (wordCount & 0x1)) + { + this.ShortNames = true; + xPackage.SetAttributeValue("ShortNames", "yes"); + } - if (OutputType.Product == this.OutputType) + if (0x2 == (wordCount & 0x2)) { - package.Compressed = Wix.YesNoType.yes; + this.Compressed = true; + + if (OutputType.Product == this.OutputType) + { + xPackage.SetAttributeValue("Compressed", "yes"); + } } - } - if (0x4 == (wordCount & 0x4)) - { - package.AdminImage = Wix.YesNoType.yes; - } + if (0x4 == (wordCount & 0x4)) + { + xPackage.SetAttributeValue("AdminImage", "yes"); + } - if (0x8 == (wordCount & 0x8)) - { - package.InstallPrivileges = Wix.Package.InstallPrivilegesType.limited; - } + if (0x8 == (wordCount & 0x8)) + { + xPackage.SetAttributeValue("InstallPrivileges", "limited"); + } - break; - case 19: - var security = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); - switch (security) - { - case 0: - package.ReadOnly = Wix.YesNoDefaultType.no; break; - case 4: - package.ReadOnly = Wix.YesNoDefaultType.yes; + case 19: + var security = row.FieldAsInteger(1); + switch (security) + { + case 0: + xPackage.SetAttributeValue("ReadOnly", "no"); + break; + case 4: + xPackage.SetAttributeValue("ReadOnly", "yes"); + break; + } break; - } - break; } } } - this.core.RootElement.AddChild(package); + this.RootElement.Add(xPackage); } else { - var patchInformation = new Wix.PatchInformation(); + var xPatchInformation = new XElement(Names.PatchInformationElement); foreach (var row in table.Rows) { - var propertyId = Convert.ToInt32(row[0]); - var value = Convert.ToString(row[1]); + var propertyId = row.FieldAsInteger(0); + var value = row.FieldAsString(1); - if (null != row[1] && 0 < value.Length) + if (!String.IsNullOrEmpty(value)) { switch (propertyId) { - case 1: - if ("1252" != value) - { - patchInformation.SummaryCodepage = value; - } - break; - case 3: - patchInformation.Description = value; - break; - case 4: - patchInformation.Manufacturer = value; - break; - case 5: - if ("Installer,Patching,PCP,Database" != value) - { - patchInformation.Keywords = value; - } - break; - case 6: - patchInformation.Comments = value; - break; - case 7: - var template = value.Split(';'); - if (0 < template.Length && 0 < template[template.Length - 1].Length) - { - patchInformation.Languages = template[template.Length - 1]; - } - - if (1 < template.Length && null != template[0] && 0 < template[0].Length) - { - patchInformation.Platforms = template[0]; - } - break; - case 15: - var wordCount = Convert.ToInt32(value, CultureInfo.InvariantCulture); - if (0x1 == (wordCount & 0x1)) - { - patchInformation.ShortNames = Wix.YesNoType.yes; - } - - if (0x2 == (wordCount & 0x2)) - { - patchInformation.Compressed = Wix.YesNoType.yes; - } - - if (0x4 == (wordCount & 0x4)) - { - patchInformation.AdminImage = Wix.YesNoType.yes; - } - break; - case 19: - var security = Convert.ToInt32(value, CultureInfo.InvariantCulture); - switch (security) - { - case 0: - patchInformation.ReadOnly = Wix.YesNoDefaultType.no; + case 1: + if ("1252" != value) + { + xPatchInformation.SetAttributeValue("SummaryCodepage", value); + } + break; + case 3: + xPatchInformation.SetAttributeValue("Description", value); break; case 4: - patchInformation.ReadOnly = Wix.YesNoDefaultType.yes; + xPatchInformation.SetAttributeValue("Manufacturer", value); + break; + case 5: + if ("Installer,Patching,PCP,Database" != value) + { + xPatchInformation.SetAttributeValue("Keywords", value); + } + break; + case 6: + xPatchInformation.SetAttributeValue("Comments", value); + break; + case 19: + var security = Convert.ToInt32(value, CultureInfo.InvariantCulture); + switch (security) + { + case 0: + xPatchInformation.SetAttributeValue("ReadOnly", "no"); + break; + case 4: + xPatchInformation.SetAttributeValue("ReadOnly", "yes"); + break; + } break; - } - break; } } } - this.core.RootElement.AddChild(patchInformation); + this.RootElement.Add(xPatchInformation); } } @@ -3739,21 +3145,12 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var progressText = new Wix.ProgressText(); + var progressText = new XElement(Names.ProgressTextElement, + new XAttribute("Action", row.FieldAsString(0)), + row.IsColumnNull(1) ? null : new XAttribute("Content", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("Template", row.FieldAsString(2))); - progressText.Action = Convert.ToString(row[0]); - - if (null != row[1]) - { - progressText.Content = Convert.ToString(row[1]); - } - - if (null != row[2]) - { - progressText.Template = Convert.ToString(row[2]); - } - - this.core.UIElement.AddChild(progressText); + this.UIElement.Add(progressText); } } @@ -3765,44 +3162,18 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var appId = new Wix.AppId(); - - appId.Advertise = Wix.YesNoType.yes; - - appId.Id = Convert.ToString(row[0]); - - if (null != row[1]) - { - appId.RemoteServerName = Convert.ToString(row[1]); - } - - if (null != row[2]) - { - appId.LocalService = Convert.ToString(row[2]); - } + var appId = new XElement(Names.AppIdElement, + new XAttribute("Advertise", "yes"), + new XAttribute("Id", row.FieldAsString(0)), + row.IsColumnNull(1) ? null : new XAttribute("RemoteServerName", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("LocalService", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("ServiceParameters", row.FieldAsString(3)), + row.IsColumnNull(4) ? null : new XAttribute("DllSurrogate", row.FieldAsString(4)), + row.IsColumnNull(5) || row.FieldAsInteger(5) != 1 ? null : new XAttribute("ActivateAtStorage", "yes"), + row.IsColumnNull(6) || row.FieldAsInteger(6) != 1 ? null : new XAttribute("RunAsInteractiveUser", "yes")); - if (null != row[3]) - { - appId.ServiceParameters = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - appId.DllSurrogate = Convert.ToString(row[4]); - } - - if (null != row[5] && Int32.Equals(row[5], 1)) - { - appId.ActivateAtStorage = Wix.YesNoType.yes; - } - - if (null != row[6] && Int32.Equals(row[6], 1)) - { - appId.RunAsInteractiveUser = Wix.YesNoType.yes; - } - - this.core.RootElement.AddChild(appId); - this.core.IndexElement(row, appId); + this.RootElement.Add(appId); + this.IndexElement(row, appId); } } @@ -3814,34 +3185,23 @@ namespace WixToolset.Core.WindowsInstaller { foreach (BBControlRow bbControlRow in table.Rows) { - var control = new Wix.Control(); - - control.Id = bbControlRow.BBControl; - - control.Type = bbControlRow.Type; - - control.X = bbControlRow.X; - - control.Y = bbControlRow.Y; - - control.Width = bbControlRow.Width; - - control.Height = bbControlRow.Height; + var xControl = new XElement(Names.ControlElement, + new XAttribute("Id", bbControlRow.BBControl), + new XAttribute("Type", bbControlRow.Type), + new XAttribute("X", bbControlRow.X), + new XAttribute("Y", bbControlRow.Y), + new XAttribute("Width", bbControlRow.Width), + new XAttribute("Height", bbControlRow.Height), + null == bbControlRow.Text ? null : new XAttribute("Text", bbControlRow.Text)); if (null != bbControlRow[7]) { - SetControlAttributes(bbControlRow.Attributes, control); + SetControlAttributes(bbControlRow.Attributes, xControl); } - if (null != bbControlRow.Text) + if (this.TryGetIndexedElement("Billboard", out var xBillboard, bbControlRow.Billboard)) { - control.Text = bbControlRow.Text; - } - - var billboard = (Wix.Billboard)this.core.GetIndexedElement("Billboard", bbControlRow.Billboard); - if (null != billboard) - { - billboard.AddChild(control); + xBillboard.Add(xControl); } else { @@ -3856,37 +3216,34 @@ namespace WixToolset.Core.WindowsInstaller /// The table to decompile. private void DecompileBillboardTable(Table table) { - var billboardActions = new Hashtable(); - var billboards = new SortedList(); + var billboards = new SortedList(); foreach (var row in table.Rows) - { - var billboard = new Wix.Billboard(); - - billboard.Id = Convert.ToString(row[0]); - - billboard.Feature = Convert.ToString(row[1]); + { + var xBillboard = new XElement(Names.BillboardElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Feature", row.FieldAsString(1))); - this.core.IndexElement(row, billboard); + this.IndexElement(row, xBillboard); billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row); } - foreach (Row row in billboards.Values) + var billboardActions = new Dictionary(); + + foreach (var row in billboards.Values) { - var billboard = (Wix.Billboard)this.core.GetIndexedElement(row); - var billboardAction = (Wix.BillboardAction)billboardActions[row[2]]; + var xBillboard = this.GetIndexedElement(row); - if (null == billboardAction) + if (!billboardActions.TryGetValue(row.FieldAsString(2), out var xBillboardAction)) { - billboardAction = new Wix.BillboardAction(); + xBillboardAction = new XElement(Names.BillboardActionElement, + new XAttribute("Id", row.FieldAsString(2))); - billboardAction.Id = Convert.ToString(row[2]); - - this.core.UIElement.AddChild(billboardAction); - billboardActions.Add(row[2], billboardAction); + this.UIElement.Add(xBillboardAction); + billboardActions.Add(row.FieldAsString(2), xBillboardAction); } - billboardAction.AddChild(billboard); + xBillboardAction.Add(xBillboard); } } @@ -3898,13 +3255,11 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var binary = new Wix.Binary(); - - binary.Id = Convert.ToString(row[0]); + var xBinary = new XElement(Names.BinaryElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); - binary.SourceFile = Convert.ToString(row[1]); - - this.core.RootElement.AddChild(binary); + this.RootElement.Add(xBinary); } } @@ -3916,15 +3271,13 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); - - if (null != file) + if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) { - file.BindPath = Convert.ToString(row[1]); + xFile.SetAttributeValue("BindPath", row.FieldAsString(1)); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); } } } @@ -3937,46 +3290,20 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var wixClass = new Wix.Class(); - - wixClass.Advertise = Wix.YesNoType.yes; - - wixClass.Id = Convert.ToString(row[0]); - - switch (Convert.ToString(row[1])) - { - case "LocalServer": - wixClass.Context = Wix.Class.ContextType.LocalServer; - break; - case "LocalServer32": - wixClass.Context = Wix.Class.ContextType.LocalServer32; - break; - case "InprocServer": - wixClass.Context = Wix.Class.ContextType.InprocServer; - break; - case "InprocServer32": - wixClass.Context = Wix.Class.ContextType.InprocServer32; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - // ProgId children are handled in FinalizeProgIdTable - - if (null != row[4]) - { - wixClass.Description = Convert.ToString(row[4]); - } - - if (null != row[5]) - { - wixClass.AppId = Convert.ToString(row[5]); - } + var xClass = new XElement(Names.ClassElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Advertise", "yes"), + new XAttribute("Context", row.FieldAsString(1)), + row.IsColumnNull(4) ? null : new XAttribute("Description", row.FieldAsString(4)), + row.IsColumnNull(5) ? null : new XAttribute("AppId", row.FieldAsString(5)), + row.IsColumnNull(7) ? null : new XAttribute("Icon", row.FieldAsString(7)), + row.IsColumnNull(8) ? null : new XAttribute("IconIndex", row.FieldAsString(8)), + row.IsColumnNull(9) ? null : new XAttribute("Handler", row.FieldAsString(9)), + row.IsColumnNull(10) ? null : new XAttribute("Argument", row.FieldAsString(10))); - if (null != row[6]) + if (!row.IsColumnNull(6)) { - var fileTypeMaskStrings = (Convert.ToString(row[6])).Split(';'); + var fileTypeMaskStrings = row.FieldAsString(6).Split(';'); try { @@ -3986,15 +3313,12 @@ namespace WixToolset.Core.WindowsInstaller if (4 == fileTypeMaskParts.Length) { - var fileTypeMask = new Wix.FileTypeMask(); + var xFileTypeMask = new XElement(Names.FileTypeMaskElement, + new XAttribute("Offset", Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture)), + new XAttribute("Mask", fileTypeMaskParts[2]), + new XAttribute("Value", fileTypeMaskParts[3])); - fileTypeMask.Offset = Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture); - - fileTypeMask.Mask = fileTypeMaskParts[2]; - - fileTypeMask.Value = fileTypeMaskParts[3]; - - wixClass.AddChild(fileTypeMask); + xClass.Add(xFileTypeMask); } else { @@ -4012,31 +3336,11 @@ namespace WixToolset.Core.WindowsInstaller } } - if (null != row[7]) - { - wixClass.Icon = Convert.ToString(row[7]); - } - - if (null != row[8]) - { - wixClass.IconIndex = Convert.ToInt32(row[8]); - } - - if (null != row[9]) - { - wixClass.Handler = Convert.ToString(row[9]); - } - - if (null != row[10]) + if (!row.IsColumnNull(12)) { - wixClass.Argument = Convert.ToString(row[10]); - } - - if (null != row[12]) - { - if (1 == Convert.ToInt32(row[12])) + if (1 == row.FieldAsInteger(12)) { - wixClass.RelativePath = Wix.YesNoType.yes; + xClass.SetAttributeValue("RelativePath", "yes"); } else { @@ -4044,17 +3348,8 @@ namespace WixToolset.Core.WindowsInstaller } } - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2])); - if (null != component) - { - component.AddChild(wixClass); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[2]), "Component")); - } - - this.core.IndexElement(row, wixClass); + this.AddChildToParent("Component", xClass, row, 2); + this.IndexElement(row, xClass); } } @@ -4064,36 +3359,27 @@ namespace WixToolset.Core.WindowsInstaller /// The table to decompile. private void DecompileComboBoxTable(Table table) { - Wix.ComboBox comboBox = null; - var comboBoxRows = new SortedList(); - // sort the combo boxes by their property and order - foreach (var row in table.Rows) - { - comboBoxRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); - } + var comboBoxRows = table.Rows.Select(row => row).OrderBy(row => String.Format("{0}|{1:0000000000}", row.FieldAsString(0), row.FieldAsInteger(1))); - foreach (Row row in comboBoxRows.Values) + XElement xComboBox = null; + string property = null; + foreach (var row in comboBoxRows) { - if (null == comboBox || Convert.ToString(row[0]) != comboBox.Property) + if (null == xComboBox || row.FieldAsString(0) != property) { - comboBox = new Wix.ComboBox(); - - comboBox.Property = Convert.ToString(row[0]); - - this.core.UIElement.AddChild(comboBox); - } + property = row.FieldAsString(0); - var listItem = new Wix.ListItem(); + xComboBox = new XElement(Names.ComboBoxElement, + new XAttribute("Property", property)); - listItem.Value = Convert.ToString(row[2]); - - if (null != row[3]) - { - listItem.Text = Convert.ToString(row[3]); + this.UIElement.Add(xComboBox); } - comboBox.AddChild(listItem); + var xListItem = new XElement(Names.ListItemElement, + new XAttribute("Value", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); + xComboBox.Add(xListItem); } } @@ -4105,80 +3391,75 @@ namespace WixToolset.Core.WindowsInstaller { foreach (ControlRow controlRow in table.Rows) { - var control = new Wix.Control(); - - control.Id = controlRow.Control; - - control.Type = controlRow.Type; + var xControl = new XElement(Names.ControlElement, + new XAttribute("Id", controlRow.Control), + new XAttribute("Type", controlRow.Type), + new XAttribute("X", controlRow.X), + new XAttribute("Y", controlRow.Y), + new XAttribute("Width", controlRow.Width), + new XAttribute("Height", controlRow.Height), + new XAttribute("Text", controlRow.Text)); - control.X = controlRow.X; - - control.Y = controlRow.Y; - - control.Width = controlRow.Width; - - control.Height = controlRow.Height; - - if (null != controlRow[7]) + if (!controlRow.IsColumnNull(7)) { string[] specialAttributes; // sets various common attributes like Disabled, Indirect, Integer, ... - SetControlAttributes(controlRow.Attributes, control); + SetControlAttributes(controlRow.Attributes, xControl); - switch (control.Type) + switch (controlRow.Type) { - case "Bitmap": - specialAttributes = BitmapControlAttributes; - break; - case "CheckBox": - specialAttributes = CheckboxControlAttributes; - break; - case "ComboBox": - specialAttributes = ComboboxControlAttributes; - break; - case "DirectoryCombo": - specialAttributes = VolumeControlAttributes; - break; - case "Edit": - specialAttributes = EditControlAttributes; - break; - case "Icon": - specialAttributes = IconControlAttributes; - break; - case "ListBox": - specialAttributes = ListboxControlAttributes; - break; - case "ListView": - specialAttributes = ListviewControlAttributes; - break; - case "MaskedEdit": - specialAttributes = EditControlAttributes; - break; - case "PathEdit": - specialAttributes = EditControlAttributes; - break; - case "ProgressBar": - specialAttributes = ProgressControlAttributes; - break; - case "PushButton": - specialAttributes = ButtonControlAttributes; - break; - case "RadioButtonGroup": - specialAttributes = RadioControlAttributes; - break; - case "Text": - specialAttributes = TextControlAttributes; - break; - case "VolumeCostList": - specialAttributes = VolumeControlAttributes; - break; - case "VolumeSelectCombo": - specialAttributes = VolumeControlAttributes; - break; - default: - specialAttributes = null; - break; + case "Bitmap": + specialAttributes = BitmapControlAttributes; + break; + case "CheckBox": + specialAttributes = CheckboxControlAttributes; + break; + case "ComboBox": + specialAttributes = ComboboxControlAttributes; + break; + case "DirectoryCombo": + specialAttributes = VolumeControlAttributes; + break; + case "Edit": + specialAttributes = EditControlAttributes; + break; + case "Icon": + specialAttributes = IconControlAttributes; + break; + case "ListBox": + specialAttributes = ListboxControlAttributes; + break; + case "ListView": + specialAttributes = ListviewControlAttributes; + break; + case "MaskedEdit": + specialAttributes = EditControlAttributes; + break; + case "PathEdit": + specialAttributes = EditControlAttributes; + break; + case "ProgressBar": + specialAttributes = ProgressControlAttributes; + break; + case "PushButton": + specialAttributes = ButtonControlAttributes; + break; + case "RadioButtonGroup": + specialAttributes = RadioControlAttributes; + break; + case "Text": + specialAttributes = TextControlAttributes; + break; + case "VolumeCostList": + specialAttributes = VolumeControlAttributes; + break; + case "VolumeSelectCombo": + specialAttributes = VolumeControlAttributes; + break; + default: + specialAttributes = null; + break; } if (null != specialAttributes) @@ -4205,102 +3486,102 @@ namespace WixToolset.Core.WindowsInstaller switch (attribute) { - case "Bitmap": - control.Bitmap = Wix.YesNoType.yes; - break; - case "CDROM": - control.CDROM = Wix.YesNoType.yes; - break; - case "ComboList": - control.ComboList = Wix.YesNoType.yes; - break; - case "ElevationShield": - control.ElevationShield = Wix.YesNoType.yes; - break; - case "Fixed": - control.Fixed = Wix.YesNoType.yes; - break; - case "FixedSize": - control.FixedSize = Wix.YesNoType.yes; - break; - case "Floppy": - control.Floppy = Wix.YesNoType.yes; - break; - case "FormatSize": - control.FormatSize = Wix.YesNoType.yes; - break; - case "HasBorder": - control.HasBorder = Wix.YesNoType.yes; - break; - case "Icon": - control.Icon = Wix.YesNoType.yes; - break; - case "Icon16": - if (iconSizeSet) - { - control.IconSize = Wix.Control.IconSizeType.Item48; - } - else - { - iconSizeSet = true; - control.IconSize = Wix.Control.IconSizeType.Item16; - } - break; - case "Icon32": - if (iconSizeSet) - { - control.IconSize = Wix.Control.IconSizeType.Item48; - } - else - { - iconSizeSet = true; - control.IconSize = Wix.Control.IconSizeType.Item32; - } - break; - case "Image": - control.Image = Wix.YesNoType.yes; - break; - case "Multiline": - control.Multiline = Wix.YesNoType.yes; - break; - case "NoPrefix": - control.NoPrefix = Wix.YesNoType.yes; - break; - case "NoWrap": - control.NoWrap = Wix.YesNoType.yes; - break; - case "Password": - control.Password = Wix.YesNoType.yes; - break; - case "ProgressBlocks": - control.ProgressBlocks = Wix.YesNoType.yes; - break; - case "PushLike": - control.PushLike = Wix.YesNoType.yes; - break; - case "RAMDisk": - control.RAMDisk = Wix.YesNoType.yes; - break; - case "Remote": - control.Remote = Wix.YesNoType.yes; - break; - case "Removable": - control.Removable = Wix.YesNoType.yes; - break; - case "ShowRollbackCost": - control.ShowRollbackCost = Wix.YesNoType.yes; - break; - case "Sorted": - control.Sorted = Wix.YesNoType.yes; - break; - case "Transparent": - control.Transparent = Wix.YesNoType.yes; - break; - case "UserLanguage": - control.UserLanguage = Wix.YesNoType.yes; - break; - default: - throw new InvalidOperationException($"Unknown control attribute: '{attribute}'."); + case "Bitmap": + xControl.SetAttributeValue("Bitmap", "yes"); + break; + case "CDROM": + xControl.SetAttributeValue("CDROM", "yes"); + break; + case "ComboList": + xControl.SetAttributeValue("ComboList", "yes"); + break; + case "ElevationShield": + xControl.SetAttributeValue("ElevationShield", "yes"); + break; + case "Fixed": + xControl.SetAttributeValue("Fixed", "yes"); + break; + case "FixedSize": + xControl.SetAttributeValue("FixedSize", "yes"); + break; + case "Floppy": + xControl.SetAttributeValue("Floppy", "yes"); + break; + case "FormatSize": + xControl.SetAttributeValue("FormatSize", "yes"); + break; + case "HasBorder": + xControl.SetAttributeValue("HasBorder", "yes"); + break; + case "Icon": + xControl.SetAttributeValue("Icon", "yes"); + break; + case "Icon16": + if (iconSizeSet) + { + xControl.SetAttributeValue("IconSize", "48"); + } + else + { + iconSizeSet = true; + xControl.SetAttributeValue("IconSize", "16"); + } + break; + case "Icon32": + if (iconSizeSet) + { + xControl.SetAttributeValue("IconSize", "48"); + } + else + { + iconSizeSet = true; + xControl.SetAttributeValue("IconSize", "32"); + } + break; + case "Image": + xControl.SetAttributeValue("Image", "yes"); + break; + case "Multiline": + xControl.SetAttributeValue("Multiline", "yes"); + break; + case "NoPrefix": + xControl.SetAttributeValue("NoPrefix", "yes"); + break; + case "NoWrap": + xControl.SetAttributeValue("NoWrap", "yes"); + break; + case "Password": + xControl.SetAttributeValue("Password", "yes"); + break; + case "ProgressBlocks": + xControl.SetAttributeValue("ProgressBlocks", "yes"); + break; + case "PushLike": + xControl.SetAttributeValue("PushLike", "yes"); + break; + case "RAMDisk": + xControl.SetAttributeValue("RAMDisk", "yes"); + break; + case "Remote": + xControl.SetAttributeValue("Remote", "yes"); + break; + case "Removable": + xControl.SetAttributeValue("Removable", "yes"); + break; + case "ShowRollbackCost": + xControl.SetAttributeValue("ShowRollbackCost", "yes"); + break; + case "Sorted": + xControl.SetAttributeValue("Sorted", "yes"); + break; + case "Transparent": + xControl.SetAttributeValue("Transparent", "yes"); + break; + case "UserLanguage": + xControl.SetAttributeValue("UserLanguage", "yes"); + break; + default: + throw new InvalidOperationException($"Unknown control attribute: '{attribute}'."); } } } @@ -4312,14 +3593,9 @@ namespace WixToolset.Core.WindowsInstaller } // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef - if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", control.Type)) - { - control.Property = controlRow.Property; - } - - if (null != controlRow.Text) + if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", controlRow.Type)) { - control.Text = controlRow.Text; + xControl.SetAttributeValue("Property", controlRow.Property); } if (null != controlRow.Help) @@ -4330,17 +3606,17 @@ namespace WixToolset.Core.WindowsInstaller { if (0 < help[0].Length) { - control.ToolTip = help[0]; + xControl.SetAttributeValue("ToolTip", help[0]); } if (0 < help[1].Length) { - control.Help = help[1]; + xControl.SetAttributeValue("Help", help[1]); } } } - this.core.IndexElement(controlRow, control); + this.IndexElement(controlRow, xControl); } } @@ -4352,40 +3628,33 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var condition = new Wix.Condition(); - - switch (Convert.ToString(row[2])) - { - case "Default": - condition.Action = Wix.Condition.ActionType.@default; - break; - case "Disable": - condition.Action = Wix.Condition.ActionType.disable; - break; - case "Enable": - condition.Action = Wix.Condition.ActionType.enable; - break; - case "Hide": - condition.Action = Wix.Condition.ActionType.hide; - break; - case "Show": - condition.Action = Wix.Condition.ActionType.show; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - break; - } - - condition.Content = Convert.ToString(row[3]); - - var control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1])); - if (null != control) + if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) { - control.AddChild(condition); + switch (row.FieldAsString(2)) + { + case "Default": + xControl.SetAttributeValue("DefaultCondition", row.FieldAsString(3)); + break; + case "Disable": + xControl.SetAttributeValue("DisableCondition", row.FieldAsString(3)); + break; + case "Enable": + xControl.SetAttributeValue("EnableCondition", row.FieldAsString(3)); + break; + case "Hide": + xControl.SetAttributeValue("HideCondition", row.FieldAsString(3)); + break; + case "Show": + xControl.SetAttributeValue("ShowCondition", row.FieldAsString(3)); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + break; + } } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); } } } @@ -4396,50 +3665,44 @@ namespace WixToolset.Core.WindowsInstaller /// The table to decompile. private void DecompileControlEventTable(Table table) { - var controlEvents = new SortedList(); + var controlEvents = new SortedList(); foreach (var row in table.Rows) { - var publish = new Wix.Publish(); + var xPublish = new XElement(Names.PublishElement, + new XAttribute("Condition", row.FieldAsString(4))); - var publishEvent = Convert.ToString(row[2]); + var publishEvent = row.FieldAsString(2); if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal)) { - publish.Property = publishEvent.Substring(1, publishEvent.Length - 2); + xPublish.SetAttributeValue("Property", publishEvent.Substring(1, publishEvent.Length - 2)); - if ("{}" != Convert.ToString(row[3])) + if ("{}" != row.FieldAsString(3)) { - publish.Value = Convert.ToString(row[3]); + xPublish.SetAttributeValue("Value", row.FieldAsString(3)); } } else { - publish.Event = publishEvent; - publish.Value = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - publish.Content = Convert.ToString(row[4]); + xPublish.SetAttributeValue("Event", publishEvent); + xPublish.SetAttributeValue("Value", row.FieldAsString(3)); } - controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row[0], row[1], (null == row[5] ? 0 : Convert.ToInt32(row[5])), row[2], row[3], row[4]), row); + controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row.FieldAsString(0), row.FieldAsString(1), row.FieldAsNullableInteger(5) ?? 0, row.FieldAsString(2), row.FieldAsString(3), row.FieldAsString(4)), row); - this.core.IndexElement(row, publish); + this.IndexElement(row, xPublish); } - foreach (Row row in controlEvents.Values) + foreach (var row in controlEvents.Values) { - var control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1])); - var publish = (Wix.Publish)this.core.GetIndexedElement(row); - - if (null != control) + if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) { - control.AddChild(publish); + var xPublish = this.GetIndexedElement(row); + xControl.Add(xPublish); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); } } } @@ -4452,221 +3715,180 @@ namespace WixToolset.Core.WindowsInstaller { if (0 < table.Rows.Count || this.SuppressDroppingEmptyTables) { - var customTable = new Wix.CustomTable(); - this.Messaging.Write(WarningMessages.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name)); - customTable.Id = table.Name; - - foreach (var columnDefinition in table.Definition.Columns) - { - var column = new Wix.Column(); - - column.Id = columnDefinition.Name; - - if (ColumnCategory.Unknown != columnDefinition.Category) - { - switch (columnDefinition.Category) - { - case ColumnCategory.Text: - column.Category = Wix.Column.CategoryType.text; - break; - case ColumnCategory.UpperCase: - column.Category = Wix.Column.CategoryType.upperCase; - break; - case ColumnCategory.LowerCase: - column.Category = Wix.Column.CategoryType.lowerCase; - break; - case ColumnCategory.Integer: - column.Category = Wix.Column.CategoryType.integer; - break; - case ColumnCategory.DoubleInteger: - column.Category = Wix.Column.CategoryType.doubleInteger; - break; - case ColumnCategory.TimeDate: - column.Category = Wix.Column.CategoryType.timeDate; - break; - case ColumnCategory.Identifier: - column.Category = Wix.Column.CategoryType.identifier; - break; - case ColumnCategory.Property: - column.Category = Wix.Column.CategoryType.property; - break; - case ColumnCategory.Filename: - column.Category = Wix.Column.CategoryType.filename; - break; - case ColumnCategory.WildCardFilename: - column.Category = Wix.Column.CategoryType.wildCardFilename; - break; - case ColumnCategory.Path: - column.Category = Wix.Column.CategoryType.path; - break; - case ColumnCategory.Paths: - column.Category = Wix.Column.CategoryType.paths; - break; - case ColumnCategory.AnyPath: - column.Category = Wix.Column.CategoryType.anyPath; - break; - case ColumnCategory.DefaultDir: - column.Category = Wix.Column.CategoryType.defaultDir; - break; - case ColumnCategory.RegPath: - column.Category = Wix.Column.CategoryType.regPath; - break; - case ColumnCategory.Formatted: - column.Category = Wix.Column.CategoryType.formatted; - break; - case ColumnCategory.FormattedSDDLText: - column.Category = Wix.Column.CategoryType.formattedSddl; - break; - case ColumnCategory.Template: - column.Category = Wix.Column.CategoryType.template; - break; - case ColumnCategory.Condition: - column.Category = Wix.Column.CategoryType.condition; - break; - case ColumnCategory.Guid: - column.Category = Wix.Column.CategoryType.guid; - break; - case ColumnCategory.Version: - column.Category = Wix.Column.CategoryType.version; - break; - case ColumnCategory.Language: - column.Category = Wix.Column.CategoryType.language; - break; - case ColumnCategory.Binary: - column.Category = Wix.Column.CategoryType.binary; - break; - case ColumnCategory.CustomSource: - column.Category = Wix.Column.CategoryType.customSource; - break; - case ColumnCategory.Cabinet: - column.Category = Wix.Column.CategoryType.cabinet; - break; - case ColumnCategory.Shortcut: - column.Category = Wix.Column.CategoryType.shortcut; - break; - default: - throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'."); - } - } - - if (null != columnDefinition.Description) - { - column.Description = columnDefinition.Description; - } - - if (columnDefinition.KeyColumn.HasValue) - { - column.KeyColumn = columnDefinition.KeyColumn.Value; - } - - if (null != columnDefinition.KeyTable) - { - column.KeyTable = columnDefinition.KeyTable; - } - - if (columnDefinition.IsLocalizable) - { - column.Localizable = Wix.YesNoType.yes; - } - - if (columnDefinition.MaxValue.HasValue) - { - column.MaxValue = columnDefinition.MaxValue.Value; - } + var xCustomTable = new XElement(Names.CustomTableElement, + new XAttribute("Id", table.Name)); - if (columnDefinition.MinValue.HasValue) + foreach (var columnDefinition in table.Definition.Columns) + { + var xColumn = new XElement(Names.ColumnElement, + new XAttribute("Id", columnDefinition.Name), + columnDefinition.Description == null ? null : new XAttribute("Description", columnDefinition.Description), + columnDefinition.KeyTable == null ? null : new XAttribute("KeyTable", columnDefinition.KeyTable), + !columnDefinition.KeyColumn.HasValue ? null : new XAttribute("KeyColumn", columnDefinition.KeyColumn.Value), + !columnDefinition.IsLocalizable ? null : new XAttribute("Localizable", "yes"), + !columnDefinition.MaxValue.HasValue ? null : new XAttribute("MaxValue", columnDefinition.MaxValue.Value), + !columnDefinition.MinValue.HasValue ? null : new XAttribute("MinValue", columnDefinition.MinValue.Value), + !columnDefinition.Nullable ? null : new XAttribute("Nullable", "yes"), + !columnDefinition.PrimaryKey ? null : new XAttribute("PrimaryKey", "yes"), + columnDefinition.Possibilities == null ? null : new XAttribute("Possibilities", "yes"), + new XAttribute("Width", columnDefinition.Length)); + + if (ColumnCategory.Unknown != columnDefinition.Category) { - column.MinValue = columnDefinition.MinValue.Value; + switch (columnDefinition.Category) + { + case ColumnCategory.Text: + xColumn.SetAttributeValue("Category", "text"); + break; + case ColumnCategory.UpperCase: + xColumn.SetAttributeValue("Category", "upperCase"); + break; + case ColumnCategory.LowerCase: + xColumn.SetAttributeValue("Category", "lowerCase"); + break; + case ColumnCategory.Integer: + xColumn.SetAttributeValue("Category", "integer"); + break; + case ColumnCategory.DoubleInteger: + xColumn.SetAttributeValue("Category", "doubleInteger"); + break; + case ColumnCategory.TimeDate: + xColumn.SetAttributeValue("Category", "timeDate"); + break; + case ColumnCategory.Identifier: + xColumn.SetAttributeValue("Category", "identifier"); + break; + case ColumnCategory.Property: + xColumn.SetAttributeValue("Category", "property"); + break; + case ColumnCategory.Filename: + xColumn.SetAttributeValue("Category", "filename"); + break; + case ColumnCategory.WildCardFilename: + xColumn.SetAttributeValue("Category", "wildCardFilename"); + break; + case ColumnCategory.Path: + xColumn.SetAttributeValue("Category", "path"); + break; + case ColumnCategory.Paths: + xColumn.SetAttributeValue("Category", "paths"); + break; + case ColumnCategory.AnyPath: + xColumn.SetAttributeValue("Category", "anyPath"); + break; + case ColumnCategory.DefaultDir: + xColumn.SetAttributeValue("Category", "defaultDir"); + break; + case ColumnCategory.RegPath: + xColumn.SetAttributeValue("Category", "regPath"); + break; + case ColumnCategory.Formatted: + xColumn.SetAttributeValue("Category", "formatted"); + break; + case ColumnCategory.FormattedSDDLText: + xColumn.SetAttributeValue("Category", "formattedSddl"); + break; + case ColumnCategory.Template: + xColumn.SetAttributeValue("Category", "template"); + break; + case ColumnCategory.Condition: + xColumn.SetAttributeValue("Category", "condition"); + break; + case ColumnCategory.Guid: + xColumn.SetAttributeValue("Category", "guid"); + break; + case ColumnCategory.Version: + xColumn.SetAttributeValue("Category", "version"); + break; + case ColumnCategory.Language: + xColumn.SetAttributeValue("Category", "language"); + break; + case ColumnCategory.Binary: + xColumn.SetAttributeValue("Category", "binary"); + break; + case ColumnCategory.CustomSource: + xColumn.SetAttributeValue("Category", "customSource"); + break; + case ColumnCategory.Cabinet: + xColumn.SetAttributeValue("Category", "cabinet"); + break; + case ColumnCategory.Shortcut: + xColumn.SetAttributeValue("Category", "shortcut"); + break; + default: + throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'."); + } } if (ColumnModularizeType.None != columnDefinition.ModularizeType) { switch (columnDefinition.ModularizeType) { - case ColumnModularizeType.Column: - column.Modularize = Wix.Column.ModularizeType.Column; - break; - case ColumnModularizeType.Condition: - column.Modularize = Wix.Column.ModularizeType.Condition; - break; - case ColumnModularizeType.Icon: - column.Modularize = Wix.Column.ModularizeType.Icon; - break; - case ColumnModularizeType.Property: - column.Modularize = Wix.Column.ModularizeType.Property; - break; - case ColumnModularizeType.SemicolonDelimited: - column.Modularize = Wix.Column.ModularizeType.SemicolonDelimited; - break; - default: - throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'."); + case ColumnModularizeType.Column: + xColumn.SetAttributeValue("Modularize", "Column"); + break; + case ColumnModularizeType.Condition: + xColumn.SetAttributeValue("Modularize", "Condition"); + break; + case ColumnModularizeType.Icon: + xColumn.SetAttributeValue("Modularize", "Icon"); + break; + case ColumnModularizeType.Property: + xColumn.SetAttributeValue("Modularize", "Property"); + break; + case ColumnModularizeType.SemicolonDelimited: + xColumn.SetAttributeValue("Modularize", "SemicolonDelimited"); + break; + default: + throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'."); } } - if (columnDefinition.Nullable) - { - column.Nullable = Wix.YesNoType.yes; - } - - if (columnDefinition.PrimaryKey) - { - column.PrimaryKey = Wix.YesNoType.yes; - } - - if (null != columnDefinition.Possibilities) - { - column.Set = columnDefinition.Possibilities; - } - if (ColumnType.Unknown != columnDefinition.Type) { switch (columnDefinition.Type) { - case ColumnType.Localized: - column.Localizable = Wix.YesNoType.yes; - column.Type = Wix.Column.TypeType.@string; - break; - case ColumnType.Number: - column.Type = Wix.Column.TypeType.@int; - break; - case ColumnType.Object: - column.Type = Wix.Column.TypeType.binary; - break; - case ColumnType.Preserved: - case ColumnType.String: - column.Type = Wix.Column.TypeType.@string; - break; - default: - throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type.ToString()}'."); + case ColumnType.Localized: + xColumn.SetAttributeValue("Localizable", "yes"); + xColumn.SetAttributeValue("Type", "string"); + break; + case ColumnType.Number: + xColumn.SetAttributeValue("Type", "int"); + break; + case ColumnType.Object: + xColumn.SetAttributeValue("Type", "binary"); + break; + case ColumnType.Preserved: + case ColumnType.String: + xColumn.SetAttributeValue("Type", "string"); + break; + default: + throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type.ToString()}'."); } } - column.Width = columnDefinition.Length; - - customTable.AddChild(column); + xCustomTable.Add(xColumn); } foreach (var row in table.Rows) { - var wixRow = new Wix.Row(); + var xRow = new XElement(Names.RowElement); foreach (var field in row.Fields) { - var data = new Wix.Data(); - - data.Column = field.Column.Name; - - data.Content = Convert.ToString(field.Data, CultureInfo.InvariantCulture); + var xData = new XElement(Names.DataElement, + new XAttribute("Column", field.Column.Name), + new XAttribute("Value", field.AsString())); - wixRow.AddChild(data); + xRow.Add(xData); } - customTable.AddChild(wixRow); + xCustomTable.Add(xRow); } - this.core.RootElement.AddChild(customTable); + this.RootElement.Add(xCustomTable); } } @@ -4678,20 +3900,11 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var createFolder = new Wix.CreateFolder(); + var xCreateFolder = new XElement(Names.CreateFolderElement, + new XAttribute("Directory", row.FieldAsString(0))); - createFolder.Directory = Convert.ToString(row[0]); - - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(createFolder); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, createFolder); + this.AddChildToParent("Component", xCreateFolder, row, 1); + this.IndexElement(row, xCreateFolder); } } @@ -4703,166 +3916,167 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var customAction = new Wix.CustomAction(); + var xCustomAction = new XElement(Names.CustomActionElement, + new XAttribute("Id", row.FieldAsString(0))); - customAction.Id = Convert.ToString(row[0]); - - var type = Convert.ToInt32(row[1]); + var type = row.FieldAsInteger(1); if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (type & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget)) { - customAction.HideTarget = Wix.YesNoType.yes; + xCustomAction.SetAttributeValue("HideTarget", "yes"); } if (WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate == (type & WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate)) { - customAction.Impersonate = Wix.YesNoType.no; + xCustomAction.SetAttributeValue("Impersonate", "no"); } if (WindowsInstallerConstants.MsidbCustomActionTypeTSAware == (type & WindowsInstallerConstants.MsidbCustomActionTypeTSAware)) { - customAction.TerminalServerAware = Wix.YesNoType.yes; + xCustomAction.SetAttributeValue("TerminalServerAware", "yes"); } if (WindowsInstallerConstants.MsidbCustomActionType64BitScript == (type & WindowsInstallerConstants.MsidbCustomActionType64BitScript)) { - customAction.Win64 = Wix.YesNoType.yes; + xCustomAction.SetAttributeValue("Win64", "yes"); } else if (WindowsInstallerConstants.MsidbCustomActionTypeVBScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) || WindowsInstallerConstants.MsidbCustomActionTypeJScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeJScript)) { - customAction.Win64 = Wix.YesNoType.no; + xCustomAction.SetAttributeValue("Win64", "no"); } switch (type & WindowsInstallerConstants.MsidbCustomActionTypeExecuteBits) { - case 0: - // this is the default value - break; - case WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence: - customAction.Execute = Wix.CustomAction.ExecuteType.firstSequence; - break; - case WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess: - customAction.Execute = Wix.CustomAction.ExecuteType.oncePerProcess; - break; - case WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat: - customAction.Execute = Wix.CustomAction.ExecuteType.secondSequence; - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInScript: - customAction.Execute = Wix.CustomAction.ExecuteType.deferred; - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeRollback: - customAction.Execute = Wix.CustomAction.ExecuteType.rollback; - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeCommit: - customAction.Execute = Wix.CustomAction.ExecuteType.commit; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; + case 0: + // this is the default value + break; + case WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence: + xCustomAction.SetAttributeValue("Execute", "firstSequence"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess: + xCustomAction.SetAttributeValue("Execute", "oncePerProcess"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat: + xCustomAction.SetAttributeValue("Execute", "secondSequence"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInScript: + xCustomAction.SetAttributeValue("Execute", "deferred"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeRollback: + xCustomAction.SetAttributeValue("Execute", "rollback"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeCommit: + xCustomAction.SetAttributeValue("Execute", "commit"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; } switch (type & WindowsInstallerConstants.MsidbCustomActionTypeReturnBits) { - case 0: - // this is the default value - break; - case WindowsInstallerConstants.MsidbCustomActionTypeContinue: - customAction.Return = Wix.CustomAction.ReturnType.ignore; - break; - case WindowsInstallerConstants.MsidbCustomActionTypeAsync: - customAction.Return = Wix.CustomAction.ReturnType.asyncWait; - break; - case WindowsInstallerConstants.MsidbCustomActionTypeAsync + WindowsInstallerConstants.MsidbCustomActionTypeContinue: - customAction.Return = Wix.CustomAction.ReturnType.asyncNoWait; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; + case 0: + // this is the default value + break; + case WindowsInstallerConstants.MsidbCustomActionTypeContinue: + xCustomAction.SetAttributeValue("Return", "ignore"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeAsync: + xCustomAction.SetAttributeValue("Return", "asyncWait"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeAsync + WindowsInstallerConstants.MsidbCustomActionTypeContinue: + xCustomAction.SetAttributeValue("Return", "asyncNoWait"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; } var source = type & WindowsInstallerConstants.MsidbCustomActionTypeSourceBits; switch (source) { - case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: - customAction.BinaryKey = Convert.ToString(row[2]); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: - if (null != row[2]) - { - customAction.FileKey = Convert.ToString(row[2]); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeDirectory: - if (null != row[2]) - { - customAction.Directory = Convert.ToString(row[2]); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeProperty: - customAction.Property = Convert.ToString(row[2]); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; + case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: + xCustomAction.SetAttributeValue("BinaryKey", row.FieldAsString(2)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: + if (!row.IsColumnNull(2)) + { + xCustomAction.SetAttributeValue("FileKey", row.FieldAsString(2)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeDirectory: + if (!row.IsColumnNull(2)) + { + xCustomAction.SetAttributeValue("Directory", row.FieldAsString(2)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeProperty: + xCustomAction.SetAttributeValue("Property", row.FieldAsString(2)); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; } switch (type & WindowsInstallerConstants.MsidbCustomActionTypeTargetBits) { - case WindowsInstallerConstants.MsidbCustomActionTypeDll: - customAction.DllEntry = Convert.ToString(row[3]); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeExe: - customAction.ExeCommand = Convert.ToString(row[3]); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeTextData: - if (WindowsInstallerConstants.MsidbCustomActionTypeSourceFile == source) - { - customAction.Error = Convert.ToString(row[3]); - } - else - { - customAction.Value = Convert.ToString(row[3]); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeJScript: - if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) - { - customAction.Script = Wix.CustomAction.ScriptType.jscript; - customAction.Content = Convert.ToString(row[3]); - } - else - { - customAction.JScriptCall = Convert.ToString(row[3]); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeVBScript: - if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) - { - customAction.Script = Wix.CustomAction.ScriptType.vbscript; - customAction.Content = Convert.ToString(row[3]); - } - else - { - customAction.VBScriptCall = Convert.ToString(row[3]); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInstall: - this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - continue; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; + case WindowsInstallerConstants.MsidbCustomActionTypeDll: + xCustomAction.SetAttributeValue("DllEntry", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeExe: + xCustomAction.SetAttributeValue("ExeCommand", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeTextData: + if (WindowsInstallerConstants.MsidbCustomActionTypeSourceFile == source) + { + xCustomAction.SetAttributeValue("Error", row.FieldAsString(3)); + } + else + { + xCustomAction.SetAttributeValue("Value", row.FieldAsString(3)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeJScript: + if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) + { + xCustomAction.SetAttributeValue("Script", "jscript"); + // TODO: Extract to @ScriptFile? + // xCustomAction.Content = row.FieldAsString(3); + } + else + { + xCustomAction.SetAttributeValue("JScriptCall", row.FieldAsString(3)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeVBScript: + if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) + { + xCustomAction.SetAttributeValue("Script", "vbscript"); + // TODO: Extract to @ScriptFile? + // xCustomAction.Content = row.FieldAsString(3); + } + else + { + xCustomAction.SetAttributeValue("VBScriptCall", row.FieldAsString(3)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInstall: + this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + continue; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; } - var extype = 4 < row.Fields.Length && null != row[4] ? Convert.ToInt32(row[4]) : 0; + var extype = 4 < row.Fields.Length && !row.IsColumnNull(4) ? row.FieldAsInteger(4) : 0; if (WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall == (extype & WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall)) { - customAction.PatchUninstall = Wix.YesNoType.yes; + xCustomAction.SetAttributeValue("PatchUninstall", "yes"); } - this.core.RootElement.AddChild(customAction); - this.core.IndexElement(row, customAction); + this.RootElement.Add(xCustomAction); + this.IndexElement(row, xCustomAction); } } @@ -4874,29 +4088,27 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var componentSearch = new Wix.ComponentSearch(); - - componentSearch.Id = Convert.ToString(row[0]); + var xComponentSearch = new XElement(Names.ComponentSearchElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Guid", row.FieldAsString(1))); - componentSearch.Guid = Convert.ToString(row[1]); - - if (null != row[2]) + if (!row.IsColumnNull(2)) { - switch (Convert.ToInt32(row[2])) + switch (row.FieldAsInteger(2)) { - case WindowsInstallerConstants.MsidbLocatorTypeDirectory: - componentSearch.Type = Wix.ComponentSearch.TypeType.directory; - break; - case WindowsInstallerConstants.MsidbLocatorTypeFileName: - // this is the default value - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - break; + case WindowsInstallerConstants.MsidbLocatorTypeDirectory: + xComponentSearch.SetAttributeValue("Type", "directory"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeFileName: + // this is the default value + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + break; } } - this.core.IndexElement(row, componentSearch); + this.IndexElement(row, xComponentSearch); } } @@ -4908,17 +4120,15 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - if (null != row[1]) + if (!row.IsColumnNull(1)) { - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0])); - - if (null != component) + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) { - component.ComPlusFlags = Convert.ToInt32(row[1]); + xComponent.SetAttributeValue("ComPlusFlags", row.FieldAsInteger(1)); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[0]), "Component")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); } } } @@ -4932,86 +4142,72 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var component = new Wix.Component(); - - component.Id = Convert.ToString(row[0]); - - component.Guid = Convert.ToString(row[1]); + var xComponent = new XElement(Names.ComponentElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Guid", row.FieldAsString(1))); - var attributes = Convert.ToInt32(row[3]); + var attributes = row.FieldAsInteger(3); if (WindowsInstallerConstants.MsidbComponentAttributesSourceOnly == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSourceOnly)) { - component.Location = Wix.Component.LocationType.source; + xComponent.SetAttributeValue("Location", "source"); } else if (WindowsInstallerConstants.MsidbComponentAttributesOptional == (attributes & WindowsInstallerConstants.MsidbComponentAttributesOptional)) { - component.Location = Wix.Component.LocationType.either; + xComponent.SetAttributeValue("Location", "either"); } if (WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount)) { - component.SharedDllRefCount = Wix.YesNoType.yes; + xComponent.SetAttributeValue("SharedDllRefCount", "yes"); } if (WindowsInstallerConstants.MsidbComponentAttributesPermanent == (attributes & WindowsInstallerConstants.MsidbComponentAttributesPermanent)) { - component.Permanent = Wix.YesNoType.yes; + xComponent.SetAttributeValue("Permanent", "yes"); } if (WindowsInstallerConstants.MsidbComponentAttributesTransitive == (attributes & WindowsInstallerConstants.MsidbComponentAttributesTransitive)) { - component.Transitive = Wix.YesNoType.yes; + xComponent.SetAttributeValue("Transitive", "yes"); } if (WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite == (attributes & WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite)) { - component.NeverOverwrite = Wix.YesNoType.yes; + xComponent.SetAttributeValue("NeverOverwrite", "yes"); } if (WindowsInstallerConstants.MsidbComponentAttributes64bit == (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit)) { - component.Win64 = Wix.YesNoType.yes; + xComponent.SetAttributeValue("Win64", "yes"); } else { - component.Win64 = Wix.YesNoType.no; + xComponent.SetAttributeValue("Win64", "no"); } if (WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection == (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection)) { - component.DisableRegistryReflection = Wix.YesNoType.yes; + xComponent.SetAttributeValue("DisableRegistryReflection", "yes"); } if (WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence == (attributes & WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence)) { - component.UninstallWhenSuperseded = Wix.YesNoType.yes; + xComponent.SetAttributeValue("UninstallWhenSuperseded", "yes"); } if (WindowsInstallerConstants.MsidbComponentAttributesShared == (attributes & WindowsInstallerConstants.MsidbComponentAttributesShared)) { - component.Shared = Wix.YesNoType.yes; + xComponent.SetAttributeValue("Shared", "yes"); } - if (null != row[4]) + if (!row.IsColumnNull(4)) { - var condition = new Wix.Condition(); - - condition.Content = Convert.ToString(row[4]); - - component.AddChild(condition); + xComponent.SetAttributeValue("Condition", row.FieldAsString(4)); } - var directory = (Wix.Directory)this.core.GetIndexedElement("Directory", Convert.ToString(row[2])); - if (null != directory) - { - directory.AddChild(component); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_", Convert.ToString(row[2]), "Directory")); - } - this.core.IndexElement(row, component); + this.AddChildToParent("Directory", xComponent, row, 2); + this.IndexElement(row, xComponent); } } @@ -5023,23 +4219,17 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var condition = new Wix.Condition(); - - condition.Level = Convert.ToInt32(row[1]); - - if (null != row[2]) + if (this.TryGetIndexedElement("Feature", out var xFeature, row.FieldAsString(0))) { - condition.Content = Convert.ToString(row[2]); - } + var xLevel = new XElement(Names.LevelElement, + row.IsColumnNull(2) ? null : new XAttribute("Condition", row.FieldAsString(2)), + new XAttribute("Level", row.FieldAsInteger(1))); - var feature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); - if (null != feature) - { - feature.AddChild(condition); + xFeature.Add(xLevel); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", Convert.ToString(row[0]), "Feature")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", row.FieldAsString(0), "Feature")); } } } @@ -5052,85 +4242,28 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var dialog = new Wix.Dialog(); - - dialog.Id = Convert.ToString(row[0]); - - dialog.X = Convert.ToInt32(row[1]); - - dialog.Y = Convert.ToInt32(row[2]); - - dialog.Width = Convert.ToInt32(row[3]); - - dialog.Height = Convert.ToInt32(row[4]); - - if (null != row[5]) - { - var attributes = Convert.ToInt32(row[5]); - - if (0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesVisible)) - { - dialog.Hidden = Wix.YesNoType.yes; - } - - if (0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesModal)) - { - dialog.Modeless = Wix.YesNoType.yes; - } - - if (0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize)) - { - dialog.NoMinimize = Wix.YesNoType.yes; - } - - if (WindowsInstallerConstants.MsidbDialogAttributesSysModal == (attributes & WindowsInstallerConstants.MsidbDialogAttributesSysModal)) - { - dialog.SystemModal = Wix.YesNoType.yes; - } - - if (WindowsInstallerConstants.MsidbDialogAttributesKeepModeless == (attributes & WindowsInstallerConstants.MsidbDialogAttributesKeepModeless)) - { - dialog.KeepModeless = Wix.YesNoType.yes; - } - - if (WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace == (attributes & WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace)) - { - dialog.TrackDiskSpace = Wix.YesNoType.yes; - } + var attributes = row.FieldAsNullableInteger(5) ?? 0; - if (WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette == (attributes & WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette)) - { - dialog.CustomPalette = Wix.YesNoType.yes; - } - - if (WindowsInstallerConstants.MsidbDialogAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbDialogAttributesRTLRO)) - { - dialog.RightToLeft = Wix.YesNoType.yes; - } - - if (WindowsInstallerConstants.MsidbDialogAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbDialogAttributesRightAligned)) - { - dialog.RightAligned = Wix.YesNoType.yes; - } - - if (WindowsInstallerConstants.MsidbDialogAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbDialogAttributesLeftScroll)) - { - dialog.LeftScroll = Wix.YesNoType.yes; - } - - if (WindowsInstallerConstants.MsidbDialogAttributesError == (attributes & WindowsInstallerConstants.MsidbDialogAttributesError)) - { - dialog.ErrorDialog = Wix.YesNoType.yes; - } - } - - if (null != row[6]) - { - dialog.Title = Convert.ToString(row[6]); - } + var xDialog = new XElement(Names.DialogElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("X", row.FieldAsString(1)), + new XAttribute("Y", row.FieldAsString(2)), + new XAttribute("Width", row.FieldAsString(3)), + new XAttribute("Height", row.FieldAsString(4)), + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesVisible) ? new XAttribute("Hidden", "yes") : null, + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesModal) ? new XAttribute("Modeless", "yes") : null, + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesSysModal == (attributes & WindowsInstallerConstants.MsidbDialogAttributesSysModal) ? new XAttribute("SystemModal", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesKeepModeless == (attributes & WindowsInstallerConstants.MsidbDialogAttributesKeepModeless) ? new XAttribute("KeepModeless", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace == (attributes & WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace) ? new XAttribute("TrackDiskSpace", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette == (attributes & WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette) ? new XAttribute("CustomPalette", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbDialogAttributesLeftScroll) ? new XAttribute("LeftScroll", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesError == (attributes & WindowsInstallerConstants.MsidbDialogAttributesError) ? new XAttribute("ErrorDialog", "yes") : null, + !row.IsColumnNull(6) ? new XAttribute("Title", row.FieldAsString(6)) : null); - this.core.UIElement.AddChild(dialog); - this.core.IndexElement(row, dialog); + this.UIElement.Add(xDialog); + this.IndexElement(row, xDialog); } } @@ -5142,16 +4275,16 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var directory = new Wix.Directory(); + var id = row.FieldAsString(0); + var xDirectory = new XElement(Names.DirectoryElement, + new XAttribute("Id", id)); - directory.Id = Convert.ToString(row[0]); + var names = Common.GetNames(row.FieldAsString(2)); - var names = Common.GetNames(Convert.ToString(row[2])); - - if (String.Equals(directory.Id, "TARGETDIR", StringComparison.Ordinal) && !String.Equals(names[0], "SourceDir", StringComparison.Ordinal)) + if (String.Equals(id, "TARGETDIR", StringComparison.Ordinal) && !String.Equals(names[0], "SourceDir", StringComparison.Ordinal)) { this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir()); - directory.Name = "SourceDir"; + xDirectory.SetAttributeValue("Name", "SourceDir"); } else { @@ -5159,17 +4292,17 @@ namespace WixToolset.Core.WindowsInstaller { if (null != names[1]) { - directory.ShortName = names[0]; + xDirectory.SetAttributeValue("ShortName", names[0]); } else { - directory.Name = names[0]; + xDirectory.SetAttributeValue("Name", names[0]); } } if (null != names[1]) { - directory.Name = names[1]; + xDirectory.SetAttributeValue("Name", names[1]); } } @@ -5177,46 +4310,44 @@ namespace WixToolset.Core.WindowsInstaller { if (null != names[3]) { - directory.ShortSourceName = names[2]; + xDirectory.SetAttributeValue("ShortSourceName", names[2]); } else { - directory.SourceName = names[2]; + xDirectory.SetAttributeValue("SourceName", names[2]); } } if (null != names[3]) { - directory.SourceName = names[3]; + xDirectory.SetAttributeValue("SourceName", names[3]); } - this.core.IndexElement(row, directory); + this.IndexElement(row, xDirectory); } // nest the directories foreach (var row in table.Rows) { - var directory = (Wix.Directory)this.core.GetIndexedElement(row); + var xDirectory = this.GetIndexedElement(row); - if (null == row[1]) + if (row.IsColumnNull(1)) { - this.core.RootElement.AddChild(directory); + this.RootElement.Add(xDirectory); } else { - var parentDirectory = (Wix.Directory)this.core.GetIndexedElement("Directory", Convert.ToString(row[1])); - - if (null == parentDirectory) + if (!this.TryGetIndexedElement("Directory", out var xParentDirectory, row.FieldAsString(1))) { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", Convert.ToString(row[1]), "Directory")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", row.FieldAsString(1), "Directory")); } - else if (parentDirectory == directory) // another way to specify a root directory + else if (xParentDirectory == xDirectory) // another way to specify a root directory { - this.core.RootElement.AddChild(directory); + this.RootElement.Add(xDirectory); } else { - parentDirectory.AddChild(directory); + xParentDirectory.Add(xDirectory); } } } @@ -5225,26 +4356,17 @@ namespace WixToolset.Core.WindowsInstaller /// /// Decompile the DrLocator table. /// - /// The table to decompile. - private void DecompileDrLocatorTable(Table table) - { - foreach (var row in table.Rows) - { - var directorySearch = new Wix.DirectorySearch(); - - directorySearch.Id = Convert.ToString(row[0]); - - if (null != row[2]) - { - directorySearch.Path = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - directorySearch.Depth = Convert.ToInt32(row[3]); - } + /// The table to decompile. + private void DecompileDrLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xDirectorySearch = new XElement(Names.DirectorySearchElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Path", row, 2), + XAttributeIfNotNull("Depth", row, 3)); - this.core.IndexElement(row, directorySearch); + this.IndexElement(row, xDirectorySearch); } } @@ -5256,38 +4378,28 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var copyFile = new Wix.CopyFile(); - - copyFile.Id = Convert.ToString(row[0]); + var xCopyFile = new XElement(Names.CopyFileElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("FileId", row.FieldAsString(2))); - copyFile.FileId = Convert.ToString(row[2]); - - if (null != row[3]) + if (!row.IsColumnNull(3)) { - var names = Common.GetNames(Convert.ToString(row[3])); + var names = Common.GetNames(row.FieldAsString(3)); if (null != names[0] && null != names[1]) { - copyFile.DestinationShortName = names[0]; - copyFile.DestinationName = names[1]; + xCopyFile.SetAttributeValue("DestinationShortName", names[0]); + xCopyFile.SetAttributeValue("DestinationName", names[1]); } else if (null != names[0]) { - copyFile.DestinationName = names[0]; + xCopyFile.SetAttributeValue("DestinationName", names[0]); } } // destination directory/property is set in FinalizeDuplicateMoveFileTables - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(copyFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, copyFile); + this.AddChildToParent("Component", xCopyFile, row, 1); + this.IndexElement(row, xCopyFile); } } @@ -5299,83 +4411,74 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var environment = new Wix.Environment(); - - environment.Id = Convert.ToString(row[0]); + var xEnvironment = new XElement(Names.EnvironmentElement, + new XAttribute("Id", row.FieldAsString(0))); var done = false; var permanent = true; - var name = Convert.ToString(row[1]); + var name = row.FieldAsString(1); for (var i = 0; i < name.Length && !done; i++) { switch (name[i]) { - case '=': - environment.Action = Wix.Environment.ActionType.set; - break; - case '+': - environment.Action = Wix.Environment.ActionType.create; - break; - case '-': - permanent = false; - break; - case '!': - environment.Action = Wix.Environment.ActionType.remove; - break; - case '*': - environment.System = Wix.YesNoType.yes; - break; - default: - environment.Name = name.Substring(i); - done = true; - break; + case '=': + xEnvironment.SetAttributeValue("Action", "set"); + break; + case '+': + xEnvironment.SetAttributeValue("Action", "create"); + break; + case '-': + permanent = false; + break; + case '!': + xEnvironment.SetAttributeValue("Action", "remove"); + break; + case '*': + xEnvironment.SetAttributeValue("System", "yes"); + break; + default: + xEnvironment.SetAttributeValue("Name", name.Substring(i)); + done = true; + break; } } if (permanent) { - environment.Permanent = Wix.YesNoType.yes; + xEnvironment.SetAttributeValue("Permanent", "yes"); } - if (null != row[2]) + if (!row.IsColumnNull(2)) { - var value = Convert.ToString(row[2]); + var value = row.FieldAsString(2); if (value.StartsWith("[~]", StringComparison.Ordinal)) { - environment.Part = Wix.Environment.PartType.last; + xEnvironment.SetAttributeValue("Part", "last"); if (3 < value.Length) { - environment.Separator = value.Substring(3, 1); - environment.Value = value.Substring(4); + xEnvironment.SetAttributeValue("Separator", value.Substring(3, 1)); + xEnvironment.SetAttributeValue("Value", value.Substring(4)); } } else if (value.EndsWith("[~]", StringComparison.Ordinal)) { - environment.Part = Wix.Environment.PartType.first; + xEnvironment.SetAttributeValue("Part", "first"); if (3 < value.Length) { - environment.Separator = value.Substring(value.Length - 4, 1); - environment.Value = value.Substring(0, value.Length - 4); + xEnvironment.SetAttributeValue("Separator", value.Substring(value.Length - 4, 1)); + xEnvironment.SetAttributeValue("Value", value.Substring(0, value.Length - 4)); } } else { - environment.Value = value; + xEnvironment.SetAttributeValue("Value", value); } } - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[3])); - if (null != component) - { - component.AddChild(environment); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[3]), "Component")); - } + this.AddChildToParent("Component", xEnvironment, row, 3); } } @@ -5387,13 +4490,11 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var error = new Wix.Error(); + var xError = new XElement(Names.ErrorElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Message", row.FieldAsString(1))); - error.Id = Convert.ToInt32(row[0]); - - error.Content = Convert.ToString(row[1]); - - this.core.UIElement.AddChild(error); + this.UIElement.Add(xError); } } @@ -5405,20 +4506,17 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var subscribe = new Wix.Subscribe(); - - subscribe.Event = Convert.ToString(row[2]); - - subscribe.Attribute = Convert.ToString(row[3]); + var xSubscribe = new XElement(Names.SubscribeElement, + new XAttribute("Event", row.FieldAsString(2)), + new XAttribute("Attribute", row.FieldAsString(3))); - var control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1])); - if (null != control) + if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) { - control.AddChild(subscribe); + xControl.Add(xSubscribe); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); } } } @@ -5431,54 +4529,32 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var extension = new Wix.Extension(); + var xExtension = new XElement(Names.ExtensionElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Advertise", "yes")); - extension.Advertise = Wix.YesNoType.yes; - - extension.Id = Convert.ToString(row[0]); - - if (null != row[3]) + if (!row.IsColumnNull(3)) { - var mime = (Wix.MIME)this.core.GetIndexedElement("MIME", Convert.ToString(row[3])); - - if (null != mime) + if (this.TryGetIndexedElement("MIME", out var xMime, row.FieldAsString(3))) { - mime.Default = Wix.YesNoType.yes; + xMime.SetAttributeValue("Default", "yes"); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", Convert.ToString(row[3]), "MIME")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); } } - if (null != row[2]) + if (!row.IsColumnNull(2)) { - var progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[2])); - - if (null != progId) - { - progId.AddChild(extension); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_", Convert.ToString(row[2]), "ProgId")); - } + this.AddChildToParent("ProgId", xExtension, row, 2); } else { - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - - if (null != component) - { - component.AddChild(extension); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } + this.AddChildToParent("Component", xExtension, row, 1); } - this.core.IndexElement(row, extension); + this.IndexElement(row, xExtension); } } @@ -5490,56 +4566,42 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var externalFile = new Wix.ExternalFile(); - - externalFile.File = Convert.ToString(row[1]); - - externalFile.Source = Convert.ToString(row[2]); - - if (null != row[3]) - { - var symbolPaths = (Convert.ToString(row[3])).Split(';'); - - foreach (var symbolPathString in symbolPaths) - { - var symbolPath = new Wix.SymbolPath(); - - symbolPath.Path = symbolPathString; + var xExternalFile = new XElement(Names.ExternalFileElement, + new XAttribute("File", row.FieldAsString(1)), + new XAttribute("Source", row.FieldAsString(2))); - externalFile.AddChild(symbolPath); - } - } + AddSymbolPaths(row, 3, xExternalFile); - if (null != row[4] && null != row[5]) + if (!row.IsColumnNull(4) && !row.IsColumnNull(5)) { - var ignoreOffsets = (Convert.ToString(row[4])).Split(','); - var ignoreLengths = (Convert.ToString(row[5])).Split(','); + var ignoreOffsets = row.FieldAsString(4).Split(','); + var ignoreLengths = row.FieldAsString(5).Split(','); if (ignoreOffsets.Length == ignoreLengths.Length) { for (var i = 0; i < ignoreOffsets.Length; i++) { - var ignoreRange = new Wix.IgnoreRange(); + var xIgnoreRange = new XElement(Names.IgnoreRangeElement); if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) { - ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i].Substring(2), 16); + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); } else { - ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture); + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); } if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) { - ignoreRange.Length = Convert.ToInt32(ignoreLengths[i].Substring(2), 16); + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); } else { - ignoreRange.Length = Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture); + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); } - externalFile.AddChild(ignoreRange); + xExternalFile.Add(xIgnoreRange); } } else @@ -5547,28 +4609,20 @@ namespace WixToolset.Core.WindowsInstaller // TODO: warn } } - else if (null != row[4] || null != row[5]) + else if (!row.IsColumnNull(4) || !row.IsColumnNull(5)) { // TODO: warn about mismatch between columns } // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable - if (null != row[7]) + if (!row.IsColumnNull(7)) { - externalFile.Order = Convert.ToInt32(row[7]); + xExternalFile.SetAttributeValue("Order", row.FieldAsInteger(7)); } - var family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[0])); - if (null != family) - { - family.AddChild(externalFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[0]), "ImageFamilies")); - } - this.core.IndexElement(row, externalFile); + this.AddChildToParent("ImageFamilies", xExternalFile, row, 0); + this.IndexElement(row, xExternalFile); } } @@ -5578,50 +4632,36 @@ namespace WixToolset.Core.WindowsInstaller /// The table to decompile. private void DecompileFeatureTable(Table table) { - var sortedFeatures = new SortedList(); + var sortedFeatures = new SortedList(); foreach (var row in table.Rows) { - var feature = new Wix.Feature(); - - feature.Id = Convert.ToString(row[0]); - - if (null != row[2]) - { - feature.Title = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - feature.Description = Convert.ToString(row[3]); - } + var feature = new XElement(Names.FeatureElement, + new XAttribute("Id", row.FieldAsString(0)), + row.IsColumnNull(2) ? null : new XAttribute("Title", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Description", row.FieldAsString(3)), + new XAttribute("Level", row.FieldAsInteger(5)), + row.IsColumnNull(6) ? null : new XAttribute("ConfigurableDirectory", row.FieldAsString(6))); - if (null == row[4]) + if (row.IsColumnNull(4)) { - feature.Display = "hidden"; + feature.SetAttributeValue("Display", "hidden"); } else { - var display = Convert.ToInt32(row[4]); + var display = row.FieldAsInteger(4); if (0 == display) { - feature.Display = "hidden"; + feature.SetAttributeValue("Display", "hidden"); } else if (1 == display % 2) { - feature.Display = "expand"; + feature.SetAttributeValue("Display", "expand"); } } - feature.Level = Convert.ToInt32(row[5]); - - if (null != row[6]) - { - feature.ConfigurableDirectory = Convert.ToString(row[6]); - } - - var attributes = Convert.ToInt32(row[7]); + var attributes = row.FieldAsInteger(7); if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource) && WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) { @@ -5629,68 +4669,69 @@ namespace WixToolset.Core.WindowsInstaller } else if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource)) { - feature.InstallDefault = Wix.Feature.InstallDefaultType.source; + feature.SetAttributeValue("InstallDefault", "source"); } else if (WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) { - feature.InstallDefault = Wix.Feature.InstallDefaultType.followParent; + feature.SetAttributeValue("InstallDefault", "followParent"); } if (WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise)) { - feature.TypicalDefault = Wix.Feature.TypicalDefaultType.advertise; + feature.SetAttributeValue("InstallDefault", "advertise"); } if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise) && WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) { this.Messaging.Write(WarningMessages.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no")); - feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.no; + feature.SetAttributeValue("AllowAdvertise", "no"); } else if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise)) { - feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.no; + feature.SetAttributeValue("AllowAdvertise", "no"); } else if (WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) { - feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.system; + feature.SetAttributeValue("AllowAdvertise", "system"); } if (WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent)) { - feature.Absent = Wix.Feature.AbsentType.disallow; + feature.SetAttributeValue("Absent", "disallow"); } - this.core.IndexElement(row, feature); + this.IndexElement(row, feature); // sort the features by their display column (and append the identifier to ensure unique keys) - sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", Convert.ToInt32(row[4], CultureInfo.InvariantCulture), row[0]), row); + sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", row.FieldAsInteger(4), row[0]), row); } // nest the features - foreach (Row row in sortedFeatures.Values) + foreach (var row in sortedFeatures.Values) { - var feature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); + var xFeature = this.GetIndexedElement("Feature", row.FieldAsString(0)); - if (null == row[1]) + if (row.IsColumnNull(1)) { - this.core.RootElement.AddChild(feature); + this.RootElement.Add(xFeature); } else { - var parentFeature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[1])); - - if (null == parentFeature) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", Convert.ToString(row[1]), "Feature")); - } - else if (parentFeature == feature) + if (this.TryGetIndexedElement("Feature", out var xParentFeature, row.FieldAsString(1))) { - // TODO: display a warning about self-nesting + if (xParentFeature == xFeature) + { + // TODO: display a warning about self-nesting + } + else + { + xParentFeature.Add(xFeature); + } } else { - parentFeature.AddChild(feature); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", row.FieldAsString(1), "Feature")); } } } @@ -5704,20 +4745,11 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var componentRef = new Wix.ComponentRef(); + var xComponentRef = new XElement(Names.ComponentRefElement, + new XAttribute("Id", row.FieldAsString(1))); - componentRef.Id = Convert.ToString(row[1]); - - var parentFeature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0])); - if (null != parentFeature) - { - parentFeature.AddChild(componentRef); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", Convert.ToString(row[0]), "Feature")); - } - this.core.IndexElement(row, componentRef); + this.AddChildToParent("Feature", xComponentRef, row, 0); + this.IndexElement(row, xComponentRef); } } @@ -5729,52 +4761,24 @@ namespace WixToolset.Core.WindowsInstaller { foreach (FileRow fileRow in table.Rows) { - var file = new Wix.File(); - - file.Id = fileRow.File; + var xFile = new XElement(Names.FileElement, + new XAttribute("Id", fileRow.File), + WindowsInstallerConstants.MsidbFileAttributesReadOnly == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly) ? new XAttribute("ReadOnly", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesHidden == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesHidden) ? new XAttribute("Hidden", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesSystem == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesSystem) ? new XAttribute("System", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesChecksum == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum) ? new XAttribute("Checksum", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesVital != (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesVital) ? new XAttribute("Vital", "no") : null, + null != fileRow.Version && 0 < fileRow.Version.Length && !Char.IsDigit(fileRow.Version[0]) ? new XAttribute("CompanionFile", fileRow.Version) : null); var names = Common.GetNames(fileRow.FileName); if (null != names[0] && null != names[1]) { - file.ShortName = names[0]; - file.Name = names[1]; + xFile.SetAttributeValue("ShortName", names[0]); + xFile.SetAttributeValue("Name", names[1]); } else if (null != names[0]) { - file.Name = names[0]; - } - - if (null != fileRow.Version && 0 < fileRow.Version.Length) - { - if (!Char.IsDigit(fileRow.Version[0])) - { - file.CompanionFile = fileRow.Version; - } - } - - if (WindowsInstallerConstants.MsidbFileAttributesReadOnly == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly)) - { - file.ReadOnly = Wix.YesNoType.yes; - } - - if (WindowsInstallerConstants.MsidbFileAttributesHidden == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesHidden)) - { - file.Hidden = Wix.YesNoType.yes; - } - - if (WindowsInstallerConstants.MsidbFileAttributesSystem == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesSystem)) - { - file.System = Wix.YesNoType.yes; - } - - if (WindowsInstallerConstants.MsidbFileAttributesVital != (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesVital)) - { - file.Vital = Wix.YesNoType.no; - } - - if (WindowsInstallerConstants.MsidbFileAttributesChecksum == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum)) - { - file.Checksum = Wix.YesNoType.yes; + xFile.SetAttributeValue("Name", names[0]); } if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) && @@ -5784,14 +4788,14 @@ namespace WixToolset.Core.WindowsInstaller } else if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed)) { - file.Compressed = Wix.YesNoDefaultType.no; + xFile.SetAttributeValue("Compressed", "no"); } else if (WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) { - file.Compressed = Wix.YesNoDefaultType.yes; + xFile.SetAttributeValue("Compressed", "yes"); } - this.core.IndexElement(fileRow, file); + this.IndexElement(fileRow, xFile); } } @@ -5803,19 +4807,10 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var sfpFile = new Wix.SFPFile(); - - sfpFile.Id = Convert.ToString(row[0]); + var xSfpFile = new XElement(Names.SFPFileElement, + new XAttribute("Id", row.FieldAsString(0))); - var sfpCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement("SFPCatalog", Convert.ToString(row[1])); - if (null != sfpCatalog) - { - sfpCatalog.AddChild(sfpFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SFPCatalog_", Convert.ToString(row[1]), "SFPCatalog")); - } + this.AddChildToParent("SFPCatalog", xSfpFile, row, 1); } } @@ -5827,22 +4822,20 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); - - if (null != file) + if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) { - if (null != row[1]) + if (!row.IsColumnNull(1)) { - file.FontTitle = Convert.ToString(row[1]); + xFile.SetAttributeValue("FontTitle", row.FieldAsString(1)); } else { - file.TrueType = Wix.YesNoType.yes; + xFile.SetAttributeValue("TrueType", "yes"); } } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); } } } @@ -5855,13 +4848,11 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var icon = new Wix.Icon(); - - icon.Id = Convert.ToString(row[0]); - - icon.SourceFile = Convert.ToString(row[1]); + var icon = new XElement(Names.IconElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); - this.core.RootElement.AddChild(icon); + this.RootElement.Add(icon); } } @@ -5873,37 +4864,16 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var family = new Wix.Family(); - - family.Name = Convert.ToString(row[0]); - - if (null != row[1]) - { - family.MediaSrcProp = Convert.ToString(row[1]); - } - - if (null != row[2]) - { - family.DiskId = Convert.ToString(Convert.ToInt32(row[2])); - } - - if (null != row[3]) - { - family.SequenceStart = Convert.ToInt32(row[3]); - } - - if (null != row[4]) - { - family.DiskPrompt = Convert.ToString(row[4]); - } - - if (null != row[5]) - { - family.VolumeLabel = Convert.ToString(row[5]); - } + var family = new XElement(Names.FamilyElement, + new XAttribute("Name", row.FieldAsString(0)), + row.IsColumnNull(1) ? null : new XAttribute("MediaSrcProp", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("DiskId", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("SequenceStart", row.FieldAsString(3)), + row.IsColumnNull(4) ? null : new XAttribute("DiskPrompt", row.FieldAsString(4)), + row.IsColumnNull(5) ? null : new XAttribute("VolumeLabel", row.FieldAsString(5))); - this.core.RootElement.AddChild(family); - this.core.IndexElement(row, family); + this.RootElement.Add(family); + this.IndexElement(row, family); } } @@ -5915,65 +4885,49 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var iniFile = new Wix.IniFile(); + var xIniFile = new XElement(Names.IniFileElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Section", row.FieldAsString(3)), + new XAttribute("Key", row.FieldAsString(4)), + new XAttribute("Value", row.FieldAsString(5)), + row.IsColumnNull(2) ? null : new XAttribute("Directory", row.FieldAsString(2))); - iniFile.Id = Convert.ToString(row[0]); - - var names = Common.GetNames(Convert.ToString(row[1])); + var names = Common.GetNames(row.FieldAsString(1)); if (null != names[0]) { if (null == names[1]) { - iniFile.Name = names[0]; + xIniFile.SetAttributeValue("Name", names[0]); } else { - iniFile.ShortName = names[0]; + xIniFile.SetAttributeValue("ShortName", names[0]); } } if (null != names[1]) { - iniFile.Name = names[1]; - } - - if (null != row[2]) - { - iniFile.Directory = Convert.ToString(row[2]); + xIniFile.SetAttributeValue("Name", names[1]); } - iniFile.Section = Convert.ToString(row[3]); - - iniFile.Key = Convert.ToString(row[4]); - - iniFile.Value = Convert.ToString(row[5]); - - switch (Convert.ToInt32(row[6])) + switch (row.FieldAsInteger(6)) { - case WindowsInstallerConstants.MsidbIniFileActionAddLine: - iniFile.Action = Wix.IniFile.ActionType.addLine; - break; - case WindowsInstallerConstants.MsidbIniFileActionCreateLine: - iniFile.Action = Wix.IniFile.ActionType.createLine; - break; - case WindowsInstallerConstants.MsidbIniFileActionAddTag: - iniFile.Action = Wix.IniFile.ActionType.addTag; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; + case WindowsInstallerConstants.MsidbIniFileActionAddLine: + xIniFile.SetAttributeValue("Action", "addLine"); + break; + case WindowsInstallerConstants.MsidbIniFileActionCreateLine: + xIniFile.SetAttributeValue("Action", "createLine"); + break; + case WindowsInstallerConstants.MsidbIniFileActionAddTag: + xIniFile.SetAttributeValue("Action", "addTag"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; } - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[7])); - if (null != component) - { - component.AddChild(iniFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[7]), "Component")); - } + this.AddChildToParent("Component", xIniFile, row, 7); } } @@ -5985,55 +4939,43 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var iniFileSearch = new Wix.IniFileSearch(); - - iniFileSearch.Id = Convert.ToString(row[0]); + var xIniFileSearch = new XElement(Names.IniFileSearchElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Section", row.FieldAsString(2)), + new XAttribute("Key", row.FieldAsString(3)), + row.IsColumnNull(4) || row.FieldAsInteger(4) == 0 ? null : new XAttribute("Field", row.FieldAsInteger(4))); - var names = Common.GetNames(Convert.ToString(row[1])); + var names = Common.GetNames(row.FieldAsString(1)); if (null != names[0] && null != names[1]) { - iniFileSearch.ShortName = names[0]; - iniFileSearch.Name = names[1]; + xIniFileSearch.SetAttributeValue("ShortName", names[0]); + xIniFileSearch.SetAttributeValue("Name", names[1]); } else if (null != names[0]) { - iniFileSearch.Name = names[0]; + xIniFileSearch.SetAttributeValue("Name", names[0]); } - iniFileSearch.Section = Convert.ToString(row[2]); - - iniFileSearch.Key = Convert.ToString(row[3]); - - if (null != row[4]) - { - var field = Convert.ToInt32(row[4]); - - if (0 != field) - { - iniFileSearch.Field = field; - } - } - - if (null != row[5]) + if (!row.IsColumnNull(5)) { - switch (Convert.ToInt32(row[5])) + switch (row.FieldAsInteger(5)) { - case WindowsInstallerConstants.MsidbLocatorTypeDirectory: - iniFileSearch.Type = Wix.IniFileSearch.TypeType.directory; - break; - case WindowsInstallerConstants.MsidbLocatorTypeFileName: - // this is the default value - break; - case WindowsInstallerConstants.MsidbLocatorTypeRawValue: - iniFileSearch.Type = Wix.IniFileSearch.TypeType.raw; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); - break; + case WindowsInstallerConstants.MsidbLocatorTypeDirectory: + xIniFileSearch.SetAttributeValue("Type", "directory"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeFileName: + // this is the default value + break; + case WindowsInstallerConstants.MsidbLocatorTypeRawValue: + xIniFileSearch.SetAttributeValue("Type", "raw"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); + break; } } - this.core.IndexElement(row, iniFileSearch); + this.IndexElement(row, xIniFileSearch); } } @@ -6045,19 +4987,10 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var isolateComponent = new Wix.IsolateComponent(); + var xIsolateComponent = new XElement(Names.IsolateComponentElement, + new XAttribute("Shared", row.FieldAsString(0))); - isolateComponent.Shared = Convert.ToString(row[0]); - - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(isolateComponent); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } + this.AddChildToParent("Component", xIsolateComponent, row, 1); } } @@ -6069,18 +5002,16 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - if (Common.DowngradePreventedCondition == Convert.ToString(row[0]) || Common.UpgradePreventedCondition == Convert.ToString(row[0])) + if (Common.DowngradePreventedCondition == row.FieldAsString(0) || Common.UpgradePreventedCondition == row.FieldAsString(0)) { continue; // MajorUpgrade rows processed in FinalizeUpgradeTable } - var condition = new Wix.Condition(); - - condition.Content = Convert.ToString(row[0]); - - condition.Message = Convert.ToString(row[1]); + var condition = new XElement(Names.LaunchElement, + new XAttribute("Condition", row.FieldAsString(0)), + new XAttribute("Message", row.FieldAsString(1))); - this.core.RootElement.AddChild(condition); + this.RootElement.Add(condition); } } @@ -6090,36 +5021,25 @@ namespace WixToolset.Core.WindowsInstaller /// The table to decompile. private void DecompileListBoxTable(Table table) { - Wix.ListBox listBox = null; - var listBoxRows = new SortedList(); - // sort the list boxes by their property and order - foreach (var row in table.Rows) - { - listBoxRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); - } + var listBoxRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); - foreach (Row row in listBoxRows.Values) + XElement xListBox = null; + foreach (Row row in listBoxRows) { - if (null == listBox || Convert.ToString(row[0]) != listBox.Property) + if (null == xListBox || row.FieldAsString(0) != xListBox.Attribute("Property")?.Value) { - listBox = new Wix.ListBox(); + xListBox = new XElement(Names.ListBoxElement, + new XAttribute("Property", row.FieldAsString(0))); - listBox.Property = Convert.ToString(row[0]); - - this.core.UIElement.AddChild(listBox); + this.UIElement.Add(xListBox); } - var listItem = new Wix.ListItem(); - - listItem.Value = Convert.ToString(row[2]); - - if (null != row[3]) - { - listItem.Text = Convert.ToString(row[3]); - } + var listItem = new XElement(Names.ListItemElement, + new XAttribute("Value", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); - listBox.AddChild(listItem); + xListBox.Add(listItem); } } @@ -6129,41 +5049,26 @@ namespace WixToolset.Core.WindowsInstaller /// The table to decompile. private void DecompileListViewTable(Table table) { - Wix.ListView listView = null; - var listViewRows = new SortedList(); - // sort the list views by their property and order - foreach (var row in table.Rows) - { - listViewRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row); - } + var listViewRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); - foreach (Row row in listViewRows.Values) + XElement xListView = null; + foreach (var row in listViewRows) { - if (null == listView || Convert.ToString(row[0]) != listView.Property) + if (null == xListView || row.FieldAsString(0) != xListView.Attribute("Property")?.Value) { - listView = new Wix.ListView(); - - listView.Property = Convert.ToString(row[0]); - - this.core.UIElement.AddChild(listView); - } + xListView = new XElement(Names.ListViewElement, + new XAttribute("Property", row.FieldAsString(0))); - var listItem = new Wix.ListItem(); - - listItem.Value = Convert.ToString(row[2]); - - if (null != row[3]) - { - listItem.Text = Convert.ToString(row[3]); + this.UIElement.Add(xListView); } - if (null != row[4]) - { - listItem.Icon = Convert.ToString(row[4]); - } + var listItem = new XElement(Names.ListItemElement, + new XAttribute("Value", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)), + row.IsColumnNull(4) ? null : new XAttribute("Icon", row.FieldAsString(4))); - listView.AddChild(listItem); + xListView.Add(listItem); } } @@ -6175,26 +5080,29 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var permission = new Wix.Permission(); + var xPermission = new XElement(Names.PermissionElement, + row.IsColumnNull(2) ? null : new XAttribute("Domain", row.FieldAsString(2)), + new XAttribute("User", row.FieldAsString(3))); + string[] specialPermissions; - switch (Convert.ToString(row[1])) + switch (row.FieldAsString(1)) { - case "CreateFolder": - specialPermissions = Common.FolderPermissions; - break; - case "File": - specialPermissions = Common.FilePermissions; - break; - case "Registry": - specialPermissions = Common.RegistryPermissions; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); - return; + case "CreateFolder": + specialPermissions = Common.FolderPermissions; + break; + case "File": + specialPermissions = Common.FilePermissions; + break; + case "Registry": + specialPermissions = Common.RegistryPermissions; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); + return; } - var permissionBits = Convert.ToInt32(row[4]); + var permissionBits = row.FieldAsInteger(4); for (var i = 0; i < 32; i++) { if (0 != ((permissionBits >> i) & 1)) @@ -6216,112 +5124,53 @@ namespace WixToolset.Core.WindowsInstaller else if (0 <= (i - 28) && Common.GenericPermissions.Length > (i - 28)) { name = Common.GenericPermissions[i - 28]; - } - - if (null == name) - { - this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i)); - } - else - { - switch (name) - { - case "Append": - permission.Append = Wix.YesNoType.yes; - break; - case "ChangePermission": - permission.ChangePermission = Wix.YesNoType.yes; - break; - case "CreateChild": - permission.CreateChild = Wix.YesNoType.yes; - break; - case "CreateFile": - permission.CreateFile = Wix.YesNoType.yes; - break; - case "CreateLink": - permission.CreateLink = Wix.YesNoType.yes; - break; - case "CreateSubkeys": - permission.CreateSubkeys = Wix.YesNoType.yes; - break; - case "Delete": - permission.Delete = Wix.YesNoType.yes; - break; - case "DeleteChild": - permission.DeleteChild = Wix.YesNoType.yes; - break; - case "EnumerateSubkeys": - permission.EnumerateSubkeys = Wix.YesNoType.yes; - break; - case "Execute": - permission.Execute = Wix.YesNoType.yes; - break; - case "FileAllRights": - permission.FileAllRights = Wix.YesNoType.yes; - break; - case "GenericAll": - permission.GenericAll = Wix.YesNoType.yes; - break; - case "GenericExecute": - permission.GenericExecute = Wix.YesNoType.yes; - break; - case "GenericRead": - permission.GenericRead = Wix.YesNoType.yes; - break; - case "GenericWrite": - permission.GenericWrite = Wix.YesNoType.yes; - break; - case "Notify": - permission.Notify = Wix.YesNoType.yes; - break; - case "Read": - permission.Read = Wix.YesNoType.yes; - break; - case "ReadAttributes": - permission.ReadAttributes = Wix.YesNoType.yes; - break; - case "ReadExtendedAttributes": - permission.ReadExtendedAttributes = Wix.YesNoType.yes; - break; - case "ReadPermission": - permission.ReadPermission = Wix.YesNoType.yes; - break; - case "SpecificRightsAll": - permission.SpecificRightsAll = Wix.YesNoType.yes; - break; - case "Synchronize": - permission.Synchronize = Wix.YesNoType.yes; - break; - case "TakeOwnership": - permission.TakeOwnership = Wix.YesNoType.yes; - break; - case "Traverse": - permission.Traverse = Wix.YesNoType.yes; - break; - case "Write": - permission.Write = Wix.YesNoType.yes; - break; - case "WriteAttributes": - permission.WriteAttributes = Wix.YesNoType.yes; - break; - case "WriteExtendedAttributes": - permission.WriteExtendedAttributes = Wix.YesNoType.yes; - break; - default: - throw new InvalidOperationException($"Unknown permission attribute '{name}'."); + } + + if (null == name) + { + this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i)); + } + else + { + switch (name) + { + case "Append": + case "ChangePermission": + case "CreateChild": + case "CreateFile": + case "CreateLink": + case "CreateSubkeys": + case "Delete": + case "DeleteChild": + case "EnumerateSubkeys": + case "Execute": + case "FileAllRights": + case "GenericAll": + case "GenericExecute": + case "GenericRead": + case "GenericWrite": + case "Notify": + case "Read": + case "ReadAttributes": + case "ReadExtendedAttributes": + case "ReadPermission": + case "SpecificRightsAll": + case "Synchronize": + case "TakeOwnership": + case "Traverse": + case "Write": + case "WriteAttributes": + case "WriteExtendedAttributes": + xPermission.SetAttributeValue(name, "yes"); + break; + default: + throw new InvalidOperationException($"Unknown permission attribute '{name}'."); } } } } - if (null != row[2]) - { - permission.Domain = Convert.ToString(row[2]); - } - - permission.User = Convert.ToString(row[3]); - - this.core.IndexElement(row, permission); + this.IndexElement(row, xPermission); } } @@ -6333,14 +5182,10 @@ namespace WixToolset.Core.WindowsInstaller { foreach (MediaRow mediaRow in table.Rows) { - var media = new Wix.Media(); - - media.Id = Convert.ToString(mediaRow.DiskId); - - if (null != mediaRow.DiskPrompt) - { - media.DiskPrompt = mediaRow.DiskPrompt; - } + var xMedia = new XElement(Names.MediaElement, + new XAttribute("Id", mediaRow.DiskId), + mediaRow.DiskPrompt == null ? null : new XAttribute("DiskPrompt", mediaRow.DiskPrompt), + mediaRow.VolumeLabel == null ? null : new XAttribute("VolumeLabel", mediaRow.VolumeLabel)); if (null != mediaRow.Cabinet) { @@ -6348,20 +5193,15 @@ namespace WixToolset.Core.WindowsInstaller if (cabinet.StartsWith("#", StringComparison.Ordinal)) { - media.EmbedCab = Wix.YesNoType.yes; + xMedia.SetAttributeValue("EmbedCab", "yes"); cabinet = cabinet.Substring(1); } - media.Cabinet = cabinet; + xMedia.SetAttributeValue("Cabinet", cabinet); } - if (null != mediaRow.VolumeLabel) - { - media.VolumeLabel = mediaRow.VolumeLabel; - } - - this.core.RootElement.AddChild(media); - this.core.IndexElement(mediaRow, media); + this.RootElement.Add(xMedia); + this.IndexElement(mediaRow, xMedia); } } @@ -6373,16 +5213,11 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var mime = new Wix.MIME(); - - mime.ContentType = Convert.ToString(row[0]); + var mime = new XElement(Names.MIMEElement, + new XAttribute("ContentType", row.FieldAsString(0)), + row.IsColumnNull(2) ? null : new XAttribute("Class", row.FieldAsString(2))); - if (null != row[2]) - { - mime.Class = Convert.ToString(row[2]); - } - - this.core.IndexElement(row, mime); + this.IndexElement(row, mime); } } @@ -6394,56 +5229,47 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var configuration = new Wix.Configuration(); - - configuration.Name = Convert.ToString(row[0]); - - switch (Convert.ToInt32(row[1])) - { - case 0: - configuration.Format = Wix.Configuration.FormatType.Text; - break; - case 1: - configuration.Format = Wix.Configuration.FormatType.Key; - break; - case 2: - configuration.Format = Wix.Configuration.FormatType.Integer; - break; - case 3: - configuration.Format = Wix.Configuration.FormatType.Bitfield; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - if (null != row[2]) - { - configuration.Type = Convert.ToString(row[2]); - } + var configuration = new XElement(Names.ConfigurationElement, + new XAttribute("Name", row.FieldAsString(0)), + XAttributeIfNotNull("Type", row, 2), + XAttributeIfNotNull("ContextData", row, 3), + XAttributeIfNotNull("DefaultValue", row, 4), + XAttributeIfNotNull("DisplayName", row, 6), + XAttributeIfNotNull("Description", row, 7), + XAttributeIfNotNull("HelpLocation", row, 8), + XAttributeIfNotNull("HelpKeyword", row, 9)); - if (null != row[3]) + switch (row.FieldAsInteger(1)) { - configuration.ContextData = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - configuration.DefaultValue = Convert.ToString(row[4]); + case 0: + configuration.SetAttributeValue("Format", "Text"); + break; + case 1: + configuration.SetAttributeValue("Format", "Key"); + break; + case 2: + configuration.SetAttributeValue("Format", "Integer"); + break; + case 3: + configuration.SetAttributeValue("Format", "Bitfield"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; } - if (null != row[5]) + if (!row.IsColumnNull(5)) { - var attributes = Convert.ToInt32(row[5]); + var attributes = row.FieldAsInteger(5); if (WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan)) { - configuration.KeyNoOrphan = Wix.YesNoType.yes; + configuration.SetAttributeValue("KeyNoOrphan", "yes"); } if (WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable)) { - configuration.NonNullable = Wix.YesNoType.yes; + configuration.SetAttributeValue("NonNullable", "yes"); } if (3 < attributes) @@ -6452,27 +5278,7 @@ namespace WixToolset.Core.WindowsInstaller } } - if (null != row[6]) - { - configuration.DisplayName = Convert.ToString(row[6]); - } - - if (null != row[7]) - { - configuration.Description = Convert.ToString(row[7]); - } - - if (null != row[8]) - { - configuration.HelpLocation = Convert.ToString(row[8]); - } - - if (null != row[9]) - { - configuration.HelpKeyword = Convert.ToString(row[9]); - } - - this.core.RootElement.AddChild(configuration); + this.RootElement.Add(configuration); } } @@ -6484,18 +5290,12 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var dependency = new Wix.Dependency(); - - dependency.RequiredId = Convert.ToString(row[2]); + var xDependency = new XElement(Names.DependencyElement, + new XAttribute("RequiredId", row.FieldAsString(2)), + new XAttribute("RequiredLanguage", row.FieldAsString(3)), + XAttributeIfNotNull("RequiredVersion", row, 4)); - dependency.RequiredLanguage = Convert.ToInt32(row[3], CultureInfo.InvariantCulture); - - if (null != row[4]) - { - dependency.RequiredVersion = Convert.ToString(row[4]); - } - - this.core.RootElement.AddChild(dependency); + this.RootElement.Add(xDependency); } } @@ -6507,31 +5307,22 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var exclusion = new Wix.Exclusion(); + var xExclusion = new XElement(Names.ExclusionElement, + new XAttribute("ExcludedId", row.FieldAsString(2)), + XAttributeIfNotNull("ExcludedMinVersion", row, 4), + XAttributeIfNotNull("ExcludedMaxVersion", row, 5)); - exclusion.ExcludedId = Convert.ToString(row[2]); - - var excludedLanguage = Convert.ToInt32(Convert.ToString(row[3]), CultureInfo.InvariantCulture); + var excludedLanguage = row.FieldAsInteger(3); if (0 < excludedLanguage) { - exclusion.ExcludeLanguage = excludedLanguage; + xExclusion.SetAttributeValue("ExcludeLanguage", excludedLanguage); } else if (0 > excludedLanguage) { - exclusion.ExcludeExceptLanguage = -excludedLanguage; - } - - if (null != row[4]) - { - exclusion.ExcludedMinVersion = Convert.ToString(row[4]); + xExclusion.SetAttributeValue("ExcludeExceptLanguage", -excludedLanguage); } - if (null != row[5]) - { - exclusion.ExcludedMinVersion = Convert.ToString(row[5]); - } - - this.core.RootElement.AddChild(exclusion); + this.RootElement.Add(xExclusion); } } @@ -6543,16 +5334,15 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var tableName = Convert.ToString(row[0]); + var tableName = row.FieldAsString(0); // the linker automatically adds a ModuleIgnoreTable row for some tables if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName) { - var ignoreTable = new Wix.IgnoreTable(); - - ignoreTable.Id = tableName; + var xIgnoreTable = new XElement(Names.IgnoreTableElement, + new XAttribute("Id", tableName)); - this.core.RootElement.AddChild(ignoreTable); + this.RootElement.Add(xIgnoreTable); } } } @@ -6567,14 +5357,10 @@ namespace WixToolset.Core.WindowsInstaller { var row = table.Rows[0]; - var module = (Wix.Module)this.core.RootElement; - - module.Id = Convert.ToString(row[0]); - + this.RootElement.SetAttributeValue("Id", row.FieldAsString(0)); // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability) - module.Language = Convert.ToString(row[1], CultureInfo.InvariantCulture); - - module.Version = Convert.ToString(row[2]); + this.RootElement.SetAttributeValue("Language", row.FieldAsString(1)); + this.RootElement.SetAttributeValue("Version", row.FieldAsString(2)); } else { @@ -6590,20 +5376,13 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var substitution = new Wix.Substitution(); - - substitution.Table = Convert.ToString(row[0]); + var xSubstitution = new XElement(Names.SubstitutionElement, + new XAttribute("Table", row.FieldAsString(0)), + new XAttribute("Row", row.FieldAsString(1)), + new XAttribute("Column", row.FieldAsString(2)), + XAttributeIfNotNull("Value", row, 3)); - substitution.Row = Convert.ToString(row[1]); - - substitution.Column = Convert.ToString(row[2]); - - if (null != row[3]) - { - substitution.Value = Convert.ToString(row[3]); - } - - this.core.RootElement.AddChild(substitution); + this.RootElement.Add(xSubstitution); } } @@ -6615,53 +5394,40 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var copyFile = new Wix.CopyFile(); - - copyFile.Id = Convert.ToString(row[0]); - - if (null != row[2]) - { - copyFile.SourceName = Convert.ToString(row[2]); - } + var xCopyFile = new XElement(Names.CopyFileElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("SourceName", row, 2)); - if (null != row[3]) + if (!row.IsColumnNull(3)) { - var names = Common.GetNames(Convert.ToString(row[3])); + var names = Common.GetNames(row.FieldAsString(3)); if (null != names[0] && null != names[1]) { - copyFile.DestinationShortName = names[0]; - copyFile.DestinationName = names[1]; + xCopyFile.SetAttributeValue("DestinationShortName", names[0]); + xCopyFile.SetAttributeValue("DestinationName", names[1]); } else if (null != names[0]) { - copyFile.DestinationName = names[0]; + xCopyFile.SetAttributeValue("DestinationName", names[0]); } } // source/destination directory/property is set in FinalizeDuplicateMoveFileTables - switch (Convert.ToInt32(row[6])) + switch (row.FieldAsInteger(6)) { - case 0: - break; - case WindowsInstallerConstants.MsidbMoveFileOptionsMove: - copyFile.Delete = Wix.YesNoType.yes; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; + case 0: + break; + case WindowsInstallerConstants.MsidbMoveFileOptionsMove: + xCopyFile.SetAttributeValue("Delete", "yes"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; } - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(copyFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, copyFile); + this.AddChildToParent("Component", xCopyFile, row, 1); + this.IndexElement(row, xCopyFile); } } @@ -6673,13 +5439,11 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var digitalCertificate = new Wix.DigitalCertificate(); - - digitalCertificate.Id = Convert.ToString(row[0]); + var xDigitalCertificate = new XElement(Names.DigitalCertificateElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); - digitalCertificate.SourceFile = Convert.ToString(row[1]); - - this.core.IndexElement(row, digitalCertificate); + this.IndexElement(row, xDigitalCertificate); } } @@ -6691,31 +5455,18 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var digitalSignature = new Wix.DigitalSignature(); - - if (null != row[3]) - { - digitalSignature.SourceFile = Convert.ToString(row[3]); - } + var xDigitalSignature = new XElement(Names.DigitalSignatureElement, + XAttributeIfNotNull("SourceFile", row, 3)); - var digitalCertificate = (Wix.DigitalCertificate)this.core.GetIndexedElement("MsiDigitalCertificate", Convert.ToString(row[2])); - if (null != digitalCertificate) - { - digitalSignature.AddChild(digitalCertificate); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", Convert.ToString(row[2]), "MsiDigitalCertificate")); - } + this.AddChildToParent("MsiDigitalCertificate", xDigitalSignature, row, 2); - var parentElement = (Wix.IParentElement)this.core.GetIndexedElement(Convert.ToString(row[0]), Convert.ToString(row[1])); - if (null != parentElement) + if (this.TryGetIndexedElement(row.FieldAsString(0), out var xParentElement, row.FieldAsString(1))) { - parentElement.AddChild(digitalSignature); + xParentElement.Add(xDigitalSignature); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", Convert.ToString(row[1]), Convert.ToString(row[0]))); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", row.FieldAsString(1), row.FieldAsString(0))); } } } @@ -6728,34 +5479,28 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var embeddedChainer = new Wix.EmbeddedChainer(); - - embeddedChainer.Id = Convert.ToString(row[0]); - - embeddedChainer.Content = Convert.ToString(row[1]); - - if (null != row[2]) - { - embeddedChainer.CommandLine = Convert.ToString(row[2]); - } + var xEmbeddedChainer = new XElement(Names.EmbeddedChainerElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Condition", row.FieldAsString(1)), + XAttributeIfNotNull("CommandLine", row, 2)); - switch (Convert.ToInt32(row[4])) + switch (row.FieldAsInteger(4)) { - case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: - embeddedChainer.BinarySource = Convert.ToString(row[3]); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: - embeddedChainer.FileSource = Convert.ToString(row[3]); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeProperty: - embeddedChainer.PropertySource = Convert.ToString(row[3]); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; + case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: + xEmbeddedChainer.SetAttributeValue("BinarySource", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: + xEmbeddedChainer.SetAttributeValue("FileSource", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeProperty: + xEmbeddedChainer.SetAttributeValue("PropertySource", row.FieldAsString(3)); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; } - this.core.RootElement.AddChild(embeddedChainer); + this.RootElement.Add(xEmbeddedChainer); } } @@ -6765,13 +5510,14 @@ namespace WixToolset.Core.WindowsInstaller /// The table to decompile. private void DecompileMsiEmbeddedUITable(Table table) { - var embeddedUI = new Wix.EmbeddedUI(); + var xEmbeddedUI = new XElement(Names.EmbeddedUIElement); + var foundEmbeddedUI = false; var foundEmbeddedResources = false; foreach (var row in table.Rows) { - var attributes = Convert.ToInt32(row[2]); + var attributes = row.FieldAsInteger(2); if (WindowsInstallerConstants.MsidbEmbeddedUI == (attributes & WindowsInstallerConstants.MsidbEmbeddedUI)) { @@ -6781,120 +5527,119 @@ namespace WixToolset.Core.WindowsInstaller } else { - embeddedUI.Id = Convert.ToString(row[0]); - embeddedUI.Name = Convert.ToString(row[1]); + xEmbeddedUI.SetAttributeValue("Id", row.FieldAsString(0)); + xEmbeddedUI.SetAttributeValue("Name", row.FieldAsString(1)); - var messageFilter = Convert.ToInt32(row[3]); + var messageFilter = row.FieldAsInteger(3); if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT)) { - embeddedUI.IgnoreFatalExit = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreFatalExit", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ERROR)) { - embeddedUI.IgnoreError = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreError", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_WARNING)) { - embeddedUI.IgnoreWarning = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreWarning", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_USER)) { - embeddedUI.IgnoreUser = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreUser", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INFO)) { - embeddedUI.IgnoreInfo = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreInfo", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE)) { - embeddedUI.IgnoreFilesInUse = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreFilesInUse", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE)) { - embeddedUI.IgnoreResolveSource = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreResolveSource", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE)) { - embeddedUI.IgnoreOutOfDiskSpace = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreOutOfDiskSpace", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART)) { - embeddedUI.IgnoreActionStart = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreActionStart", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA)) { - embeddedUI.IgnoreActionData = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreActionData", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS)) { - embeddedUI.IgnoreProgress = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreProgress", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA)) { - embeddedUI.IgnoreCommonData = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreCommonData", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE)) { - embeddedUI.IgnoreInitialize = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreInitialize", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE)) { - embeddedUI.IgnoreTerminate = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreTerminate", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG)) { - embeddedUI.IgnoreShowDialog = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreShowDialog", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE)) { - embeddedUI.IgnoreRMFilesInUse = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreRMFilesInUse", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART)) { - embeddedUI.IgnoreInstallStart = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreInstallStart", "yes"); } if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND)) { - embeddedUI.IgnoreInstallEnd = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("IgnoreInstallEnd", "yes"); } if (WindowsInstallerConstants.MsidbEmbeddedHandlesBasic == (attributes & WindowsInstallerConstants.MsidbEmbeddedHandlesBasic)) { - embeddedUI.SupportBasicUI = Wix.YesNoType.yes; + xEmbeddedUI.SetAttributeValue("SupportBasicUI", "yes"); } - embeddedUI.SourceFile = Convert.ToString(row[4]); + xEmbeddedUI.SetAttributeValue("SourceFile", row.FieldAsString(4)); - this.core.UIElement.AddChild(embeddedUI); + this.UIElement.Add(xEmbeddedUI); foundEmbeddedUI = true; } } else { - var embeddedResource = new Wix.EmbeddedUIResource(); - - embeddedResource.Id = Convert.ToString(row[0]); - embeddedResource.Name = Convert.ToString(row[1]); - embeddedResource.SourceFile = Convert.ToString(row[4]); + var xEmbeddedResource = new XElement(Names.EmbeddedUIResourceElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(1)), + new XAttribute("SourceFile", row.FieldAsString(4))); - embeddedUI.AddChild(embeddedResource); + xEmbeddedUI.Add(xEmbeddedResource); foundEmbeddedResources = true; } } @@ -6913,30 +5658,24 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var permissionEx = new Wix.PermissionEx(); - permissionEx.Id = Convert.ToString(row[0]); - permissionEx.Sddl = Convert.ToString(row[3]); + var xPermissionEx = new XElement(Names.PermissionExElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Sddl", row.FieldAsString(3)), + XAttributeIfNotNull("Condition", row, 4)); - if (null != row[4]) + switch (row.FieldAsString(2)) { - var condition = new Wix.Condition(); - condition.Content = Convert.ToString(row[4]); - permissionEx.AddChild(condition); - } - - switch (Convert.ToString(row[2])) - { - case "CreateFolder": - case "File": - case "Registry": - case "ServiceInstall": - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); - return; + case "CreateFolder": + case "File": + case "Registry": + case "ServiceInstall": + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); + return; } - this.core.IndexElement(row, permissionEx); + this.IndexElement(row, xPermissionEx); } } @@ -6948,9 +5687,9 @@ namespace WixToolset.Core.WindowsInstaller { if (0 < table.Rows.Count) { - var packageCertificates = new Wix.PackageCertificates(); - this.core.RootElement.AddChild(packageCertificates); - this.AddCertificates(table, packageCertificates); + var xPackageCertificates = new XElement(Names.PatchCertificatesElement); + this.RootElement.Add(xPackageCertificates); + this.AddCertificates(table, xPackageCertificates); } } @@ -6962,9 +5701,9 @@ namespace WixToolset.Core.WindowsInstaller { if (0 < table.Rows.Count) { - var patchCertificates = new Wix.PatchCertificates(); - this.core.RootElement.AddChild(patchCertificates); - this.AddCertificates(table, patchCertificates); + var xPatchCertificates = new XElement(Names.PatchCertificatesElement); + this.RootElement.Add(xPatchCertificates); + this.AddCertificates(table, xPatchCertificates); } } @@ -6973,19 +5712,17 @@ namespace WixToolset.Core.WindowsInstaller /// /// The table being decompiled. /// DigitalCertificate parent - private void AddCertificates(Table table, Wix.IParentElement parent) + private void AddCertificates(Table table, XElement parent) { foreach (var row in table.Rows) { - var digitalCertificate = (Wix.DigitalCertificate)this.core.GetIndexedElement("MsiDigitalCertificate", Convert.ToString(row[1])); - - if (null != digitalCertificate) + if (this.TryGetIndexedElement("MsiDigitalCertificate", out var xDigitalCertificate, row.FieldAsString(1))) { - parent.AddChild(digitalCertificate); + parent.Add(xDigitalCertificate); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", Convert.ToString(row[1]), "MsiDigitalCertificate")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", row.FieldAsString(1), "MsiDigitalCertificate")); } } } @@ -6998,20 +5735,12 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var property = new Wix.ShortcutProperty(); - property.Id = Convert.ToString(row[0]); - property.Key = Convert.ToString(row[2]); - property.Value = Convert.ToString(row[3]); + var xProperty = new XElement(Names.ShortcutPropertyElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + new XAttribute("Value", row.FieldAsString(3))); - var shortcut = (Wix.Shortcut)this.core.GetIndexedElement("Shortcut", Convert.ToString(row[1])); - if (null != shortcut) - { - shortcut.AddChild(property); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Shortcut_", Convert.ToString(row[1]), "Shortcut")); - } + this.AddChildToParent("Shortcut", xProperty, row, 1); } } @@ -7023,24 +5752,11 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var property = new Wix.Property(); - - property.Id = Convert.ToString(row[1]); - - if (null != row[2]) - { - property.Value = Convert.ToString(row[2]); - } + var xProperty = new XElement(Names.PropertyElement, + new XAttribute("Id", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("Value", row.FieldAsString(2))); - var odbcDriver = (Wix.ODBCDriver)this.core.GetIndexedElement("ODBCDriver", Convert.ToString(row[0])); - if (null != odbcDriver) - { - odbcDriver.AddChild(property); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Driver_", Convert.ToString(row[0]), "ODBCDriver")); - } + this.AddChildToParent("ODBCDriver", xProperty, row, 0); } } @@ -7052,28 +5768,25 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var odbcDataSource = new Wix.ODBCDataSource(); - - odbcDataSource.Id = Convert.ToString(row[0]); - - odbcDataSource.Name = Convert.ToString(row[2]); + var xOdbcDataSource = new XElement(Names.ODBCDataSourceElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(2)), + new XAttribute("DriverName", row.FieldAsString(3))); - odbcDataSource.DriverName = Convert.ToString(row[3]); - - switch (Convert.ToInt32(row[4])) + switch (row.FieldAsInteger(4)) { - case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerMachine: - odbcDataSource.Registration = Wix.ODBCDataSource.RegistrationType.machine; - break; - case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerUser: - odbcDataSource.Registration = Wix.ODBCDataSource.RegistrationType.user; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; + case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerMachine: + xOdbcDataSource.SetAttributeValue("Registration", "machine"); + break; + case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerUser: + xOdbcDataSource.SetAttributeValue("Registration", "user"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; } - this.core.IndexElement(row, odbcDataSource); + this.IndexElement(row, xOdbcDataSource); } } @@ -7085,29 +5798,14 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var odbcDriver = new Wix.ODBCDriver(); - - odbcDriver.Id = Convert.ToString(row[0]); + var xOdbcDriver = new XElement(Names.ODBCDriverElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(2)), + new XAttribute("File", row.FieldAsString(3)), + XAttributeIfNotNull("SetupFile", row, 4)); - odbcDriver.Name = Convert.ToString(row[2]); - - odbcDriver.File = Convert.ToString(row[3]); - - if (null != row[4]) - { - odbcDriver.SetupFile = Convert.ToString(row[4]); - } - - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(odbcDriver); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, odbcDriver); + this.AddChildToParent("Component", xOdbcDriver, row, 1); + this.IndexElement(row, xOdbcDriver); } } @@ -7119,24 +5817,11 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var property = new Wix.Property(); - - property.Id = Convert.ToString(row[1]); - - if (null != row[2]) - { - property.Value = Convert.ToString(row[2]); - } + var xProperty = new XElement(Names.PropertyElement, + new XAttribute("Id", row.FieldAsString(1)), + XAttributeIfNotNull("Value", row, 2)); - var odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement("ODBCDataSource", Convert.ToString(row[0])); - if (null != odbcDataSource) - { - odbcDataSource.AddChild(property); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DataSource_", Convert.ToString(row[0]), "ODBCDataSource")); - } + this.AddChildToParent("ODBCDataSource", xProperty, row, 0); } } @@ -7148,28 +5833,13 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var odbcTranslator = new Wix.ODBCTranslator(); - - odbcTranslator.Id = Convert.ToString(row[0]); - - odbcTranslator.Name = Convert.ToString(row[2]); - - odbcTranslator.File = Convert.ToString(row[3]); - - if (null != row[4]) - { - odbcTranslator.SetupFile = Convert.ToString(row[4]); - } + var xOdbcTranslator = new XElement(Names.ODBCTranslatorElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(2)), + new XAttribute("File", row.FieldAsString(3)), + XAttributeIfNotNull("SetupFile", row, 4)); - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(odbcTranslator); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } + this.AddChildToParent("Component", xOdbcTranslator, row, 1); } } @@ -7181,115 +5851,106 @@ namespace WixToolset.Core.WindowsInstaller { if (0 < table.Rows.Count) { - var patchMetadata = new Wix.PatchMetadata(); + var xPatchMetadata = new XElement(Names.PatchMetadataElement); foreach (var row in table.Rows) { - var value = Convert.ToString(row[2]); + var value = row.FieldAsString(2); - switch (Convert.ToString(row[1])) + switch (row.FieldAsString(1)) { - case "AllowRemoval": - if ("1" == value) - { - patchMetadata.AllowRemoval = Wix.YesNoType.yes; - } - break; - case "Classification": - if (null != value) - { - patchMetadata.Classification = value; - } - break; - case "CreationTimeUTC": - if (null != value) - { - patchMetadata.CreationTimeUTC = value; - } - break; - case "Description": - if (null != value) - { - patchMetadata.Description = value; - } - break; - case "DisplayName": - if (null != value) - { - patchMetadata.DisplayName = value; - } - break; - case "ManufacturerName": - if (null != value) - { - patchMetadata.ManufacturerName = value; - } - break; - case "MinorUpdateTargetRTM": - if (null != value) - { - patchMetadata.MinorUpdateTargetRTM = value; - } - break; - case "MoreInfoURL": - if (null != value) - { - patchMetadata.MoreInfoURL = value; - } - break; - case "OptimizeCA": - var optimizeCustomActions = new Wix.OptimizeCustomActions(); - var optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture); - if (0 != (Convert.ToInt32(OptimizeCA.SkipAssignment) & optimizeCA)) - { - optimizeCustomActions.SkipAssignment = Wix.YesNoType.yes; - } - - if (0 != (Convert.ToInt32(OptimizeCA.SkipImmediate) & optimizeCA)) - { - optimizeCustomActions.SkipImmediate = Wix.YesNoType.yes; - } - - if (0 != (Convert.ToInt32(OptimizeCA.SkipDeferred) & optimizeCA)) - { - optimizeCustomActions.SkipDeferred = Wix.YesNoType.yes; - } - - patchMetadata.AddChild(optimizeCustomActions); - break; - case "OptimizedInstallMode": - if ("1" == value) - { - patchMetadata.OptimizedInstallMode = Wix.YesNoType.yes; - } - break; - case "TargetProductName": - if (null != value) - { - patchMetadata.TargetProductName = value; - } - break; - default: - var customProperty = new Wix.CustomProperty(); + case "AllowRemoval": + if ("1" == value) + { + xPatchMetadata.SetAttributeValue("AllowRemoval", "yes"); + } + break; + case "Classification": + if (null != value) + { + xPatchMetadata.SetAttributeValue("Classification", value); + } + break; + case "CreationTimeUTC": + if (null != value) + { + xPatchMetadata.SetAttributeValue("CreationTimeUTC", value); + } + break; + case "Description": + if (null != value) + { + xPatchMetadata.SetAttributeValue("Description", value); + } + break; + case "DisplayName": + if (null != value) + { + xPatchMetadata.SetAttributeValue("DisplayName", value); + } + break; + case "ManufacturerName": + if (null != value) + { + xPatchMetadata.SetAttributeValue("ManufacturerName", value); + } + break; + case "MinorUpdateTargetRTM": + if (null != value) + { + xPatchMetadata.SetAttributeValue("MinorUpdateTargetRTM", value); + } + break; + case "MoreInfoURL": + if (null != value) + { + xPatchMetadata.SetAttributeValue("MoreInfoURL", value); + } + break; + case "OptimizeCA": + var xOptimizeCustomActions = new XElement(Names.OptimizeCustomActionsElement); + var optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture); + if (0 != (Convert.ToInt32(OptimizeCA.SkipAssignment) & optimizeCA)) + { + xOptimizeCustomActions.SetAttributeValue("SkipAssignment", "yes"); + } - if (null != row[0]) - { - customProperty.Company = Convert.ToString(row[0]); - } + if (0 != (Convert.ToInt32(OptimizeCA.SkipImmediate) & optimizeCA)) + { + xOptimizeCustomActions.SetAttributeValue("SkipImmediate", "yes"); + } - customProperty.Property = Convert.ToString(row[1]); + if (0 != (Convert.ToInt32(OptimizeCA.SkipDeferred) & optimizeCA)) + { + xOptimizeCustomActions.SetAttributeValue("SkipDeferred", "yes"); + } - if (null != row[2]) - { - customProperty.Value = Convert.ToString(row[2]); - } + xPatchMetadata.Add(xOptimizeCustomActions); + break; + case "OptimizedInstallMode": + if ("1" == value) + { + xPatchMetadata.SetAttributeValue("OptimizedInstallMode", "yes"); + } + break; + case "TargetProductName": + if (null != value) + { + xPatchMetadata.SetAttributeValue("TargetProductName", value); + } + break; + default: + var xCustomProperty = new XElement(Names.CustomPropertyElement, + XAttributeIfNotNull("Company", row, 0), + XAttributeIfNotNull("Property", row, 1), + XAttributeIfNotNull("Value", row, 2)); - patchMetadata.AddChild(customProperty); - break; + xPatchMetadata.Add(xCustomProperty); + break; } } - this.core.RootElement.AddChild(patchMetadata); + this.RootElement.Add(xPatchMetadata); } } @@ -7301,35 +5962,34 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var patchSequence = new Wix.PatchSequence(); - - patchSequence.PatchFamily = Convert.ToString(row[0]); + var patchSequence = new XElement(Names.PatchSequenceElement, + new XAttribute("PatchFamily", row.FieldAsString(0))); - if (null != row[1]) + if (!row.IsColumnNull(1)) { try { - var guid = new Guid(Convert.ToString(row[1])); + var guid = new Guid(row.FieldAsString(1)); - patchSequence.ProductCode = Convert.ToString(row[1]); + patchSequence.SetAttributeValue("ProductCode", row.FieldAsString(1)); } catch // non-guid value { - patchSequence.TargetImage = Convert.ToString(row[1]); + patchSequence.SetAttributeValue("TargetImage", row.FieldAsString(1)); } } - if (null != row[2]) + if (!row.IsColumnNull(2)) { - patchSequence.Sequence = Convert.ToString(row[2]); + patchSequence.SetAttributeValue("Sequence", row.FieldAsString(2)); } - if (null != row[3] && 0x1 == Convert.ToInt32(row[3])) + if (!row.IsColumnNull(3) && 0x1 == row.FieldAsInteger(3)) { - patchSequence.Supersede = Wix.YesNoType.yes; + patchSequence.SetAttributeValue("Supersede", "yes"); } - this.core.RootElement.AddChild(patchSequence); + this.RootElement.Add(patchSequence); } } @@ -7341,49 +6001,26 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var progId = new Wix.ProgId(); - - progId.Advertise = Wix.YesNoType.yes; - - progId.Id = Convert.ToString(row[0]); - - if (null != row[3]) - { - progId.Description = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - progId.Icon = Convert.ToString(row[4]); - } - - if (null != row[5]) - { - progId.IconIndex = Convert.ToInt32(row[5]); - } + var xProgId = new XElement(Names.ProgIdElement, + new XAttribute("Advertise", "yes"), + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Description", row, 3), + XAttributeIfNotNull("Icon", row, 4), + XAttributeIfNotNull("IconIndex", row, 5)); - this.core.IndexElement(row, progId); + this.IndexElement(row, xProgId); } // nest the ProgIds foreach (var row in table.Rows) { - var progId = (Wix.ProgId)this.core.GetIndexedElement(row); + var xProgId = this.GetIndexedElement(row); - if (null != row[1]) + if (!row.IsColumnNull(1)) { - var parentProgId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[1])); - - if (null != parentProgId) - { - parentProgId.AddChild(progId); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Parent", Convert.ToString(row[1]), "ProgId")); - } + this.AddChildToParent("ProgId", xProgId, row, 1); } - else if (null != row[2]) + else if (!row.IsColumnNull(2)) { // nesting is handled in FinalizeProgIdTable } @@ -7400,107 +6037,101 @@ namespace WixToolset.Core.WindowsInstaller /// The table to decompile. private void DecompilePropertiesTable(Table table) { - var patchCreation = (Wix.PatchCreation)this.core.RootElement; - foreach (var row in table.Rows) { - var name = Convert.ToString(row[0]); - var value = Convert.ToString(row[1]); + var name = row.FieldAsString(0); + var value = row.FieldAsString(1); switch (name) { - case "AllowProductCodeMismatches": - if ("1" == value) - { - patchCreation.AllowProductCodeMismatches = Wix.YesNoType.yes; - } - break; - case "AllowProductVersionMajorMismatches": - if ("1" == value) - { - patchCreation.AllowMajorVersionMismatches = Wix.YesNoType.yes; - } - break; - case "ApiPatchingSymbolFlags": - if (null != value) - { - try + case "AllowProductCodeMismatches": + if ("1" == value) { - // remove the leading "0x" if its present - if (value.StartsWith("0x", StringComparison.Ordinal)) + this.RootElement.SetAttributeValue("AllowProductCodeMismatches", "yes"); + } + break; + case "AllowProductVersionMajorMismatches": + if ("1" == value) + { + this.RootElement.SetAttributeValue("AllowMajorVersionMismatches", "yes"); + } + break; + case "ApiPatchingSymbolFlags": + if (null != value) + { + try { - value = value.Substring(2); - } + // remove the leading "0x" if its present + if (value.StartsWith("0x", StringComparison.Ordinal)) + { + value = value.Substring(2); + } - patchCreation.SymbolFlags = Convert.ToInt32(value, 16); + this.RootElement.SetAttributeValue("SymbolFlags", Convert.ToInt32(value, 16)); + } + catch + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + } } - catch + break; + case "DontRemoveTempFolderWhenFinished": + if ("1" == value) { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + this.RootElement.SetAttributeValue("CleanWorkingFolder", "no"); } - } - break; - case "DontRemoveTempFolderWhenFinished": - if ("1" == value) - { - patchCreation.CleanWorkingFolder = Wix.YesNoType.no; - } - break; - case "IncludeWholeFilesOnly": - if ("1" == value) - { - patchCreation.WholeFilesOnly = Wix.YesNoType.yes; - } - break; - case "ListOfPatchGUIDsToReplace": - if (null != value) - { - var guidRegex = 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}\}"); - var guidMatches = guidRegex.Matches(value); - - foreach (Match guidMatch in guidMatches) + break; + case "IncludeWholeFilesOnly": + if ("1" == value) { - var replacePatch = new Wix.ReplacePatch(); - - replacePatch.Id = guidMatch.Value; - - this.core.RootElement.AddChild(replacePatch); + this.RootElement.SetAttributeValue("WholeFilesOnly", "yes"); } - } - break; - case "ListOfTargetProductCodes": - if (null != value) - { - var targetProductCodes = value.Split(';'); - - foreach (var targetProductCodeString in targetProductCodes) + break; + case "ListOfPatchGUIDsToReplace": + if (null != value) { - var targetProductCode = new Wix.TargetProductCode(); + var guidRegex = 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}\}"); + var guidMatches = guidRegex.Matches(value); - targetProductCode.Id = targetProductCodeString; + foreach (Match guidMatch in guidMatches) + { + var xReplacePatch = new XElement(Names.ReplacePatchElement, + new XAttribute("Id", guidMatch.Value)); - this.core.RootElement.AddChild(targetProductCode); + this.RootElement.Add(xReplacePatch); + } } - } - break; - case "PatchGUID": - patchCreation.Id = value; - break; - case "PatchSourceList": - patchCreation.SourceList = value; - break; - case "PatchOutputPath": - patchCreation.OutputPath = value; - break; - default: - var patchProperty = new Wix.PatchProperty(); + break; + case "ListOfTargetProductCodes": + if (null != value) + { + var targetProductCodes = value.Split(';'); - patchProperty.Name = name; + foreach (var targetProductCodeString in targetProductCodes) + { + var xTargetProductCode = new XElement(Names.TargetProductCodeElement, + new XAttribute("Id", targetProductCodeString)); - patchProperty.Value = value; + this.RootElement.Add(xTargetProductCode); + } + } + break; + case "PatchGUID": + this.RootElement.SetAttributeValue("Id", value); + break; + case "PatchSourceList": + this.RootElement.SetAttributeValue("SourceList", value); + break; + case "PatchOutputPath": + this.RootElement.SetAttributeValue("OutputPath", value); + break; + default: + var patchProperty = new XElement(Names.PatchPropertyElement, + new XAttribute("Name", name), + new XAttribute("Value", value)); - this.core.RootElement.AddChild(patchProperty); - break; + this.RootElement.Add(patchProperty); + break; } } } @@ -7513,8 +6144,8 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var id = Convert.ToString(row[0]); - var value = Convert.ToString(row[1]); + var id = row.FieldAsString(0); + var value = row.FieldAsString(1); if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id) { @@ -7531,9 +6162,9 @@ namespace WixToolset.Core.WindowsInstaller var suppressModulularization = false; if (OutputType.Module == this.OutputType) { - if (propertyId.EndsWith(this.modularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal)) + if (propertyId.EndsWith(this.ModularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal)) { - property = propertyId.Substring(0, propertyId.Length - this.modularizationGuid.Length + 1); + property = propertyId.Substring(0, propertyId.Length - this.ModularizationGuid.Length + 1); } else { @@ -7541,23 +6172,23 @@ namespace WixToolset.Core.WindowsInstaller } } - var specialProperty = this.EnsureProperty(property); + var xSpecialProperty = this.EnsureProperty(property); if (suppressModulularization) { - specialProperty.SuppressModularization = Wix.YesNoType.yes; + xSpecialProperty.SetAttributeValue("SuppressModularization", "yes"); } switch (id) { - case "AdminProperties": - specialProperty.Admin = Wix.YesNoType.yes; - break; - case "MsiHiddenProperties": - specialProperty.Hidden = Wix.YesNoType.yes; - break; - case "SecureCustomProperties": - specialProperty.Secure = Wix.YesNoType.yes; - break; + case "AdminProperties": + xSpecialProperty.SetAttributeValue("Admin", "yes"); + break; + case "MsiHiddenProperties": + xSpecialProperty.SetAttributeValue("Hidden", "yes"); + break; + case "SecureCustomProperties": + xSpecialProperty.SetAttributeValue("Secure", "yes"); + break; } } } @@ -7566,36 +6197,34 @@ namespace WixToolset.Core.WindowsInstaller } else if (OutputType.Product == this.OutputType) { - var product = (Wix.Product)this.core.RootElement; - switch (id) { - case "Manufacturer": - product.Manufacturer = value; - continue; - case "ProductCode": - product.Id = value.ToUpper(CultureInfo.InvariantCulture); - continue; - case "ProductLanguage": - product.Language = value; - continue; - case "ProductName": - product.Name = value; - continue; - case "ProductVersion": - product.Version = value; - continue; - case "UpgradeCode": - product.UpgradeCode = value; - continue; + case "Manufacturer": + this.RootElement.SetAttributeValue("Manufacturer", value); + continue; + case "ProductCode": + this.RootElement.SetAttributeValue("Id", value.ToUpper(CultureInfo.InvariantCulture)); + continue; + case "ProductLanguage": + this.RootElement.SetAttributeValue("Language", value); + continue; + case "ProductName": + this.RootElement.SetAttributeValue("Name", value); + continue; + case "ProductVersion": + this.RootElement.SetAttributeValue("Version", value); + continue; + case "UpgradeCode": + this.RootElement.SetAttributeValue("UpgradeCode", value); + continue; } } if (!this.SuppressUI || "ErrorDialog" != id) { - var property = this.EnsureProperty(id); + var xProperty = this.EnsureProperty(id); - property.Value = value; + xProperty.SetAttributeValue("Value", value); } } } @@ -7608,26 +6237,12 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var category = new Wix.Category(); - - category.Id = Convert.ToString(row[0]); - - category.Qualifier = Convert.ToString(row[1]); + var category = new XElement(Names.CategoryElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Qualifier", row.FieldAsString(1)), + XAttributeIfNotNull("AppData", row, 3)); - if (null != row[3]) - { - category.AppData = Convert.ToString(row[3]); - } - - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2])); - if (null != component) - { - component.AddChild(category); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[2]), "Component")); - } + this.AddChildToParent("Component", category, row, 2); } } @@ -7637,67 +6252,53 @@ namespace WixToolset.Core.WindowsInstaller /// The table to decompile. private void DecompileRadioButtonTable(Table table) { - var radioButtons = new SortedList(); - var radioButtonGroups = new Hashtable(); - foreach (var row in table.Rows) { - var radioButton = new Wix.RadioButton(); - - radioButton.Value = Convert.ToString(row[2]); - - radioButton.X = Convert.ToString(row[3], CultureInfo.InvariantCulture); - - radioButton.Y = Convert.ToString(row[4], CultureInfo.InvariantCulture); - - radioButton.Width = Convert.ToString(row[5], CultureInfo.InvariantCulture); - - radioButton.Height = Convert.ToString(row[6], CultureInfo.InvariantCulture); - - if (null != row[7]) - { - radioButton.Text = Convert.ToString(row[7]); - } + var radioButton = new XElement(Names.RadioButtonElement, + new XAttribute("Value", row.FieldAsString(2)), + new XAttribute("X", row.FieldAsInteger(3)), + new XAttribute("Y", row.FieldAsInteger(4)), + new XAttribute("Width", row.FieldAsInteger(5)), + new XAttribute("Height", row.FieldAsInteger(6)), + XAttributeIfNotNull("Text", row, 7)); - if (null != row[8]) + if (!row.IsColumnNull(8)) { - var help = (Convert.ToString(row[8])).Split('|'); + var help = (row.FieldAsString(8)).Split('|'); if (2 == help.Length) { if (0 < help[0].Length) { - radioButton.ToolTip = help[0]; + radioButton.SetAttributeValue("ToolTip", help[0]); } if (0 < help[1].Length) { - radioButton.Help = help[1]; + radioButton.SetAttributeValue("Help", help[1]); } } } - radioButtons.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[1]), row); - this.core.IndexElement(row, radioButton); + this.IndexElement(row, radioButton); } // nest the radio buttons - foreach (Row row in radioButtons.Values) + var xRadioButtonGroups = new Dictionary(); + foreach (var row in table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1))) { - var radioButton = (Wix.RadioButton)this.core.GetIndexedElement(row); - var radioButtonGroup = (Wix.RadioButtonGroup)radioButtonGroups[Convert.ToString(row[0])]; + var xRadioButton = this.GetIndexedElement(row); - if (null == radioButtonGroup) + if (!xRadioButtonGroups.TryGetValue(row.FieldAsString(0), out var xRadioButtonGroup)) { - radioButtonGroup = new Wix.RadioButtonGroup(); - - radioButtonGroup.Property = Convert.ToString(row[0]); + xRadioButtonGroup = new XElement(Names.RadioButtonGroupElement, + new XAttribute("Property", row.FieldAsString(0))); - this.core.UIElement.AddChild(radioButtonGroup); - radioButtonGroups.Add(Convert.ToString(row[0]), radioButtonGroup); + this.UIElement.Add(xRadioButtonGroup); + xRadioButtonGroups.Add(row.FieldAsString(0), xRadioButtonGroup); } - radioButtonGroup.AddChild(radioButton); + xRadioButtonGroup.Add(xRadioButton); } } @@ -7709,71 +6310,63 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - if (("-" == Convert.ToString(row[3]) || "+" == Convert.ToString(row[3]) || "*" == Convert.ToString(row[3])) && null == row[4]) + if (("-" == row.FieldAsString(3) || "+" == row.FieldAsString(3) || "*" == row.FieldAsString(3)) && row.IsColumnNull(4)) { - var registryKey = new Wix.RegistryKey(); - - registryKey.Id = Convert.ToString(row[0]); + var xRegistryKey = new XElement(Names.RegistryKeyElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2))); if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) { - registryKey.Root = registryRootType; + xRegistryKey.SetAttributeValue("Root", registryRootType); } - registryKey.Key = Convert.ToString(row[2]); - - switch (Convert.ToString(row[3])) + switch (row.FieldAsString(3)) { - case "+": - registryKey.ForceCreateOnInstall = Wix.YesNoType.yes; - break; - case "-": - registryKey.ForceDeleteOnUninstall = Wix.YesNoType.yes; - break; - case "*": - registryKey.ForceDeleteOnUninstall = Wix.YesNoType.yes; - registryKey.ForceCreateOnInstall = Wix.YesNoType.yes; - break; + case "+": + xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); + break; + case "-": + xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); + break; + case "*": + xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); + xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); + break; } - this.core.IndexElement(row, registryKey); + this.IndexElement(row, xRegistryKey); } else { - var registryValue = new Wix.RegistryValue(); - - registryValue.Id = Convert.ToString(row[0]); + var xRegistryValue = new XElement(Names.RegistryValueElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + XAttributeIfNotNull("Name", row, 3)); if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) { - registryValue.Root = registryRootType; - } - - registryValue.Key = Convert.ToString(row[2]); - - if (null != row[3]) - { - registryValue.Name = Convert.ToString(row[3]); + xRegistryValue.SetAttributeValue("Root", registryRootType); } - if (null != row[4]) + if (!row.IsColumnNull(4)) { - var value = Convert.ToString(row[4]); + var value = row.FieldAsString(4); if (value.StartsWith("#x", StringComparison.Ordinal)) { - registryValue.Type = Wix.RegistryValue.TypeType.binary; - registryValue.Value = value.Substring(2); + xRegistryValue.SetAttributeValue("Type", "binary"); + xRegistryValue.SetAttributeValue("Value", value.Substring(2)); } else if (value.StartsWith("#%", StringComparison.Ordinal)) { - registryValue.Type = Wix.RegistryValue.TypeType.expandable; - registryValue.Value = value.Substring(2); + xRegistryValue.SetAttributeValue("Type", "expandable"); + xRegistryValue.SetAttributeValue("Value", value.Substring(2)); } else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal)) { - registryValue.Type = Wix.RegistryValue.TypeType.integer; - registryValue.Value = value.Substring(1); + xRegistryValue.SetAttributeValue("Type", "integer"); + xRegistryValue.SetAttributeValue("Value", value.Substring(1)); } else { @@ -7784,7 +6377,7 @@ namespace WixToolset.Core.WindowsInstaller if (0 <= value.IndexOf("[~]", StringComparison.Ordinal)) { - registryValue.Type = Wix.RegistryValue.TypeType.multiString; + xRegistryValue.SetAttributeValue("Type", "multiString"); if ("[~]" == value) { @@ -7796,39 +6389,38 @@ namespace WixToolset.Core.WindowsInstaller } else if (value.StartsWith("[~]", StringComparison.Ordinal)) { - registryValue.Action = Wix.RegistryValue.ActionType.append; + xRegistryValue.SetAttributeValue("Action", "append"); value = value.Substring(3); } else if (value.EndsWith("[~]", StringComparison.Ordinal)) { - registryValue.Action = Wix.RegistryValue.ActionType.prepend; + xRegistryValue.SetAttributeValue("Action", "prepend"); value = value.Substring(0, value.Length - 3); } var multiValues = NullSplitter.Split(value); foreach (var multiValue in multiValues) { - var multiStringValue = new Wix.MultiStringValue(); - - multiStringValue.Content = multiValue; + var xMultiStringValue = new XElement(Names.MultiStringElement, + new XAttribute("Value", multiValue)); - registryValue.AddChild(multiStringValue); + xRegistryValue.Add(xMultiStringValue); } } else { - registryValue.Type = Wix.RegistryValue.TypeType.@string; - registryValue.Value = value; + xRegistryValue.SetAttributeValue("Type", "string"); + xRegistryValue.SetAttributeValue("Value", value); } } } else { - registryValue.Type = Wix.RegistryValue.TypeType.@string; - registryValue.Value = String.Empty; + xRegistryValue.SetAttributeValue("Type", "string"); + xRegistryValue.SetAttributeValue("Value", String.Empty); } - this.core.IndexElement(row, registryValue); + this.IndexElement(row, xRegistryValue); } } } @@ -7841,72 +6433,66 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var registrySearch = new Wix.RegistrySearch(); - - registrySearch.Id = Convert.ToString(row[0]); - - switch (Convert.ToInt32(row[1])) - { - case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: - registrySearch.Root = Wix.RegistrySearch.RootType.HKCR; - break; - case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: - registrySearch.Root = Wix.RegistrySearch.RootType.HKCU; - break; - case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: - registrySearch.Root = Wix.RegistrySearch.RootType.HKLM; - break; - case WindowsInstallerConstants.MsidbRegistryRootUsers: - registrySearch.Root = Wix.RegistrySearch.RootType.HKU; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } + var xRegistrySearch = new XElement(Names.RegistrySearchElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + XAttributeIfNotNull("Name", row, 3)); - registrySearch.Key = Convert.ToString(row[2]); - - if (null != row[3]) + switch (row.FieldAsInteger(1)) { - registrySearch.Name = Convert.ToString(row[3]); + case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: + xRegistrySearch.SetAttributeValue("Root", "HKCR"); + break; + case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: + xRegistrySearch.SetAttributeValue("Root", "HKCU"); + break; + case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: + xRegistrySearch.SetAttributeValue("Root", "HKLM"); + break; + case WindowsInstallerConstants.MsidbRegistryRootUsers: + xRegistrySearch.SetAttributeValue("Root", "HKU"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; } - if (null == row[4]) + if (row.IsColumnNull(4)) { - registrySearch.Type = Wix.RegistrySearch.TypeType.file; + xRegistrySearch.SetAttributeValue("Type", "file"); } else { - var type = Convert.ToInt32(row[4]); + var type = row.FieldAsInteger(4); if (WindowsInstallerConstants.MsidbLocatorType64bit == (type & WindowsInstallerConstants.MsidbLocatorType64bit)) { - registrySearch.Win64 = Wix.YesNoType.yes; + xRegistrySearch.SetAttributeValue("Win64", "yes"); type &= ~WindowsInstallerConstants.MsidbLocatorType64bit; } else { - registrySearch.Win64 = Wix.YesNoType.no; + xRegistrySearch.SetAttributeValue("Win64", "no"); } switch (type) { - case WindowsInstallerConstants.MsidbLocatorTypeDirectory: - registrySearch.Type = Wix.RegistrySearch.TypeType.directory; - break; - case WindowsInstallerConstants.MsidbLocatorTypeFileName: - registrySearch.Type = Wix.RegistrySearch.TypeType.file; - break; - case WindowsInstallerConstants.MsidbLocatorTypeRawValue: - registrySearch.Type = Wix.RegistrySearch.TypeType.raw; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; + case WindowsInstallerConstants.MsidbLocatorTypeDirectory: + xRegistrySearch.SetAttributeValue("Type", "directory"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeFileName: + xRegistrySearch.SetAttributeValue("Type", "file"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeRawValue: + xRegistrySearch.SetAttributeValue("Type", "raw"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; } } - this.core.IndexElement(row, registrySearch); + this.IndexElement(row, xRegistrySearch); } } @@ -7918,86 +6504,68 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - if (null == row[2]) + if (row.IsColumnNull(2)) { - var removeFolder = new Wix.RemoveFolder(); - - removeFolder.Id = Convert.ToString(row[0]); + var xRemoveFolder = new XElement(Names.RemoveFolderElement, + new XAttribute("Id", row.FieldAsString(0))); // directory/property is set in FinalizeDecompile - switch (Convert.ToInt32(row[4])) + switch (row.FieldAsInteger(4)) { - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: - removeFolder.On = Wix.InstallUninstallType.install; - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: - removeFolder.On = Wix.InstallUninstallType.uninstall; - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: - removeFolder.On = Wix.InstallUninstallType.both; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: + xRemoveFolder.SetAttributeValue("On", "install"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: + xRemoveFolder.SetAttributeValue("On", "uninstall"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: + xRemoveFolder.SetAttributeValue("On", "both"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; } - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(removeFolder); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, removeFolder); + this.AddChildToParent("Component", xRemoveFolder, row, 1); + this.IndexElement(row, xRemoveFolder); } else { - var removeFile = new Wix.RemoveFile(); - - removeFile.Id = Convert.ToString(row[0]); + var xRemoveFile = new XElement(Names.RemoveFileElement, + new XAttribute("Id", row.FieldAsString(0))); - var names = Common.GetNames(Convert.ToString(row[2])); + var names = Common.GetNames(row.FieldAsString(2)); if (null != names[0] && null != names[1]) { - removeFile.ShortName = names[0]; - removeFile.Name = names[1]; + xRemoveFile.SetAttributeValue("ShortName", names[0]); + xRemoveFile.SetAttributeValue("Name", names[1]); } else if (null != names[0]) { - removeFile.Name = names[0]; + xRemoveFile.SetAttributeValue("Name", names[0]); } // directory/property is set in FinalizeDecompile - switch (Convert.ToInt32(row[4])) + switch (row.FieldAsInteger(4)) { - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: - removeFile.On = Wix.InstallUninstallType.install; - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: - removeFile.On = Wix.InstallUninstallType.uninstall; - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: - removeFile.On = Wix.InstallUninstallType.both; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: + xRemoveFile.SetAttributeValue("On", "install"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: + xRemoveFile.SetAttributeValue("On", "uninstall"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: + xRemoveFile.SetAttributeValue("On", "both"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; } - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(removeFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } - this.core.IndexElement(row, removeFile); + this.AddChildToParent("Component", xRemoveFile, row, 1); + this.IndexElement(row, xRemoveFile); } } } @@ -8010,57 +6578,38 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var iniFile = new Wix.IniFile(); + var xIniFile = new XElement(Names.IniFileElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Directory", row, 2), + new XAttribute("Section", row.FieldAsString(3)), + new XAttribute("Key", row.FieldAsString(4)), + XAttributeIfNotNull("Value", row, 5)); - iniFile.Id = Convert.ToString(row[0]); - - var names = Common.GetNames(Convert.ToString(row[1])); + var names = Common.GetNames(row.FieldAsString(1)); if (null != names[0] && null != names[1]) { - iniFile.ShortName = names[0]; - iniFile.Name = names[1]; + xIniFile.SetAttributeValue("ShortName", names[0]); + xIniFile.SetAttributeValue("Name", names[1]); } else if (null != names[0]) { - iniFile.Name = names[0]; - } - - if (null != row[2]) - { - iniFile.Directory = Convert.ToString(row[2]); - } - - iniFile.Section = Convert.ToString(row[3]); - - iniFile.Key = Convert.ToString(row[4]); - - if (null != row[5]) - { - iniFile.Value = Convert.ToString(row[5]); - } - - switch (Convert.ToInt32(row[6])) - { - case WindowsInstallerConstants.MsidbIniFileActionRemoveLine: - iniFile.Action = Wix.IniFile.ActionType.removeLine; - break; - case WindowsInstallerConstants.MsidbIniFileActionRemoveTag: - iniFile.Action = Wix.IniFile.ActionType.removeTag; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; + xIniFile.SetAttributeValue("Name", names[0]); } - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[7])); - if (null != component) - { - component.AddChild(iniFile); - } - else + switch (row.FieldAsInteger(6)) { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[7]), "Component")); + case WindowsInstallerConstants.MsidbIniFileActionRemoveLine: + xIniFile.SetAttributeValue("Action", "removeLine"); + break; + case WindowsInstallerConstants.MsidbIniFileActionRemoveTag: + xIniFile.SetAttributeValue("Action", "removeTag"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; } + + this.AddChildToParent("Component", xIniFile, row, 7); } } @@ -8072,58 +6621,33 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - if ("-" == Convert.ToString(row[3])) + if ("-" == row.FieldAsString(3)) { - var removeRegistryKey = new Wix.RemoveRegistryKey(); - - removeRegistryKey.Id = Convert.ToString(row[0]); + var xRemoveRegistryKey = new XElement(Names.RemoveRegistryKeyElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + new XAttribute("Action", "removeOnInstall")); if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) { - removeRegistryKey.Root = registryRootType; + xRemoveRegistryKey.SetAttributeValue("Root", registryRootType); } - removeRegistryKey.Key = Convert.ToString(row[2]); - - removeRegistryKey.Action = Wix.RemoveRegistryKey.ActionType.removeOnInstall; - - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[4])); - if (null != component) - { - component.AddChild(removeRegistryKey); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[4]), "Component")); - } + this.AddChildToParent("Component", xRemoveRegistryKey, row, 4); } else { - var removeRegistryValue = new Wix.RemoveRegistryValue(); - - removeRegistryValue.Id = Convert.ToString(row[0]); + var xRemoveRegistryValue = new XElement(Names.RemoveRegistryValueElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + XAttributeIfNotNull("Name", row, 3)); if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) { - removeRegistryValue.Root = registryRootType; - } - - removeRegistryValue.Key = Convert.ToString(row[2]); - - if (null != row[3]) - { - removeRegistryValue.Name = Convert.ToString(row[3]); + xRemoveRegistryValue.SetAttributeValue("Root", registryRootType); } - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[4])); - if (null != component) - { - component.AddChild(removeRegistryValue); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[4]), "Component")); - } + this.AddChildToParent("Component", xRemoveRegistryValue, row, 4); } } } @@ -8136,28 +6660,13 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var reserveCost = new Wix.ReserveCost(); - - reserveCost.Id = Convert.ToString(row[0]); - - if (null != row[2]) - { - reserveCost.Directory = Convert.ToString(row[2]); - } - - reserveCost.RunLocal = Convert.ToInt32(row[3]); + var xReserveCost = new XElement(Names.ReserveCostElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Directory", row, 2), + new XAttribute("RunLocal", row.FieldAsString(3)), + new XAttribute("RunFromSource", row.FieldAsString(4))); - reserveCost.RunFromSource = Convert.ToInt32(row[4]); - - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1])); - if (null != component) - { - component.AddChild(reserveCost); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component")); - } + this.AddChildToParent("Component", xReserveCost, row, 4); } } @@ -8169,22 +6678,13 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0])); - - if (null != file) + if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) { - if (null != row[1]) - { - file.SelfRegCost = Convert.ToInt32(row[1]); - } - else - { - file.SelfRegCost = 0; - } + xFile.SetAttributeValue("SelfRegCost", row.IsColumnNull(1) ? 0 : row.FieldAsInteger(1)); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); } } } @@ -8197,90 +6697,72 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var serviceControl = new Wix.ServiceControl(); - - serviceControl.Id = Convert.ToString(row[0]); - - serviceControl.Name = Convert.ToString(row[1]); + var xServiceControl = new XElement(Names.ServiceControlElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(1))); - var eventValue = Convert.ToInt32(row[2]); + var eventValue = row.FieldAsInteger(2); if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart) && WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) { - serviceControl.Start = Wix.InstallUninstallType.both; + xServiceControl.SetAttributeValue("Start", "both"); } else if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart)) { - serviceControl.Start = Wix.InstallUninstallType.install; + xServiceControl.SetAttributeValue("Start", "install"); } else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) { - serviceControl.Start = Wix.InstallUninstallType.uninstall; + xServiceControl.SetAttributeValue("Start", "uninstall"); } if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop) && WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) { - serviceControl.Stop = Wix.InstallUninstallType.both; + xServiceControl.SetAttributeValue("Stop", "both"); } else if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop)) { - serviceControl.Stop = Wix.InstallUninstallType.install; + xServiceControl.SetAttributeValue("Stop", "install"); } else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) { - serviceControl.Stop = Wix.InstallUninstallType.uninstall; + xServiceControl.SetAttributeValue("Stop", "uninstall"); } if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete) && WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) { - serviceControl.Remove = Wix.InstallUninstallType.both; + xServiceControl.SetAttributeValue("Remove", "both"); } else if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete)) { - serviceControl.Remove = Wix.InstallUninstallType.install; + xServiceControl.SetAttributeValue("Remove", "install"); } else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) { - serviceControl.Remove = Wix.InstallUninstallType.uninstall; + xServiceControl.SetAttributeValue("Remove", "uninstall"); } - if (null != row[3]) + if (!row.IsColumnNull(3)) { - var arguments = NullSplitter.Split(Convert.ToString(row[3])); + var arguments = NullSplitter.Split(row.FieldAsString(3)); foreach (var argument in arguments) { - var serviceArgument = new Wix.ServiceArgument(); + var xServiceArgument = new XElement(Names.ServiceArgumentElement, + new XAttribute("Value", argument)); - serviceArgument.Content = argument; - - serviceControl.AddChild(serviceArgument); + xServiceControl.Add(xServiceArgument); } } - if (null != row[4]) + if (!row.IsColumnNull(4)) { - if (0 == Convert.ToInt32(row[4])) - { - serviceControl.Wait = Wix.YesNoType.no; - } - else - { - serviceControl.Wait = Wix.YesNoType.yes; - } + xServiceControl.SetAttributeValue("Wait", row.FieldAsInteger(4) == 0 ? "no" : "yes"); } - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[5])); - if (null != component) - { - component.AddChild(serviceControl); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[5]), "Component")); - } + this.AddChildToParent("Component", xServiceControl, row, 5); } } @@ -8292,21 +6774,20 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var serviceInstall = new Wix.ServiceInstall(); - - serviceInstall.Id = Convert.ToString(row[0]); - - serviceInstall.Name = Convert.ToString(row[1]); - - if (null != row[2]) - { - serviceInstall.DisplayName = Convert.ToString(row[2]); - } - - var serviceType = Convert.ToInt32(row[3]); + var xServiceInstall = new XElement(Names.ServiceInstallElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(1)), + XAttributeIfNotNull("DisplayName", row, 2), + XAttributeIfNotNull("LoadOrderGroup", row, 6), + XAttributeIfNotNull("Account", row, 8), + XAttributeIfNotNull("Password", row, 9), + XAttributeIfNotNull("Arguments", row, 10), + XAttributeIfNotNull("Description", row, 12)); + + var serviceType = row.FieldAsInteger(3); if (WindowsInstallerConstants.MsidbServiceInstallInteractive == (serviceType & WindowsInstallerConstants.MsidbServiceInstallInteractive)) { - serviceInstall.Interactive = Wix.YesNoType.yes; + xServiceInstall.SetAttributeValue("Interactive", "yes"); } if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess) && @@ -8316,110 +6797,77 @@ namespace WixToolset.Core.WindowsInstaller } else if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess)) { - serviceInstall.Type = Wix.ServiceInstall.TypeType.ownProcess; + xServiceInstall.SetAttributeValue("Type", "ownProcess"); } else if (WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) { - serviceInstall.Type = Wix.ServiceInstall.TypeType.shareProcess; + xServiceInstall.SetAttributeValue("Type", "shareProcess"); } - var startType = Convert.ToInt32(row[4]); + var startType = row.FieldAsInteger(4); if (WindowsInstallerConstants.MsidbServiceInstallDisabled == startType) { - serviceInstall.Start = Wix.ServiceInstall.StartType.disabled; + xServiceInstall.SetAttributeValue("Start", "disabled"); } else if (WindowsInstallerConstants.MsidbServiceInstallDemandStart == startType) { - serviceInstall.Start = Wix.ServiceInstall.StartType.demand; + xServiceInstall.SetAttributeValue("Start", "demand"); } else if (WindowsInstallerConstants.MsidbServiceInstallAutoStart == startType) { - serviceInstall.Start = Wix.ServiceInstall.StartType.auto; + xServiceInstall.SetAttributeValue("Start", "auto"); } else { this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); } - var errorControl = Convert.ToInt32(row[5]); + var errorControl = row.FieldAsInteger(5); if (WindowsInstallerConstants.MsidbServiceInstallErrorCritical == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorCritical)) { - serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.critical; + xServiceInstall.SetAttributeValue("ErrorControl", "critical"); } else if (WindowsInstallerConstants.MsidbServiceInstallErrorNormal == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorNormal)) { - serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.normal; + xServiceInstall.SetAttributeValue("ErrorControl", "normal"); } else { - serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.ignore; + xServiceInstall.SetAttributeValue("ErrorControl", "ignore"); } if (WindowsInstallerConstants.MsidbServiceInstallErrorControlVital == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorControlVital)) { - serviceInstall.Vital = Wix.YesNoType.yes; - } - - if (null != row[6]) - { - serviceInstall.LoadOrderGroup = Convert.ToString(row[6]); + xServiceInstall.SetAttributeValue("Vital", "yes"); } - if (null != row[7]) + if (!row.IsColumnNull(7)) { - var dependencies = NullSplitter.Split(Convert.ToString(row[7])); + var dependencies = NullSplitter.Split(row.FieldAsString(7)); foreach (var dependency in dependencies) { if (0 < dependency.Length) { - var serviceDependency = new Wix.ServiceDependency(); + var xServiceDependency = new XElement(Names.ServiceDependencyElement); if (dependency.StartsWith("+", StringComparison.Ordinal)) { - serviceDependency.Group = Wix.YesNoType.yes; - serviceDependency.Id = dependency.Substring(1); + xServiceDependency.SetAttributeValue("Group", "yes"); + xServiceDependency.SetAttributeValue("Id", dependency.Substring(1)); } else { - serviceDependency.Id = dependency; + xServiceDependency.SetAttributeValue("Id", dependency); } - serviceInstall.AddChild(serviceDependency); + xServiceInstall.Add(xServiceDependency); } } } - if (null != row[8]) - { - serviceInstall.Account = Convert.ToString(row[8]); - } - - if (null != row[9]) - { - serviceInstall.Password = Convert.ToString(row[9]); - } - - if (null != row[10]) - { - serviceInstall.Arguments = Convert.ToString(row[10]); - } - - if (null != row[12]) - { - serviceInstall.Description = Convert.ToString(row[12]); - } - - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[11])); - if (null != component) - { - component.AddChild(serviceInstall); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[11]), "Component")); - } - this.core.IndexElement(row, serviceInstall); + this.AddChildToParent("Component", xServiceInstall, row, 11); + this.IndexElement(row, xServiceInstall); } } @@ -8431,38 +6879,34 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var sfpCatalog = new Wix.SFPCatalog(); - - sfpCatalog.Name = Convert.ToString(row[0]); + var xSfpCatalog = new XElement(Names.SFPCatalogElement, + new XAttribute("Name", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); - sfpCatalog.SourceFile = Convert.ToString(row[1]); - - this.core.IndexElement(row, sfpCatalog); + this.IndexElement(row, xSfpCatalog); } // nest the SFPCatalog elements foreach (var row in table.Rows) { - var sfpCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement(row); + var xSfpCatalog = this.GetIndexedElement(row); - if (null != row[2]) + if (!row.IsColumnNull(2)) { - var parentSFPCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement("SFPCatalog", Convert.ToString(row[2])); - - if (null != parentSFPCatalog) + if (this.TryGetIndexedElement("SFPCatalog", out var xParentSFPCatalog, row.FieldAsString(2))) { - parentSFPCatalog.AddChild(sfpCatalog); + xParentSFPCatalog.Add(xSfpCatalog); } else { - sfpCatalog.Dependency = Convert.ToString(row[2]); + xSfpCatalog.SetAttributeValue("Dependency", row.FieldAsString(2)); - this.core.RootElement.AddChild(sfpCatalog); + this.RootElement.Add(xSfpCatalog); } } else { - this.core.RootElement.AddChild(sfpCatalog); + this.RootElement.Add(xSfpCatalog); } } } @@ -8475,107 +6919,72 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var shortcut = new Wix.Shortcut(); - - shortcut.Id = Convert.ToString(row[0]); - - shortcut.Directory = Convert.ToString(row[1]); - - var names = Common.GetNames(Convert.ToString(row[2])); + var xShortcut = new XElement(Names.ShortcutElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Directory", row.FieldAsString(1)), + XAttributeIfNotNull("Arguments", row, 5), + XAttributeIfNotNull("Description", row, 6), + XAttributeIfNotNull("Hotkey", row, 7), + XAttributeIfNotNull("Icon", row, 8), + XAttributeIfNotNull("IconIndex", row, 9), + XAttributeIfNotNull("WorkingDirectory", row, 11)); + + var names = Common.GetNames(row.FieldAsString(2)); if (null != names[0] && null != names[1]) { - shortcut.ShortName = names[0]; - shortcut.Name = names[1]; + xShortcut.SetAttributeValue("ShortName", names[0]); + xShortcut.SetAttributeValue("Name", names[1]); } else if (null != names[0]) { - shortcut.Name = names[0]; - } - - if (null != row[5]) - { - shortcut.Arguments = Convert.ToString(row[5]); - } - - if (null != row[6]) - { - shortcut.Description = Convert.ToString(row[6]); - } - - if (null != row[7]) - { - shortcut.Hotkey = Convert.ToInt32(row[7]); - } - - if (null != row[8]) - { - shortcut.Icon = Convert.ToString(row[8]); - } - - if (null != row[9]) - { - shortcut.IconIndex = Convert.ToInt32(row[9]); + xShortcut.SetAttributeValue("Name", names[0]); } - if (null != row[10]) + if (!row.IsColumnNull(10)) { - switch (Convert.ToInt32(row[10])) + switch (row.FieldAsInteger(10)) { - case 1: - shortcut.Show = Wix.Shortcut.ShowType.normal; - break; - case 3: - shortcut.Show = Wix.Shortcut.ShowType.maximized; - break; - case 7: - shortcut.Show = Wix.Shortcut.ShowType.minimized; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10])); - break; + case 1: + xShortcut.SetAttributeValue("Show", "normal"); + break; + case 3: + xShortcut.SetAttributeValue("Show", "maximized"); + break; + case 7: + xShortcut.SetAttributeValue("Show", "minimized"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10])); + break; } } - if (null != row[11]) - { - shortcut.WorkingDirectory = Convert.ToString(row[11]); - } - // Only try to read the MSI 4.0-specific columns if they actually exist if (15 < row.Fields.Length) { - if (null != row[12]) + if (!row.IsColumnNull(12)) { - shortcut.DisplayResourceDll = Convert.ToString(row[12]); + xShortcut.SetAttributeValue("DisplayResourceDll", row.FieldAsString(12)); } if (null != row[13]) { - shortcut.DisplayResourceId = Convert.ToInt32(row[13]); + xShortcut.SetAttributeValue("DisplayResourceId", row.FieldAsInteger(13)); } if (null != row[14]) { - shortcut.DescriptionResourceDll = Convert.ToString(row[14]); + xShortcut.SetAttributeValue("DescriptionResourceDll", row.FieldAsString(14)); } if (null != row[15]) { - shortcut.DescriptionResourceId = Convert.ToInt32(row[15]); + xShortcut.SetAttributeValue("DescriptionResourceId", row.FieldAsInteger(15)); } } - var component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[3])); - if (null != component) - { - component.AddChild(shortcut); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[3]), "Component")); - } - - this.core.IndexElement(row, shortcut); + this.AddChildToParent("Component", xShortcut, row, 3); + this.IndexElement(row, xShortcut); } } @@ -8587,65 +6996,44 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var fileSearch = new Wix.FileSearch(); + var fileSearch = new XElement(Names.FileSearchElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("MinVersion", row, 2), + XAttributeIfNotNull("MaxVersion", row, 3), + XAttributeIfNotNull("MinSize", row, 4), + XAttributeIfNotNull("MaxSize", row, 5), + XAttributeIfNotNull("Languages", row, 8)); - fileSearch.Id = Convert.ToString(row[0]); - - var names = Common.GetNames(Convert.ToString(row[1])); + var names = Common.GetNames(row.FieldAsString(1)); if (null != names[0]) { // it is permissable to just have a long name - if (!this.core.IsValidShortFilename(names[0], false) && null == names[1]) + if (!Common.IsValidShortFilename(names[0], false) && null == names[1]) { - fileSearch.Name = names[0]; + fileSearch.SetAttributeValue("Name", names[0]); } else { - fileSearch.ShortName = names[0]; + fileSearch.SetAttributeValue("ShortName", names[0]); } } if (null != names[1]) { - fileSearch.Name = names[1]; - } - - if (null != row[2]) - { - fileSearch.MinVersion = Convert.ToString(row[2]); - } - - if (null != row[3]) - { - fileSearch.MaxVersion = Convert.ToString(row[3]); - } - - if (null != row[4]) - { - fileSearch.MinSize = Convert.ToInt32(row[4]); - } - - if (null != row[5]) - { - fileSearch.MaxSize = Convert.ToInt32(row[5]); - } - - if (null != row[6]) - { - fileSearch.MinDate = this.core.ConvertIntegerToDateTime(Convert.ToInt32(row[6])); + fileSearch.SetAttributeValue("Name", names[1]); } - if (null != row[7]) + if (!row.IsColumnNull(6)) { - fileSearch.MaxDate = this.core.ConvertIntegerToDateTime(Convert.ToInt32(row[7])); + fileSearch.SetAttributeValue("MinDate", ConvertIntegerToDateTime(row.FieldAsInteger(6))); } - if (null != row[8]) + if (!row.IsColumnNull(7)) { - fileSearch.Languages = Convert.ToString(row[8]); + fileSearch.SetAttributeValue("MaxDate", ConvertIntegerToDateTime(row.FieldAsInteger(7))); } - this.core.IndexElement(row, fileSearch); + this.IndexElement(row, fileSearch); } } @@ -8657,69 +7045,55 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var targetFile = (Wix.TargetFile)this.patchTargetFiles[row[0]]; - if (null == targetFile) + if (!this.PatchTargetFiles.TryGetValue(row.FieldAsString(0), out var xPatchTargetFile)) { - targetFile = new Wix.TargetFile(); + xPatchTargetFile = new XElement(Names.TargetFileElement, + new XAttribute("Id", row.FieldAsString(1))); - targetFile.Id = Convert.ToString(row[1]); - - var targetImage = (Wix.TargetImage)this.core.GetIndexedElement("TargetImages", Convert.ToString(row[0])); - if (null != targetImage) + if (this.TryGetIndexedElement("TargetImages", out var xTargetImage, row.FieldAsString(0))) { - targetImage.AddChild(targetFile); + xTargetImage.Add(xPatchTargetFile); } else { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", Convert.ToString(row[0]), "TargetImages")); + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); } - this.patchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), targetFile); - } - - if (null != row[2]) - { - var symbolPaths = (Convert.ToString(row[2])).Split(';'); - - foreach (var symbolPathString in symbolPaths) - { - var symbolPath = new Wix.SymbolPath(); - symbolPath.Path = symbolPathString; - - targetFile.AddChild(symbolPath); - } + this.PatchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), xPatchTargetFile); } - if (null != row[3] && null != row[4]) + AddSymbolPaths(row, 2, xPatchTargetFile); + + if (!row.IsColumnNull(3) && !row.IsColumnNull(4)) { - var ignoreOffsets = (Convert.ToString(row[3])).Split(','); - var ignoreLengths = (Convert.ToString(row[4])).Split(','); + var ignoreOffsets = row.FieldAsString(3).Split(','); + var ignoreLengths = row.FieldAsString(4).Split(','); if (ignoreOffsets.Length == ignoreLengths.Length) { for (var i = 0; i < ignoreOffsets.Length; i++) { - var ignoreRange = new Wix.IgnoreRange(); + var xIgnoreRange = new XElement(Names.IgnoreRangeElement); if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) { - ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i].Substring(2), 16); + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); } else { - ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture); + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); } if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) { - ignoreRange.Length = Convert.ToInt32(ignoreLengths[i].Substring(2), 16); + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); } else { - ignoreRange.Length = Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture); + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); } - targetFile.AddChild(ignoreRange); + xPatchTargetFile.Add(xIgnoreRange); } } else @@ -8727,7 +7101,7 @@ namespace WixToolset.Core.WindowsInstaller // TODO: warn } } - else if (null != row[3] || null != row[4]) + else if (!row.IsColumnNull(3) || !row.IsColumnNull(4)) { // TODO: warn about mismatch between columns } @@ -8744,48 +7118,21 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var targetImage = new Wix.TargetImage(); - - targetImage.Id = Convert.ToString(row[0]); - - targetImage.SourceFile = Convert.ToString(row[1]); - - if (null != row[2]) - { - var symbolPaths = (Convert.ToString(row[3])).Split(';'); - - foreach (var symbolPathString in symbolPaths) - { - var symbolPath = new Wix.SymbolPath(); - - symbolPath.Path = symbolPathString; - - targetImage.AddChild(symbolPath); - } - } - - targetImage.Order = Convert.ToInt32(row[4]); + var xTargetImage = new XElement(Names.TargetImageElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1)), + new XAttribute("Order", row.FieldAsInteger(4)), + XAttributeIfNotNull("Validation", row, 5)); - if (null != row[5]) - { - targetImage.Validation = Convert.ToString(row[5]); - } + AddSymbolPaths(row, 2, xTargetImage); - if (0 != Convert.ToInt32(row[6])) + if (0 != row.FieldAsInteger(6)) { - targetImage.IgnoreMissingFiles = Wix.YesNoType.yes; + xTargetImage.SetAttributeValue("IgnoreMissingFiles", "yes"); } - var upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[3])); - if (null != upgradeImage) - { - upgradeImage.AddChild(targetImage); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[3]), "UpgradedImages")); - } - this.core.IndexElement(row, targetImage); + this.AddChildToParent("UpgradedImages", xTargetImage, row, 3); + this.IndexElement(row, xTargetImage); } } @@ -8797,51 +7144,46 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var textStyle = new Wix.TextStyle(); - - textStyle.Id = Convert.ToString(row[0]); - - textStyle.FaceName = Convert.ToString(row[1]); - - textStyle.Size = Convert.ToString(row[2]); + var xTextStyle = new XElement(Names.TextStyleElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("FaceName", row.FieldAsString(1)), + new XAttribute("Size", row.FieldAsString(2))); - if (null != row[3]) + if (!row.IsColumnNull(3)) { - var color = Convert.ToInt32(row[3]); + var color = row.FieldAsInteger(3); - textStyle.Red = color & 0xFF; - - textStyle.Green = (color & 0xFF00) >> 8; - - textStyle.Blue = (color & 0xFF0000) >> 16; + xTextStyle.SetAttributeValue("Red", color & 0xFF); + xTextStyle.SetAttributeValue("Green", (color & 0xFF00) >> 8); + xTextStyle.SetAttributeValue("Blue", (color & 0xFF0000) >> 16); } - if (null != row[4]) + if (!row.IsColumnNull(4)) { - var styleBits = Convert.ToInt32(row[4]); + var styleBits = row.FieldAsInteger(4); if (WindowsInstallerConstants.MsidbTextStyleStyleBitsBold == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsBold)) { - textStyle.Bold = Wix.YesNoType.yes; + xTextStyle.SetAttributeValue("Bold", "yes"); } if (WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic)) { - textStyle.Italic = Wix.YesNoType.yes; + xTextStyle.SetAttributeValue("Italic", "yes"); } if (WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline)) { - textStyle.Underline = Wix.YesNoType.yes; + xTextStyle.SetAttributeValue("Underline", "yes"); } if (WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike)) { - textStyle.Strike = Wix.YesNoType.yes; + xTextStyle.SetAttributeValue("Strike", "yes"); } } - this.core.UIElement.AddChild(textStyle); + this.UIElement.Add(xTextStyle); } } @@ -8853,44 +7195,34 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var typeLib = new Wix.TypeLib(); - - typeLib.Id = Convert.ToString(row[0]); - - typeLib.Advertise = Wix.YesNoType.yes; + var id = row.FieldAsString(0); + var xTypeLib = new XElement(Names.TypeLibElement, + new XAttribute("Advertise", "yes"), + new XAttribute("Id", id), + new XAttribute("Language", row.FieldAsInteger(1)), + XAttributeIfNotNull("Description", row, 4), + XAttributeIfNotNull("HelpDirectory", row, 5)); - typeLib.Language = Convert.ToInt32(row[1]); - - if (null != row[3]) + if (!row.IsColumnNull(3)) { - var version = Convert.ToInt32(row[3]); + var version = row.FieldAsInteger(3); if (65536 == version) { - this.Messaging.Write(WarningMessages.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, typeLib.Id)); + this.Messaging.Write(WarningMessages.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, id)); } - typeLib.MajorVersion = ((version & 0xFFFF00) >> 8); - typeLib.MinorVersion = (version & 0xFF); - } - - if (null != row[4]) - { - typeLib.Description = Convert.ToString(row[4]); - } - - if (null != row[5]) - { - typeLib.HelpDirectory = Convert.ToString(row[5]); + xTypeLib.SetAttributeValue("MajorVersion", (version & 0xFFFF00) >> 8); + xTypeLib.SetAttributeValue("MinorVersion", version & 0xFF); } - if (null != row[7]) + if (!row.IsColumnNull(7)) { - typeLib.Cost = Convert.ToInt32(row[7]); + xTypeLib.SetAttributeValue("Cost", row.FieldAsInteger(7)); } // nested under the appropriate File element in FinalizeFileTable - this.core.IndexElement(row, typeLib); + this.IndexElement(row, xTypeLib); } } @@ -8900,7 +7232,7 @@ namespace WixToolset.Core.WindowsInstaller /// The table to decompile. private void DecompileUpgradeTable(Table table) { - var upgradeElements = new Hashtable(); + var xUpgrades = new Dictionary(); foreach (UpgradeRow upgradeRow in table.Rows) { @@ -8909,74 +7241,70 @@ namespace WixToolset.Core.WindowsInstaller continue; // MajorUpgrade rows processed in FinalizeUpgradeTable } - var upgrade = (Wix.Upgrade)upgradeElements[upgradeRow.UpgradeCode]; - - // create the parent Upgrade element if it doesn't already exist - if (null == upgrade) + if (!xUpgrades.TryGetValue(upgradeRow.UpgradeCode, out var xUpgrade)) { - upgrade = new Wix.Upgrade(); + xUpgrade = new XElement(Names.UpgradeElement, + new XAttribute("Id", upgradeRow.UpgradeCode)); - upgrade.Id = upgradeRow.UpgradeCode; - - this.core.RootElement.AddChild(upgrade); - upgradeElements.Add(upgrade.Id, upgrade); + this.RootElement.Add(xUpgrade); + xUpgrades.Add(upgradeRow.UpgradeCode, xUpgrade); } - var upgradeVersion = new Wix.UpgradeVersion(); + var xUpgradeVersion = new XElement(Names.UpgradeVersionElement, + new XAttribute("Id", upgradeRow.UpgradeCode), + new XAttribute("Property", upgradeRow.ActionProperty)); if (null != upgradeRow.VersionMin) { - upgradeVersion.Minimum = upgradeRow.VersionMin; + xUpgradeVersion.SetAttributeValue("Minimum", upgradeRow.VersionMin); } if (null != upgradeRow.VersionMax) { - upgradeVersion.Maximum = upgradeRow.VersionMax; + xUpgradeVersion.SetAttributeValue("Maximum", upgradeRow.VersionMax); } if (null != upgradeRow.Language) { - upgradeVersion.Language = upgradeRow.Language; + xUpgradeVersion.SetAttributeValue("Language", upgradeRow.Language); } if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) { - upgradeVersion.MigrateFeatures = Wix.YesNoType.yes; + xUpgradeVersion.SetAttributeValue("MigrateFeatures", "yes"); } if (WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect)) { - upgradeVersion.OnlyDetect = Wix.YesNoType.yes; + xUpgradeVersion.SetAttributeValue("OnlyDetect", "yes"); } if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) { - upgradeVersion.IgnoreRemoveFailure = Wix.YesNoType.yes; + xUpgradeVersion.SetAttributeValue("IgnoreRemoveFailure", "yes"); } if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive)) { - upgradeVersion.IncludeMinimum = Wix.YesNoType.yes; + xUpgradeVersion.SetAttributeValue("IncludeMinimum", "yes"); } if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) { - upgradeVersion.IncludeMaximum = Wix.YesNoType.yes; + xUpgradeVersion.SetAttributeValue("IncludeMaximum", "yes"); } if (WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive)) { - upgradeVersion.ExcludeLanguages = Wix.YesNoType.yes; + xUpgradeVersion.SetAttributeValue("ExcludeLanguages", "yes"); } if (null != upgradeRow.Remove) { - upgradeVersion.RemoveFeatures = upgradeRow.Remove; + xUpgradeVersion.SetAttributeValue("RemoveFeatures", upgradeRow.Remove); } - upgradeVersion.Property = upgradeRow.ActionProperty; - - upgrade.AddChild(upgradeVersion); + xUpgrade.Add(xUpgradeVersion); } } @@ -8988,45 +7316,23 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var upgradeFile = new Wix.UpgradeFile(); - - upgradeFile.File = Convert.ToString(row[1]); - - if (null != row[2]) - { - var symbolPaths = (Convert.ToString(row[2])).Split(';'); - - foreach (var symbolPathString in symbolPaths) - { - var symbolPath = new Wix.SymbolPath(); - - symbolPath.Path = symbolPathString; + var xUpgradeFile = new XElement(Names.UpgradeFileElement, + new XAttribute("File", row.FieldAsString(1)), + new XAttribute("Ignore", "no")); - upgradeFile.AddChild(symbolPath); - } - } + AddSymbolPaths(row, 2, xUpgradeFile); - if (null != row[3] && 1 == Convert.ToInt32(row[3])) + if (!row.IsColumnNull(3) && 1 == row.FieldAsInteger(3)) { - upgradeFile.AllowIgnoreOnError = Wix.YesNoType.yes; + xUpgradeFile.SetAttributeValue("AllowIgnoreOnError", "yes"); } - if (null != row[4] && 0 != Convert.ToInt32(row[4])) + if (!row.IsColumnNull(4) && 0 != row.FieldAsInteger(4)) { - upgradeFile.WholeFile = Wix.YesNoType.yes; + xUpgradeFile.SetAttributeValue("WholeFile", "yes"); } - upgradeFile.Ignore = Wix.YesNoType.no; - - var upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[0])); - if (null != upgradeImage) - { - upgradeImage.AddChild(upgradeFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[0]), "UpgradedImages")); - } + this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); } } @@ -9038,23 +7344,13 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - if ("*" != Convert.ToString(row[0])) + if ("*" != row.FieldAsString(0)) { - var upgradeFile = new Wix.UpgradeFile(); + var xUpgradeFile = new XElement(Names.UpgradeFileElement, + new XAttribute("File", row.FieldAsString(1)), + new XAttribute("Ignore", "yes")); - upgradeFile.File = Convert.ToString(row[1]); - - upgradeFile.Ignore = Wix.YesNoType.yes; - - var upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[0])); - if (null != upgradeImage) - { - upgradeImage.AddChild(upgradeFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[0]), "UpgradedImages")); - } + this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); } else { @@ -9071,41 +7367,31 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var upgradeImage = new Wix.UpgradeImage(); + var xUpgradeImage = new XElement(Names.UpgradeImageElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1)), + XAttributeIfNotNull("SourcePatch", row, 2)); - upgradeImage.Id = Convert.ToString(row[0]); + AddSymbolPaths(row, 3, xUpgradeImage); - upgradeImage.SourceFile = Convert.ToString(row[1]); + this.AddChildToParent("ImageFamilies", xUpgradeImage, row, 4); + this.IndexElement(row, xUpgradeImage); + } + } - if (null != row[2]) - { - upgradeImage.SourcePatch = Convert.ToString(row[2]); - } + private static void AddSymbolPaths(Row row, int column, XElement xParent) + { + if (!row.IsColumnNull(column)) + { + var symbolPaths = row.FieldAsString(column).Split(';'); - if (null != row[3]) + foreach (var symbolPath in symbolPaths) { - var symbolPaths = (Convert.ToString(row[3])).Split(';'); - - foreach (var symbolPathString in symbolPaths) - { - var symbolPath = new Wix.SymbolPath(); - - symbolPath.Path = symbolPathString; + var xSymbolPath = new XElement(Names.SymbolPathElement, + new XAttribute("Path", symbolPath)); - upgradeImage.AddChild(symbolPath); - } - } - - var family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[4])); - if (null != family) - { - family.AddChild(upgradeImage); + xParent.Add(xSymbolPath); } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[4]), "ImageFamilies")); - } - this.core.IndexElement(row, upgradeImage); } } @@ -9117,13 +7403,11 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var uiText = new Wix.UIText(); - - uiText.Id = Convert.ToString(row[0]); + var xUiText = new XElement(Names.UITextElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Value", row.FieldAsString(1))); - uiText.Content = Convert.ToString(row[1]); - - this.core.UIElement.AddChild(uiText); + this.UIElement.Add(xUiText); } } @@ -9135,26 +7419,13 @@ namespace WixToolset.Core.WindowsInstaller { foreach (var row in table.Rows) { - var verb = new Wix.Verb(); - - verb.Id = Convert.ToString(row[1]); - - if (null != row[2]) - { - verb.Sequence = Convert.ToInt32(row[2]); - } - - if (null != row[3]) - { - verb.Command = Convert.ToString(row[3]); - } + var verb = new XElement(Names.VerbElement, + new XAttribute("Id", row.FieldAsString(1)), + XAttributeIfNotNull("Sequence", row, 2), + XAttributeIfNotNull("Command", row, 3), + XAttributeIfNotNull("Argument", row, 4)); - if (null != row[4]) - { - verb.Argument = Convert.ToString(row[4]); - } - - this.core.IndexElement(row, verb); + this.IndexElement(row, verb); } } @@ -9166,29 +7437,29 @@ namespace WixToolset.Core.WindowsInstaller /// The field containing the root value. /// The strongly-typed representation of the root. /// true if the value could be converted; false otherwise. - private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out Wix.RegistryRootType registryRootType) + private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out string registryRootType) { switch (Convert.ToInt32(field.Data)) { - case (-1): - registryRootType = Wix.RegistryRootType.HKMU; - return true; - case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: - registryRootType = Wix.RegistryRootType.HKCR; - return true; - case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: - registryRootType = Wix.RegistryRootType.HKCU; - return true; - case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: - registryRootType = Wix.RegistryRootType.HKLM; - return true; - case WindowsInstallerConstants.MsidbRegistryRootUsers: - registryRootType = Wix.RegistryRootType.HKU; - return true; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data)); - registryRootType = Wix.RegistryRootType.HKCR; // assign anything to satisfy the out parameter - return false; + case (-1): + registryRootType = "HKMU"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: + registryRootType = "HKCR"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: + registryRootType = "HKCU"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: + registryRootType = "HKLM"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootUsers: + registryRootType = "HKU"; + return true; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data)); + registryRootType = null; // assign anything to satisfy the out parameter + return false; } } @@ -9206,11 +7477,9 @@ namespace WixToolset.Core.WindowsInstaller var featureField = row.Fields[featureColumnIndex]; var componentField = row.Fields[componentColumnIndex]; - var componentRef = (Wix.ComponentRef)this.core.GetIndexedElement("FeatureComponents", Convert.ToString(featureField.Data), Convert.ToString(componentField.Data)); - - if (null != componentRef) + if (this.TryGetIndexedElement("FeatureComponents", out var xComponentRef, Convert.ToString(featureField.Data), Convert.ToString(componentField.Data))) { - componentRef.Primary = Wix.YesNoType.yes; + xComponentRef.SetAttributeValue("Primary", "yes"); } else { @@ -9223,7 +7492,7 @@ namespace WixToolset.Core.WindowsInstaller /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it. /// /// The collection of all tables. - private static Wix.MajorUpgrade.ScheduleType DetermineMajorUpgradeScheduling(TableIndexedCollection tables) + private static string DetermineMajorUpgradeScheduling(TableIndexedCollection tables) { var sequenceRemoveExistingProducts = 0; var sequenceInstallValidate = 0; @@ -9239,30 +7508,30 @@ namespace WixToolset.Core.WindowsInstaller for (var i = 0; i < installExecuteSequenceTable.Rows.Count; i++) { var row = installExecuteSequenceTable.Rows[i]; - var action = Convert.ToString(row[0]); - var sequence = Convert.ToInt32(row[2]); + var action = row.FieldAsString(0); + var sequence = row.FieldAsInteger(2); switch (action) { - case "RemoveExistingProducts": - sequenceRemoveExistingProducts = sequence; - removeExistingProductsRow = i; - break; - case "InstallValidate": - sequenceInstallValidate = sequence; - break; - case "InstallInitialize": - sequenceInstallInitialize = sequence; - break; - case "InstallExecute": - sequenceInstallExecute = sequence; - break; - case "InstallExecuteAgain": - sequenceInstallExecuteAgain = sequence; - break; - case "InstallFinalize": - sequenceInstallFinalize = sequence; - break; + case "RemoveExistingProducts": + sequenceRemoveExistingProducts = sequence; + removeExistingProductsRow = i; + break; + case "InstallValidate": + sequenceInstallValidate = sequence; + break; + case "InstallInitialize": + sequenceInstallInitialize = sequence; + break; + case "InstallExecute": + sequenceInstallExecute = sequence; + break; + case "InstallExecuteAgain": + sequenceInstallExecuteAgain = sequence; + break; + case "InstallFinalize": + sequenceInstallFinalize = sequence; + break; } } @@ -9271,23 +7540,23 @@ namespace WixToolset.Core.WindowsInstaller if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize) { - return Wix.MajorUpgrade.ScheduleType.afterInstallValidate; + return "afterInstallValidate"; } else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute) { - return Wix.MajorUpgrade.ScheduleType.afterInstallInitialize; + return "afterInstallInitialize"; } else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain) { - return Wix.MajorUpgrade.ScheduleType.afterInstallExecute; + return "afterInstallExecute"; } else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize) { - return Wix.MajorUpgrade.ScheduleType.afterInstallExecuteAgain; + return "afterInstallExecuteAgain"; } else { - return Wix.MajorUpgrade.ScheduleType.afterInstallFinalize; + return "afterInstallFinalize"; } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/DecompilerCore.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/DecompilerCore.cs deleted file mode 100644 index 17c97e09..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/DecompilerCore.cs +++ /dev/null @@ -1,110 +0,0 @@ -// 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 -{ - using System; - using System.Collections; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using Wix = WixToolset.Data.Serialize; - - /// - /// The base of the decompiler. Holds some variables used by the decompiler and extensions, - /// as well as some utility methods. - /// - internal class DecompilerCore - { - private readonly Hashtable elements; - private Wix.UI uiElement; - - /// - /// Instantiate a new decompiler core. - /// - /// The root element of the decompiled database. - /// The message handler. - internal DecompilerCore(Wix.IParentElement rootElement) - { - this.elements = new Hashtable(); - this.RootElement = rootElement; - } - - /// - /// Gets the root element of the decompiled output. - /// - /// The root element of the decompiled output. - public Wix.IParentElement RootElement { get; } - - /// - /// Gets the UI element. - /// - /// The UI element. - public Wix.UI UIElement - { - get - { - if (null == this.uiElement) - { - this.uiElement = new Wix.UI(); - this.RootElement.AddChild(this.uiElement); - } - - return this.uiElement; - } - } - - /// - /// Verifies if a filename is a valid short filename. - /// - /// Filename to verify. - /// true if wildcards are allowed in the filename. - /// True if the filename is a valid short filename - public virtual bool IsValidShortFilename(string filename, bool allowWildcards) - { - return false; - } - - /// - /// Convert an Int32 into a DateTime. - /// - /// The Int32 value. - /// The DateTime. - public DateTime ConvertIntegerToDateTime(int value) - { - var date = value / 65536; - var time = value % 65536; - - return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2); - } - - /// - /// Gets the element corresponding to the row it came from. - /// - /// The row corresponding to the element. - /// The indexed element. - public Wix.ISchemaElement GetIndexedElement(Row row) - { - return this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); - } - - /// - /// Gets the element corresponding to the primary key of the given table. - /// - /// The table corresponding to the element. - /// The primary key corresponding to the element. - /// The indexed element. - public Wix.ISchemaElement GetIndexedElement(string table, params string[] primaryKey) - { - return (Wix.ISchemaElement)this.elements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))]; - } - - /// - /// Index an element by its corresponding row. - /// - /// The row corresponding to the element. - /// The element to index. - public void IndexElement(Row row, Wix.ISchemaElement element) - { - this.elements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs new file mode 100644 index 00000000..63ab5cd3 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs @@ -0,0 +1,158 @@ +namespace WixToolset.Core.WindowsInstaller.Decompile +{ + using System.Xml.Linq; + + internal static class Names + { + public static readonly XNamespace WxsNamespace = "http://wixtoolset.org/schemas/v4/wxs"; + + public static readonly XName WixElement = WxsNamespace + "Wix"; + + public static readonly XName ProductElement = WxsNamespace + "Product"; + public static readonly XName ModuleElement = WxsNamespace + "Module"; + public static readonly XName PatchCreationElement = WxsNamespace + "PatchCreation"; + + public static readonly XName CustomElement = WxsNamespace + "Custom"; + + public static readonly XName AdminExecuteSequenceElement = WxsNamespace + "AdminExecuteSequence"; + public static readonly XName AdminUISequenceElement = WxsNamespace + "AdminUISequence"; + public static readonly XName AdvertiseExecuteSequenceElement = WxsNamespace + "AdvertiseExecuteSequence"; + public static readonly XName InstallExecuteSequenceElement = WxsNamespace + "InstallExecuteSequence"; + public static readonly XName InstallUISequenceElement = WxsNamespace + "InstallUISequence"; + + public static readonly XName AppSearchElement = WxsNamespace + "AppSearch"; + + public static readonly XName PropertyElement = WxsNamespace + "Property"; + + public static readonly XName ProtectRangeElement = WxsNamespace + "ProtectRange"; + public static readonly XName ProtectFileElement = WxsNamespace + "ProtectFile"; + + public static readonly XName FileElement = WxsNamespace + "File"; + + public static readonly XName EnsureTableElement = WxsNamespace + "EnsureTable"; + public static readonly XName PackageElement = WxsNamespace + "Package"; + public static readonly XName PatchInformationElement = WxsNamespace + "PatchInformation"; + + public static readonly XName ProgressTextElement = WxsNamespace + "ProgressText"; + public static readonly XName UIElement = WxsNamespace + "UI"; + + public static readonly XName AppIdElement = WxsNamespace + "AppId"; + + public static readonly XName ControlElement = WxsNamespace + "Control"; + + public static readonly XName BillboardElement = WxsNamespace + "Billboard"; + public static readonly XName BillboardActionElement = WxsNamespace + "BillboardAction"; + + public static readonly XName BinaryElement = WxsNamespace + "Binary"; + + public static readonly XName ClassElement = WxsNamespace + "Class"; + + public static readonly XName FileTypeMaskElement = WxsNamespace + "FileTypeMask"; + + public static readonly XName ComboBoxElement = WxsNamespace + "ComboBox"; + + public static readonly XName ListItemElement = WxsNamespace + "ListItem"; + + public static readonly XName ConditionElement = WxsNamespace + "Condition"; + public static readonly XName PublishElement = WxsNamespace + "Publish"; + public static readonly XName CustomTableElement = WxsNamespace + "CustomTable"; + public static readonly XName ColumnElement = WxsNamespace + "Column"; + public static readonly XName RowElement = WxsNamespace + "Row"; + public static readonly XName DataElement = WxsNamespace + "Data"; + public static readonly XName CreateFolderElement = WxsNamespace + "CreateFolder"; + + public static readonly XName CustomActionElement = WxsNamespace + "CustomAction"; + + public static readonly XName ComponentSearchElement = WxsNamespace + "ComponentSearch"; + public static readonly XName ComponentElement = WxsNamespace + "Component"; + + public static readonly XName LevelElement = WxsNamespace + "Level"; + public static readonly XName DialogElement = WxsNamespace + "Dialog"; + public static readonly XName DirectoryElement = WxsNamespace + "Directory"; + public static readonly XName DirectorySearchElement = WxsNamespace + "DirectorySearch"; + public static readonly XName CopyFileElement = WxsNamespace + "CopyFile"; + public static readonly XName EnvironmentElement = WxsNamespace + "Environment"; + public static readonly XName ErrorElement = WxsNamespace + "Error"; + public static readonly XName SubscribeElement = WxsNamespace + "Subscribe"; + public static readonly XName ExtensionElement = WxsNamespace + "Extension"; + public static readonly XName ExternalFileElement = WxsNamespace + "ExternalFile"; + public static readonly XName SymbolPathElement = WxsNamespace + "SymbolPath"; + public static readonly XName IgnoreRangeElement = WxsNamespace + "IgnoreRange"; + + public static readonly XName FeatureElement = WxsNamespace + "Feature"; + public static readonly XName ComponentRefElement = WxsNamespace + "ComponentRef"; + public static readonly XName SFPFileElement = WxsNamespace + "SFPFile"; + public static readonly XName IconElement = WxsNamespace + "Icon"; + public static readonly XName FamilyElement = WxsNamespace + "Family"; + public static readonly XName IniFileElement = WxsNamespace + "IniFile"; + public static readonly XName IniFileSearchElement = WxsNamespace + "IniFileSearch"; + public static readonly XName IsolateComponentElement = WxsNamespace + "IsolateComponent"; + public static readonly XName LaunchElement = WxsNamespace + "Launch"; + public static readonly XName ListBoxElement = WxsNamespace + "ListBox"; + public static readonly XName ListViewElement = WxsNamespace + "ListView"; + public static readonly XName PermissionElement = WxsNamespace + "Permission"; + public static readonly XName MediaElement = WxsNamespace + "Media"; + public static readonly XName MIMEElement = WxsNamespace + "MIME"; + public static readonly XName ConfigurationElement = WxsNamespace + "Configuration"; + public static readonly XName DependencyElement = WxsNamespace + "Dependency"; + public static readonly XName ExclusionElement = WxsNamespace + "Exclusion"; + public static readonly XName IgnoreTableElement = WxsNamespace + "IgnoreTable"; + public static readonly XName SubstitutionElement = WxsNamespace + "Substitution"; + public static readonly XName DigitalCertificateElement = WxsNamespace + "DigitalCertificate"; + public static readonly XName DigitalSignatureElement = WxsNamespace + "DigitalSignature"; + public static readonly XName EmbeddedChainerElement = WxsNamespace + "EmbeddedChainer"; + public static readonly XName EmbeddedUIElement = WxsNamespace + "EmbeddedUI"; + public static readonly XName EmbeddedUIResourceElement = WxsNamespace + "EmbeddedUIResource"; + public static readonly XName PermissionExElement = WxsNamespace + "PermissionEx"; + public static readonly XName PackageCertificatesElement = WxsNamespace + "PackageCertificates"; + public static readonly XName PatchCertificatesElement = WxsNamespace + "PatchCertificates"; + public static readonly XName ShortcutPropertyElement = WxsNamespace + "ShortcutProperty"; + public static readonly XName ODBCDataSourceElement = WxsNamespace + "ODBCDataSource"; + public static readonly XName ODBCDriverElement = WxsNamespace + "ODBCDriver"; + public static readonly XName ODBCTranslatorElement = WxsNamespace + "ODBCTranslator"; + public static readonly XName PatchMetadataElement = WxsNamespace + "PatchMetadata"; + public static readonly XName OptimizeCustomActionsElement = WxsNamespace + "OptimizeCustomActions"; + public static readonly XName CustomPropertyElement = WxsNamespace + "CustomProperty"; + public static readonly XName PatchSequenceElement = WxsNamespace + "PatchSequence"; + public static readonly XName ProgIdElement = WxsNamespace + "ProgId"; + public static readonly XName ReplacePatchElement = WxsNamespace + "ReplacePatch"; + public static readonly XName TargetProductCodeElement = WxsNamespace + "TargetProductCode"; + public static readonly XName PatchPropertyElement = WxsNamespace + "PatchProperty"; + public static readonly XName CategoryElement = WxsNamespace + "Category"; + public static readonly XName RadioButtonElement = WxsNamespace + "RadioButton"; + public static readonly XName RadioButtonGroupElement = WxsNamespace + "RadioButtonGroup"; + public static readonly XName RegistryKeyElement = WxsNamespace + "RegistryKey"; + public static readonly XName RegistryValueElement = WxsNamespace + "RegistryValue"; + public static readonly XName MultiStringElement = WxsNamespace + "MultiString"; + public static readonly XName RegistrySearchElement = WxsNamespace + "RegistrySearch"; + public static readonly XName RemoveFolderElement = WxsNamespace + "RemoveFolder"; + public static readonly XName RemoveFileElement = WxsNamespace + "RemoveFile"; + public static readonly XName RemoveRegistryKeyElement = WxsNamespace + "RemoveRegistryKey"; + public static readonly XName RemoveRegistryValueElement = WxsNamespace + "RemoveRegistryValue"; + public static readonly XName ReserveCostElement = WxsNamespace + "ReserveCost"; + public static readonly XName ServiceControlElement = WxsNamespace + "ServiceControl"; + public static readonly XName ServiceArgumentElement = WxsNamespace + "ServiceArgument"; + public static readonly XName ServiceInstallElement = WxsNamespace + "ServiceInstall"; + public static readonly XName ServiceDependencyElement = WxsNamespace + "ServiceDependency"; + public static readonly XName SFPCatalogElement = WxsNamespace + "SFPCatalog"; + public static readonly XName ShortcutElement = WxsNamespace + "Shortcut"; + public static readonly XName FileSearchElement = WxsNamespace + "FileSearch"; + public static readonly XName TargetFileElement = WxsNamespace + "TargetFile"; + public static readonly XName TargetImageElement = WxsNamespace + "TargetImage"; + public static readonly XName TextStyleElement = WxsNamespace + "TextStyle"; + public static readonly XName TypeLibElement = WxsNamespace + "TypeLib"; + public static readonly XName UpgradeElement = WxsNamespace + "Upgrade"; + public static readonly XName UpgradeVersionElement = WxsNamespace + "UpgradeVersion"; + public static readonly XName UpgradeFileElement = WxsNamespace + "UpgradeFile"; + public static readonly XName UpgradeImageElement = WxsNamespace + "UpgradeImage"; + public static readonly XName UITextElement = WxsNamespace + "UIText"; + public static readonly XName VerbElement = WxsNamespace + "Verb"; + public static readonly XName ComplianceCheckElement = WxsNamespace + "ComplianceCheck"; + public static readonly XName FileSearchRefElement = WxsNamespace + "FileSearchRef"; + public static readonly XName ComplianceDriveElement = WxsNamespace + "ComplianceDrive"; + public static readonly XName DirectorySearchRefElement = WxsNamespace + "DirectorySearchRef"; + public static readonly XName RegistrySearchRefElement = WxsNamespace + "RegistrySearchRef"; + public static readonly XName MajorUpgradeElement = WxsNamespace + "MajorUpgrade"; + //public static readonly XName Element = WxsNamespace + ""; + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Melter.cs b/src/WixToolset.Core.WindowsInstaller/Melter.cs index a57f73a5..4e4d9e4e 100644 --- a/src/WixToolset.Core.WindowsInstaller/Melter.cs +++ b/src/WixToolset.Core.WindowsInstaller/Melter.cs @@ -12,7 +12,6 @@ namespace WixToolset using System.Text; using System.Text.RegularExpressions; using WixToolset.Data; - using Wix = WixToolset.Data.Serialize; /// /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. diff --git a/src/WixToolset.Core/CommandLine/DecompileCommand.cs b/src/WixToolset.Core/CommandLine/DecompileCommand.cs index 1e11ae52..fc77a7b4 100644 --- a/src/WixToolset.Core/CommandLine/DecompileCommand.cs +++ b/src/WixToolset.Core/CommandLine/DecompileCommand.cs @@ -33,7 +33,7 @@ namespace WixToolset.Core.CommandLine public Task ExecuteAsync(CancellationToken _) { - if (this.commandLine.ShowHelp) + if (this.commandLine.ShowHelp || String.IsNullOrEmpty(this.commandLine.DecompileFilePath)) { Console.WriteLine("TODO: Show decompile command help"); return Task.FromResult(-1); diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index c641bceb..6cc3a2e8 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -3331,7 +3331,7 @@ namespace WixToolset.Core break; } break; - case "ScriptFile": + case "ScriptSourceFile": scriptFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "SuppressModularization": @@ -3387,7 +3387,7 @@ namespace WixToolset.Core { if (String.IsNullOrEmpty(scriptFile)) { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ScriptFile", "Script")); + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); } } else if (CustomActionTargetType.VBScript == targetType) // non-inline vbscript @@ -3426,7 +3426,7 @@ namespace WixToolset.Core if (!inlineScript && !String.IsNullOrEmpty(scriptFile)) { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ScriptFile", "Script")); + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); } if (win64 && CustomActionTargetType.VBScript != targetType && CustomActionTargetType.JScript != targetType) diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs index 0a45c914..4a5cf544 100644 --- a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs @@ -3,6 +3,7 @@ namespace WixToolsetTest.CoreIntegration { using System.IO; + using System.Xml.Linq; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using Xunit; @@ -224,20 +225,8 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); - CompareLineByLine(expectedFile, decompiledWxsPath); + WixAssert.CompareXml(expectedFile, decompiledWxsPath); } } - - private static void CompareLineByLine(string expectedFile, string actualFile) - { - var expectedLines = File.ReadAllLines(expectedFile); - var actualLines = File.ReadAllLines(actualFile); - for (var i = 0; i < expectedLines.Length; ++i) - { - Assert.True(actualLines.Length > i, $"{i}: Expected file longer than actual file"); - Assert.Equal($"{i}: {expectedLines[i]}", $"{i}: {actualLines[i]}"); - } - Assert.True(expectedLines.Length == actualLines.Length, "Actual file longer than expected file"); - } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs index 9893a525..15082f2b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -6,14 +6,14 @@ namespace WixToolsetTest.CoreIntegration using System.Xml.Linq; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; + using WixToolset.Extensibility.Services; using Xunit; public class DecompileFixture { - [Fact] - public void CanDecompileSingleFileCompressed() + private static void DecompileAndCompare(string sourceFolder, string msiName, string expectedWxsName) { - var folder = TestData.Get(@"TestData\DecompileSingleFileCompressed"); + var folder = TestData.Get(sourceFolder); using (var fs = new DisposableFileSystem()) { @@ -23,75 +23,33 @@ namespace WixToolsetTest.CoreIntegration var result = WixRunner.Execute(new[] { "decompile", - Path.Combine(folder, "example.msi"), + Path.Combine(folder, msiName), "-intermediateFolder", intermediateFolder, "-o", outputPath }); result.AssertSuccess(); - var actual = File.ReadAllText(outputPath); - var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - var expected = XDocument.Load(Path.Combine(folder, "Expected.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - - Assert.Equal(expected, actualFormatted); + WixAssert.CompareXml(Path.Combine(folder, expectedWxsName), outputPath); } } [Fact] - public void CanDecompile64BitSingleFileCompressed() + public void CanDecompileSingleFileCompressed() { - var folder = TestData.Get(@"TestData\DecompileSingleFileCompressed64"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); - - var result = WixRunner.Execute(new[] - { - "decompile", - Path.Combine(folder, "example.msi"), - "-intermediateFolder", intermediateFolder, - "-o", outputPath - }); - - result.AssertSuccess(); - - var actual = File.ReadAllText(outputPath); - var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - var expected = XDocument.Load(Path.Combine(folder, "Expected.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); + DecompileAndCompare(@"TestData\DecompileSingleFileCompressed", "example.msi", "Expected.wxs"); + } - Assert.Equal(expected, actualFormatted); - } + [Fact] + public void CanDecompile64BitSingleFileCompressed() + { + DecompileAndCompare(@"TestData\DecompileSingleFileCompressed64", "example.msi", "Expected.wxs"); } [Fact] public void CanDecompileNestedDirSearchUnderRegSearch() { - var folder = TestData.Get(@"TestData\AppSearch"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); - - var result = WixRunner.Execute(new[] - { - "decompile", - Path.Combine(folder, "NestedDirSearchUnderRegSearch.msi"), - "-intermediateFolder", intermediateFolder, - "-o", outputPath - }); - - result.AssertSuccess(); - - var actual = File.ReadAllText(outputPath); - var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - var expected = XDocument.Load(Path.Combine(folder, "DecompiledNestedDirSearchUnderRegSearch.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - - Assert.Equal(expected, actualFormatted); - } + DecompileAndCompare(@"TestData\AppSearch", "NestedDirSearchUnderRegSearch.msi", "DecompiledNestedDirSearchUnderRegSearch.wxs"); } [Fact] @@ -100,85 +58,19 @@ namespace WixToolsetTest.CoreIntegration // The input MSI was not created using standard methods, it is an example of a real world database that needs to be decompiled. // The Class/@Feature_ column has length of 32, the File/@Attributes has length of 2, // and numerous foreign key relationships are missing. - var folder = TestData.Get(@"TestData\Class"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); - - var result = WixRunner.Execute(new[] - { - "decompile", - Path.Combine(folder, "OldClassTableDef.msi"), - "-intermediateFolder", intermediateFolder, - "-o", outputPath - }); - - result.AssertSuccess(); - - var actual = File.ReadAllText(outputPath); - var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - var expected = XDocument.Load(Path.Combine(folder, "DecompiledOldClassTableDef.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - - Assert.Equal(expected, actualFormatted); - } + DecompileAndCompare(@"TestData\Class", "OldClassTableDef.msi", "DecompiledOldClassTableDef.wxs"); } [Fact] public void CanDecompileSequenceTables() { - var folder = TestData.Get(@"TestData\SequenceTables"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); - - var result = WixRunner.Execute(new[] - { - "decompile", - Path.Combine(folder, "SequenceTables.msi"), - "-intermediateFolder", intermediateFolder, - "-o", outputPath - }); - - result.AssertSuccess(); - - var actual = File.ReadAllText(outputPath); - var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - var expected = XDocument.Load(Path.Combine(folder, "DecompiledSequenceTables.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - - Assert.Equal(expected, actualFormatted); - } + DecompileAndCompare(@"TestData\SequenceTables", "SequenceTables.msi", "DecompiledSequenceTables.wxs"); } [Fact] public void CanDecompileShortcuts() { - var folder = TestData.Get(@"TestData\Shortcut"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); - - var result = WixRunner.Execute(new[] - { - "decompile", - Path.Combine(folder, "shortcuts.msi"), - "-intermediateFolder", intermediateFolder, - "-o", outputPath - }); - - result.AssertSuccess(); - - var actual = File.ReadAllText(outputPath); - var actualFormatted = XDocument.Parse(actual, LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - var expected = XDocument.Load(Path.Combine(folder, "DecompiledShortcuts.wxs"), LoadOptions.PreserveWhitespace | LoadOptions.SetBaseUri | LoadOptions.SetLineInfo).ToString(); - - Assert.Equal(expected, actualFormatted); - } + DecompileAndCompare(@"TestData\Shortcut", "shortcuts.msi", "DecompiledShortcuts.wxs"); } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs index c55f4ed0..22036ae5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs @@ -6,12 +6,12 @@ - Row1 - test.txt + + - Row2 - test.txt + + -- cgit v1.2.3-55-g6feb From e1ff7245ee176c18bd0b773a7e11df6bb95d7b7e Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Tue, 1 Sep 2020 18:31:26 -0400 Subject: Fix check for valid 8.3 names for >3 extensions. --- src/WixToolset.Core/Common.cs | 2 +- src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs | 4 ++++ .../WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs index 6efc7571..1bb895be 100644 --- a/src/WixToolset.Core/Common.cs +++ b/src/WixToolset.Core/Common.cs @@ -253,7 +253,7 @@ namespace WixToolset.Core { return filename.Length < 9; } - else if (expectedDot > 8 || filename[expectedDot] != '.') + else if (expectedDot > 8 || filename[expectedDot] != '.' || expectedDot + 4 < filename.Length) { return false; } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 158687cf..f5353c87 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -371,6 +371,10 @@ namespace WixToolsetTest.CoreIntegration WixAssert.CompareLineByLine(new[] { "Directory:DUPLICATENAMEANDSHORTNAME\tINSTALLFOLDER\tduplicat", + "Directory:Folder1\tINSTALLFOLDER\tFolder.1", + "Directory:Folder12\tINSTALLFOLDER\tFolder.12", + "Directory:Folder123\tINSTALLFOLDER\tFolder.123", + "Directory:Folder1234\tINSTALLFOLDER\tyakwclwy|Folder.1234", "Directory:INSTALLFOLDER\tProgramFiles6432Folder\t1egc1laj|MsiPackage", "Directory:NAMEANDSHORTNAME\tINSTALLFOLDER\tSHORTNAM|NameAndShortName", "Directory:NAMEANDSHORTSOURCENAME\tINSTALLFOLDER\tNAMEASSN|NameAndShortSourceName", diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs index a217fa34..2f277956 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs @@ -17,6 +17,10 @@ + + + + -- cgit v1.2.3-55-g6feb From 3f05c650723ecd0a8fe7ab151c67db6c13cb5d75 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Sun, 6 Sep 2020 21:09:19 -0400 Subject: Replace BinaryKey with BinaryRef and FileKey with FileRef. --- .../Decompile/Decompiler.cs | 4 ++-- src/WixToolset.Core/Compiler.cs | 20 ++++++++++---------- .../TestData/CustomAction/CustomActionCycle.wxs | 9 ++++----- .../TestData/CustomAction/SimpleCustomAction.wxs | 5 ++--- .../CustomAction/UnscheduledCustomAction.wxs | 7 +++---- .../TestData/Wixipl/Package.wxs | 2 +- 6 files changed, 22 insertions(+), 25 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index 591ff5ef..e471d924 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -3997,12 +3997,12 @@ namespace WixToolset.Core.WindowsInstaller switch (source) { case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: - xCustomAction.SetAttributeValue("BinaryKey", row.FieldAsString(2)); + xCustomAction.SetAttributeValue("BinaryRef", row.FieldAsString(2)); break; case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: if (!row.IsColumnNull(2)) { - xCustomAction.SetAttributeValue("FileKey", row.FieldAsString(2)); + xCustomAction.SetAttributeValue("FileRef", row.FieldAsString(2)); } break; case WindowsInstallerConstants.MsidbCustomActionTypeDirectory: diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 2ef85ff0..7be581bc 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -3159,10 +3159,10 @@ namespace WixToolset.Core case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; - case "BinaryKey": + case "BinaryRef": if (null != source) { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileKey", "Property", "Script")); + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); } source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); sourceType = CustomActionSourceType.Binary; @@ -3171,7 +3171,7 @@ namespace WixToolset.Core case "Directory": if (null != source) { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileKey", "Property", "Script")); + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileRef", "Property", "Script")); } source = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); sourceType = CustomActionSourceType.Directory; @@ -3239,10 +3239,10 @@ namespace WixToolset.Core break; } break; - case "FileKey": + case "FileRef": if (null != source) { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileKey", "Property", "Script")); + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); } source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); sourceType = CustomActionSourceType.File; @@ -3268,7 +3268,7 @@ namespace WixToolset.Core case "Property": if (null != source) { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileKey", "Property", "Script")); + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); } source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); sourceType = CustomActionSourceType.Property; @@ -3299,7 +3299,7 @@ namespace WixToolset.Core case "Script": if (null != source) { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileKey", "Property", "Script")); + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); } if (null != target) @@ -3394,7 +3394,7 @@ namespace WixToolset.Core { if (null == source) { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "BinaryKey", "FileKey", "Property")); + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "BinaryRef", "FileRef", "Property")); } else if (CustomActionSourceType.Directory == sourceType) { @@ -3405,7 +3405,7 @@ namespace WixToolset.Core { if (null == source) { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "BinaryKey", "FileKey", "Property")); + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "BinaryRef", "FileRef", "Property")); } else if (CustomActionSourceType.Directory == sourceType) { @@ -3416,7 +3416,7 @@ namespace WixToolset.Core { if (null == source) { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryKey", "Directory", "FileKey", "Property")); + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryRef", "Directory", "FileRef", "Property")); } } else if (CustomActionTargetType.TextData == targetType && CustomActionSourceType.Directory != sourceType && CustomActionSourceType.Property != sourceType && CustomActionSourceType.File != sourceType) diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs index 56c07eb9..be991c65 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs @@ -1,14 +1,13 @@ - - + - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs index 72d5e4a5..ff8741cf 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs @@ -1,12 +1,11 @@ - - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs index 0784824a..f8ce1c38 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs @@ -1,14 +1,13 @@ - - + - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs index 00a80fca..bf5223c1 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs @@ -5,7 +5,7 @@ - + -- cgit v1.2.3-55-g6feb From 7a7f88c3009ad824852322682cc8cfd3173c2e02 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Wed, 30 Sep 2020 18:56:06 -0400 Subject: Add error message for cases where inner text used to be used. --- src/WixToolset.Core/Compiler.cs | 6 +++++- src/WixToolset.Core/CompilerCore.cs | 9 +++++++++ src/WixToolset.Core/Compiler_2.cs | 18 ++++++++++++++++++ src/WixToolset.Core/Compiler_EmbeddedUI.cs | 2 ++ src/WixToolset.Core/Compiler_UI.cs | 8 ++++++++ .../DialogsInInstallUISequence/PackageComponents.wxs | 14 +++++--------- 6 files changed, 47 insertions(+), 10 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 286a598b..d9c60ef6 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -3382,6 +3382,8 @@ namespace WixToolset.Core win64 = true; } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + // if we have an in-lined Script CustomAction ensure no source or target attributes were provided if (inlineScript) { @@ -5349,6 +5351,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (CompilerConstants.IntegerNotSet == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); @@ -7598,7 +7602,7 @@ namespace WixToolset.Core } /// - /// Parses a condition element. + /// Parses a Level element. /// /// Element to parse. /// Id of the parent Feature element. diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index 8e4f3d89..721eae6b 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -1086,6 +1086,15 @@ namespace WixToolset.Core return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); } + internal void VerifyNoInnerText(SourceLineNumber sourceLineNumbers, XElement element) + { + var innerText = Common.GetInnerText(element); + if (!String.IsNullOrWhiteSpace(innerText)) + { + this.messaging.Write(ErrorMessages.IllegalInnerText(sourceLineNumbers, element.Name.LocalName, innerText)); + } + } + private static string CreateValueList(ValueListKind kind, IEnumerable values) { // Ideally, we could denote the list kind (and the list itself) directly in the diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index 204a3788..fbad873e 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -1211,6 +1211,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (null == sddl) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl")); @@ -1499,6 +1501,8 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if ("ErrorDialog" == id.Id) { this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, value); @@ -2043,6 +2047,8 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + this.Core.ParseForExtensionElements(node); return (null == value) ? multiStringValue : String.Concat(value, "[~]", multiStringValue); @@ -2608,6 +2614,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(childSourceLineNumbers, node); + if (customAction && "Custom" == actionName) { this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); @@ -3071,6 +3079,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (privilege == null) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); @@ -3432,6 +3442,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (argument == null) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); @@ -3769,6 +3781,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); @@ -3876,6 +3890,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (null == id) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); @@ -4353,6 +4369,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (String.IsNullOrEmpty(key)) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); diff --git a/src/WixToolset.Core/Compiler_EmbeddedUI.cs b/src/WixToolset.Core/Compiler_EmbeddedUI.cs index 4353e3cd..d71bb09a 100644 --- a/src/WixToolset.Core/Compiler_EmbeddedUI.cs +++ b/src/WixToolset.Core/Compiler_EmbeddedUI.cs @@ -80,6 +80,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (null == id) { id = this.Core.CreateIdentifier("mec", source, type.ToString()); diff --git a/src/WixToolset.Core/Compiler_UI.cs b/src/WixToolset.Core/Compiler_UI.cs index 9353d966..cb1d34ac 100644 --- a/src/WixToolset.Core/Compiler_UI.cs +++ b/src/WixToolset.Core/Compiler_UI.cs @@ -700,6 +700,8 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + this.Core.ParseForExtensionElements(node); if (!this.Core.EncounteredError) @@ -746,6 +748,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (null == id) { id = this.Core.CreateIdentifier("txt", text); @@ -1422,6 +1426,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (!String.IsNullOrEmpty(text) && null != sourceFile) { this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "SourceFile", "Value")); @@ -1689,6 +1695,8 @@ namespace WixToolset.Core } } + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + if (null == control) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Control")); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs index 10c8b2c3..ec6e62df 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs @@ -1,21 +1,17 @@ - - + - + - - NOT Installed + @@ -24,7 +20,7 @@ - Installed AND PATCH + -- cgit v1.2.3-55-g6feb From 95188080c8005c01c39bb071459b36f8660bcfcd Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 14 Oct 2020 23:05:10 -0500 Subject: Add failing tests. --- .../BundleFixture.cs | 52 ++++++++++++++++++++++ .../TestData/BadInput/DuplicateCacheIds.wxs | 9 ++++ .../TestData/BadInput/DuplicatePayloadNames.wxs | 9 ++++ .../WixToolsetTest.CoreIntegration.csproj | 2 + 4 files changed, 72 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index 5e1e5866..ad1648e6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -244,5 +244,57 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(exePath)); } } + + [Fact(Skip = "Test demonstrates failure")] + public void CantBuildWithDuplicateCacheIds() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "DuplicateCacheIds.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void CantBuildWithDuplicatePayloadNames() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "DuplicatePayloadNames.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs new file mode 100644 index 00000000..5c58ef50 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs new file mode 100644 index 00000000..2d4e8a3c --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index f4aab391..fdb56987 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -23,6 +23,8 @@ + + -- cgit v1.2.3-55-g6feb From 8b5505cd13367d48bce4ec8a6018e370ed3755b1 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Fri, 23 Oct 2020 17:32:44 -0400 Subject: Reorganize Product/Package to Package/SummaryInformation. --- .../Bind/BindSummaryInfoCommand.cs | 23 +- .../Decompile/Decompiler.cs | 99 +++-- .../Decompile/Names.cs | 7 +- .../CommandLine/DecompileCommand.cs | 1 + src/WixToolset.Core/Compiler.cs | 4 +- src/WixToolset.Core/Compiler_2.cs | 467 +++++++++------------ src/WixToolset.Core/Compiler_Module.cs | 56 ++- .../PreprocessorFixture.cs | 4 +- .../DecompiledNestedDirSearchUnderRegSearch.wxs | 9 +- .../TestData/Assembly/Package.wxs | 9 +- .../TestData/BadIf/Package.wxs | 9 +- .../TestData/Class/DecompiledOldClassTableDef.wxs | 9 +- .../TestData/ComplexExampleExtension/Package.wxs | 9 +- .../TestData/Components/Package.wxs | 9 +- .../TestData/CustomTable/CustomTable-Expected.wxs | 9 +- .../DecompileSingleFileCompressed/Expected.wxs | 9 +- .../DecompileSingleFileCompressed64/Expected.wxs | 9 +- .../TestData/ErrorsInUI/Package.wxs | 9 +- .../TestData/ExampleExtension/Package.wxs | 9 +- .../TestData/ForEach/Package.wxs | 9 +- .../TestData/IncludePath/Package.wxs | 9 +- .../TestData/InstanceTransform/Package.wxs | 9 +- .../TestData/ManualUpgrade/Package.wxs | 9 +- .../TestData/MultiFileCompressed/Package.wxs | 9 +- .../TestData/OverridableActions/Package.wxs | 10 +- .../TestData/PatchFamilyFilter/Package.wxs | 32 +- .../TestData/PatchFamilyFilter/Patch.wxs | 19 +- .../TestData/PatchSingle/Package.wxs | 30 +- .../ProductWithComponentGroupRef/Product.wxs | 9 +- .../TestData/ProgId/Package.wxs | 9 +- .../SequenceTables/DecompiledSequenceTables.wxs | 9 +- .../TestData/SetProperty/Package.wxs | 9 +- .../TestData/Shortcut/DecompiledShortcuts.wxs | 9 +- .../TestData/SimpleMerge/Package.wxs | 9 +- .../TestData/SimpleModule/Module.wxs | 7 +- .../TestData/SingleFile/Package.wxs | 9 +- .../TestData/SingleFileCompressed/Package.wxs | 9 +- .../TestData/Variables/Package.wxs | 10 +- .../TestData/Wixipl/Package.wxs | 9 +- .../TestData/WixlibWithBinaries/Package.wxs | 9 +- 40 files changed, 473 insertions(+), 530 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs index 6af2dc7a..a496c7ce 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs @@ -46,9 +46,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.InstallerVersion = 0; this.ModularizationSuffix = null; - var foundCreateDataTime = false; + var foundCreateDateTime = false; var foundLastSaveDataTime = false; var foundCreatingApplication = false; + var foundPackageCode = false; var now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); foreach (var summaryInformationSymbol in this.Section.Symbols.OfType()) @@ -73,20 +74,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind break; case SummaryInformationType.PackageCode: // PID_REVNUMBER + foundPackageCode = true; var packageCode = summaryInformationSymbol.Value; if (SectionType.Module == this.Section.Type) { this.ModularizationSuffix = "." + packageCode.Substring(1, 36).Replace('-', '_'); } - else if ("*" == packageCode) - { - // set the revision number (package/patch code) if it should be automatically generated - summaryInformationSymbol.Value = Common.GenerateGuid(); - } break; case SummaryInformationType.Created: - foundCreateDataTime = true; + foundCreateDateTime = true; break; case SummaryInformationType.LastSaved: foundLastSaveDataTime = true; @@ -113,8 +110,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } + // set the revision number (package/patch code) if it should be automatically generated + if (!foundPackageCode) + { + this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.PackageCode, + Value = Common.GenerateGuid(), + }); + } + // add a summary information row for the create time/date property if its not already set - if (!foundCreateDataTime) + if (!foundCreateDateTime) { this.Section.AddSymbol(new SummaryInformationSymbol(null) { diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index a0146fda..80ee75a5 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -160,7 +160,7 @@ namespace WixToolset.Core.WindowsInstaller this.RootElement = new XElement(Names.PatchCreationElement); break; case OutputType.Product: - this.RootElement = new XElement(Names.ProductElement); + this.RootElement = new XElement(Names.PackageElement); break; default: throw new InvalidOperationException("Unknown output type."); @@ -669,6 +669,7 @@ namespace WixToolset.Core.WindowsInstaller } else { + this.FinalizeSummaryInformationStream(tables); this.FinalizeCheckBoxTable(tables); this.FinalizeComponentTable(tables); this.FinalizeDialogTable(tables); @@ -2548,7 +2549,7 @@ namespace WixToolset.Core.WindowsInstaller switch (table.Name) { case "_SummaryInformation": - this.Decompile_SummaryInformationTable(table); + // handled in FinalizeDecompile break; case "AdminExecuteSequence": case "AdminUISequence": @@ -2976,11 +2977,13 @@ namespace WixToolset.Core.WindowsInstaller /// Decompile the _SummaryInformation table. /// /// The table to decompile. - private void Decompile_SummaryInformationTable(Table table) + private void FinalizeSummaryInformationStream(TableIndexedCollection tables) { + var table = tables["_SummaryInformation"]; + if (OutputType.Module == this.OutputType || OutputType.Product == this.OutputType) { - var xPackage = new XElement(Names.PackageElement); + var xSummaryInformation = new XElement(Names.SummaryInformationElement); foreach (var row in table.Rows) { @@ -2993,56 +2996,63 @@ namespace WixToolset.Core.WindowsInstaller case 1: if ("1252" != value) { - xPackage.SetAttributeValue("SummaryCodepage", value); + xSummaryInformation.SetAttributeValue("Codepage", value); } break; case 3: - xPackage.SetAttributeValue("Description", value); + { + var productName = this.RootElement.Attribute("Name")?.Value; + if (value != productName) + { + xSummaryInformation.SetAttributeValue("Description", value); + } break; + } case 4: - xPackage.SetAttributeValue("Manufacturer", value); - break; - case 5: - if ("Installer" != value) + { + var productManufacturer = this.RootElement.Attribute("Manufacturer")?.Value; + if (value != productManufacturer) { - xPackage.SetAttributeValue("Keywords", value); + xSummaryInformation.SetAttributeValue("Manufacturer", value); } break; - case 6: - if (!value.StartsWith("This installer database contains the logic and data required to install ")) + } + case 5: + if ("Installer" != value) { - xPackage.SetAttributeValue("Comments", value); + xSummaryInformation.SetAttributeValue("Keywords", value); } break; case 7: var template = value.Split(';'); if (0 < template.Length && 0 < template[template.Length - 1].Length) { - xPackage.SetAttributeValue("Languages", template[template.Length - 1]); - } - - var platform = GetPlatformFromTemplateSummaryInformation(template).ToString().ToLowerInvariant(); - if (!String.IsNullOrEmpty(platform)) - { - xPackage.SetAttributeValue("Platform", platform); + this.RootElement.SetAttributeValue("Language", template[template.Length - 1]); } break; case 9: if (OutputType.Module == this.OutputType) { this.ModularizationGuid = value; - xPackage.SetAttributeValue("Id", value); } break; case 14: - xPackage.SetAttributeValue("InstallerVersion", row.FieldAsInteger(1)); + var installerVersion = row.FieldAsInteger(1); + // Default InstallerVersion. + if (installerVersion != 500) + { + this.RootElement.SetAttributeValue("InstallerVersion", installerVersion); + } break; case 15: var wordCount = row.FieldAsInteger(1); if (0x1 == (wordCount & 0x1)) { this.ShortNames = true; - xPackage.SetAttributeValue("ShortNames", "yes"); + if (OutputType.Product == this.OutputType) + { + this.RootElement.SetAttributeValue("ShortNames", "yes"); + } } if (0x2 == (wordCount & 0x2)) @@ -3051,38 +3061,35 @@ namespace WixToolset.Core.WindowsInstaller if (OutputType.Product == this.OutputType) { - xPackage.SetAttributeValue("Compressed", "yes"); + this.RootElement.SetAttributeValue("Compressed", "yes"); } } - if (0x4 == (wordCount & 0x4)) + if (OutputType.Product == this.OutputType) { - xPackage.SetAttributeValue("AdminImage", "yes"); - } - - if (0x8 == (wordCount & 0x8)) - { - xPackage.SetAttributeValue("InstallPrivileges", "limited"); + if (0x8 == (wordCount & 0x8)) + { + this.RootElement.SetAttributeValue("Scope", "perUser"); + } + else + { + var xAllUsers = this.RootElement.Elements(Names.PropertyElement).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS"); + if (xAllUsers?.Attribute("Value")?.Value == "1") + { + xAllUsers?.Remove(); + } + } } - break; - case 19: - var security = row.FieldAsInteger(1); - switch (security) - { - case 0: - xPackage.SetAttributeValue("ReadOnly", "no"); - break; - case 4: - xPackage.SetAttributeValue("ReadOnly", "yes"); - break; - } break; } } } - this.RootElement.Add(xPackage); + if (xSummaryInformation.HasAttributes) + { + this.RootElement.Add(xSummaryInformation); + } } else { @@ -6204,7 +6211,7 @@ namespace WixToolset.Core.WindowsInstaller this.RootElement.SetAttributeValue("Manufacturer", value); continue; case "ProductCode": - this.RootElement.SetAttributeValue("Id", value.ToUpper(CultureInfo.InvariantCulture)); + this.RootElement.SetAttributeValue("ProductCode", value.ToUpper(CultureInfo.InvariantCulture)); continue; case "ProductLanguage": this.RootElement.SetAttributeValue("Language", value); diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs index 63ab5cd3..82258c57 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs @@ -8,10 +8,12 @@ namespace WixToolset.Core.WindowsInstaller.Decompile public static readonly XName WixElement = WxsNamespace + "Wix"; - public static readonly XName ProductElement = WxsNamespace + "Product"; + public static readonly XName PackageElement = WxsNamespace + "Package"; public static readonly XName ModuleElement = WxsNamespace + "Module"; public static readonly XName PatchCreationElement = WxsNamespace + "PatchCreation"; + public static readonly XName SummaryInformationElement = WxsNamespace + "SummaryInformation"; + public static readonly XName CustomElement = WxsNamespace + "Custom"; public static readonly XName AdminExecuteSequenceElement = WxsNamespace + "AdminExecuteSequence"; @@ -30,9 +32,8 @@ namespace WixToolset.Core.WindowsInstaller.Decompile public static readonly XName FileElement = WxsNamespace + "File"; public static readonly XName EnsureTableElement = WxsNamespace + "EnsureTable"; - public static readonly XName PackageElement = WxsNamespace + "Package"; public static readonly XName PatchInformationElement = WxsNamespace + "PatchInformation"; - + public static readonly XName ProgressTextElement = WxsNamespace + "ProgressText"; public static readonly XName UIElement = WxsNamespace + "UI"; diff --git a/src/WixToolset.Core/CommandLine/DecompileCommand.cs b/src/WixToolset.Core/CommandLine/DecompileCommand.cs index fc77a7b4..bce7a5a4 100644 --- a/src/WixToolset.Core/CommandLine/DecompileCommand.cs +++ b/src/WixToolset.Core/CommandLine/DecompileCommand.cs @@ -53,6 +53,7 @@ namespace WixToolset.Core.CommandLine if (!this.Messaging.EncounteredError) { + Directory.CreateDirectory(Path.GetDirectoryName(context.OutputPath)); result.Document.Save(context.OutputPath, SaveOptions.OmitDuplicateNamespaces); } } diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index d9c60ef6..a575bb95 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -227,8 +227,8 @@ namespace WixToolset.Core case "PatchCreation": this.ParsePatchCreationElement(child); break; - case "Product": - this.ParseProductElement(child); + case "Package": + this.ParsePackageElement(child); break; case "Patch": this.ParsePatchElement(child); diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index fbad873e..a3bc09b6 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -23,15 +23,24 @@ namespace WixToolset.Core /// Parses a product element. /// /// Element to parse. - private void ParseProductElement(XElement node) + private void ParsePackageElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var sourceBits = 0; var codepage = 65001; var productCode = "*"; + var isPerMachine = true; + string installScope = null; string upgradeCode = null; string manufacturer = null; string version = null; string symbols = null; + var isCodepageSet = false; + var isPackageNameSet = false; + var isKeywordsSet = false; + var isPackageAuthorSet = false; + + this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); this.activeName = null; this.activeLanguage = null; @@ -42,12 +51,18 @@ namespace WixToolset.Core { switch (attrib.Name.LocalName) { - case "Id": - productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); - break; case "Codepage": codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); break; + case "Compressed": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + sourceBits |= 2; + } + break; + case "InstallerVersion": + msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; case "Language": this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); break; @@ -65,6 +80,31 @@ namespace WixToolset.Core this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); } break; + case "ProductCode": + productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); + break; + case "Scope": + installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (installScope) + { + case "perMachine": + // handled below after we create the section. + break; + case "perUser": + isPerMachine = false; + sourceBits |= 8; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); + break; + } + break; + case "ShortNames": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + sourceBits |= 1; + } + break; case "UpgradeCode": upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); break; @@ -140,6 +180,19 @@ namespace WixToolset.Core this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "UpgradeCode"), upgradeCode, false, false, false, true); } + if (isPerMachine) + { + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ALLUSERS"), "1", false, false, false, false); + } + + this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WordCount, + Value = sourceBits.ToString(CultureInfo.InvariantCulture) + }); + var contextValues = new Dictionary { ["ProductLanguage"] = this.activeLanguage, @@ -240,9 +293,6 @@ namespace WixToolset.Core case "MediaTemplate": this.ParseMediaTemplateElement(child, null); break; - case "Package": - this.ParsePackageElement(child, manufacturer, null); - break; case "PackageCertificates": case "PatchCertificates": this.ParseCertificatesElement(child); @@ -263,6 +313,9 @@ namespace WixToolset.Core string parentName = null; this.ParseSFPCatalogElement(child, ref parentName); break; + case "SummaryInformation": + this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); + break; case "SymbolPath": if (null != symbols) { @@ -298,6 +351,42 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { + if (!isCodepageSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = "1252" + }); + } + + if (!isPackageNameSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = this.activeName + }); + } + + if (!isPackageAuthorSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = manufacturer + }); + } + + if (!isKeywordsSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = "Installer" + }); + } + if (null != symbols) { this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers) @@ -315,6 +404,74 @@ namespace WixToolset.Core } } + private void GetDefaultPlatformAndInstallerVersion(out string platform, out int msiVersion) + { + // Let's default to a modern version of MSI. Users can override, + // of course, subject to platform-specific limitations. + msiVersion = 500; + + switch (this.CurrentPlatform) + { + case Platform.X86: + platform = "Intel"; + break; + case Platform.X64: + platform = "x64"; + break; + case Platform.ARM64: + platform = "Arm64"; + break; + default: + throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString()); + } + } + + private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform) + { + if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) + { + msiVersion = 200; + this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers)); + } + + if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion) + { + msiVersion = 500; + this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers)); + } + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Comments, + Value = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Title, + Value = "Installation Database" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.PlatformAndLanguage, + Value = String.Format(CultureInfo.InvariantCulture, "{0};{1}", platform, this.activeLanguage) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WindowsInstallerVersion, + Value = msiVersion.ToString(CultureInfo.InvariantCulture) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Security, + Value = "2" + }); + + } + /// /// Parses an odbc driver or translator element. /// @@ -615,40 +772,13 @@ namespace WixToolset.Core /// Element to parse. /// Default package author. /// The module guid - this is necessary until Module/@Guid is removed. - private void ParsePackageElement(XElement node, string productAuthor, string moduleId) + private void ParseSummaryInformationElement(XElement node, ref bool isCodepageSet, ref bool isPackageNameSet, ref bool isKeywordsSet, ref bool isPackageAuthorSet) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var codepage = "1252"; - var comments = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName); - var keywords = "Installer"; - var msiVersion = 100; // lowest released version, really should be specified - var packageAuthor = productAuthor; - string packageCode = null; - var packageLanguages = this.activeLanguage; - var packageName = this.activeName; - string platform = null; - string platformValue = null; - var security = YesNoDefaultType.Default; - var sourceBits = (this.compilingModule ? 2 : 0); - var installPrivilegeSeen = false; - var installScopeSeen = false; - - switch (this.CurrentPlatform) - { - case Platform.X86: - platform = "Intel"; - break; - case Platform.X64: - platform = "x64"; - msiVersion = 200; - break; - case Platform.ARM64: - platform = "Arm64"; - msiVersion = 500; - break; - default: - throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString()); - } + string codepage = null; + string packageName = null; + string keywords = null; + string packageAuthor = null; foreach (var attrib in node.Attributes()) { @@ -656,83 +786,15 @@ namespace WixToolset.Core { switch (attrib.Name.LocalName) { - case "Id": - packageCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, this.compilingProduct); - break; - case "AdminImage": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - sourceBits |= 4; - } - break; - case "Comments": - comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Compressed": - // merge modules must always be compressed, so this attribute is invalid - if (this.compilingModule) - { - this.Core.Write(WarningMessages.DeprecatedPackageCompressedAttribute(sourceLineNumbers)); - // this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Compressed", "Module")); - } - else if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - sourceBits |= 2; - } + case "Codepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); break; case "Description": packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; - case "InstallPrivileges": - var installPrivileges = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (installPrivileges) - { - case "elevated": - // this is the default setting - installPrivilegeSeen = true; - break; - case "limited": - sourceBits |= 8; - installPrivilegeSeen = true; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installPrivileges, "elevated", "limited")); - break; - } - break; - case "InstallScope": - var installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (installScope) - { - case "perMachine": - this.Core.AddSymbol(new PropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, "ALLUSERS")) - { - Value = "1" - }); - installScopeSeen = true; - break; - case "perUser": - sourceBits |= 8; - installScopeSeen = true; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); - break; - } - break; - case "InstallerVersion": - msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; case "Keywords": keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; - case "Languages": - packageLanguages = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; case "Manufacturer": packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); if ("PUT-COMPANY-NAME-HERE" == packageAuthor) @@ -740,56 +802,6 @@ namespace WixToolset.Core this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor)); } break; - case "Platform": - if (null != platformValue) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Platforms")); - } - - platformValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (platformValue) - { - case "intel": - this.Core.Write(WarningMessages.DeprecatedAttributeValue(sourceLineNumbers, platformValue, node.Name.LocalName, attrib.Name.LocalName, "x86")); - goto case "x86"; - case "x86": - platform = "Intel"; - break; - case "x64": - platform = "x64"; - break; - case "arm64": - platform = "Arm64"; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.InvalidPlatformValue(sourceLineNumbers, platformValue)); - break; - } - break; - case "Platforms": - if (null != platformValue) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Platform")); - } - - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Platform")); - platformValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - platform = platformValue; - break; - case "ReadOnly": - security = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - break; - case "ShortNames": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - sourceBits |= 1; - } - break; - case "SummaryCodepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); - break; default: this.Core.UnexpectedAttribute(node, attrib); break; @@ -801,127 +813,50 @@ namespace WixToolset.Core } } - if (installPrivilegeSeen && installScopeSeen) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPrivileges", "InstallScope")); - } - - if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) - { - msiVersion = 200; - this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers)); - } - - if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion) - { - msiVersion = 500; - this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers)); - } - - if (null == packageAuthor) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); - } + this.Core.ParseForExtensionElements(node); - if (this.compilingModule) + if (!this.Core.EncounteredError) { - if (null == packageCode) + if (null != codepage) { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + isCodepageSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = codepage + }); } - // merge modules use the modularization guid as the package code - if (null != moduleId) + if (null != packageName) { - packageCode = moduleId; + isPackageNameSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = packageName + }); } - // merge modules are always compressed - sourceBits = 2; - } - else // product - { - if (null == packageCode) + if (null != packageAuthor) { - packageCode = "*"; + isPackageAuthorSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = packageAuthor + }); } - if ("*" != packageCode) + if (null != keywords) { - this.Core.Write(WarningMessages.PackageCodeSet(sourceLineNumbers)); + isKeywordsSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = keywords + }); } } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Codepage, - Value = codepage - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Title, - Value = "Installation Database" - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = packageName - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Author, - Value = packageAuthor - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = keywords - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Comments, - Value = comments - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.PlatformAndLanguage, - Value = String.Format(CultureInfo.InvariantCulture, "{0};{1}", platform, packageLanguages) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.PackageCode, - Value = packageCode - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WindowsInstallerVersion, - Value = msiVersion.ToString(CultureInfo.InvariantCulture) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WordCount, - Value = sourceBits.ToString(CultureInfo.InvariantCulture) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Security, - Value = YesNoDefaultType.No == security ? "0" : YesNoDefaultType.Yes == security ? "4" : "2" - }); - } } /// diff --git a/src/WixToolset.Core/Compiler_Module.cs b/src/WixToolset.Core/Compiler_Module.cs index 2ecd9113..2f926d82 100644 --- a/src/WixToolset.Core/Compiler_Module.cs +++ b/src/WixToolset.Core/Compiler_Module.cs @@ -24,6 +24,12 @@ namespace WixToolset.Core var codepage = 0; string moduleId = null; string version = null; + var setCodepage = false; + var setPackageName = false; + var setKeywords = false; + var ignoredForMergeModules = false; + + this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); this.activeName = null; this.activeLanguage = null; @@ -50,7 +56,9 @@ namespace WixToolset.Core break; case "Guid": moduleId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - this.Core.Write(WarningMessages.DeprecatedModuleGuidAttribute(sourceLineNumbers)); + break; + case "InstallerVersion": + msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); break; case "Language": this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); @@ -74,6 +82,11 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } + if (null == moduleId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Guid")); + } + if (null == this.activeLanguage) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); @@ -174,9 +187,6 @@ namespace WixToolset.Core case "IgnoreTable": this.ParseIgnoreTableElement(child); break; - case "Package": - this.ParsePackageElement(child, null, moduleId); - break; case "Property": this.ParsePropertyElement(child); break; @@ -196,6 +206,9 @@ namespace WixToolset.Core case "Substitution": this.ParseSubstitutionElement(child); break; + case "SummaryInformation": + this.ParseSummaryInformationElement(child, ref setCodepage, ref setPackageName, ref setKeywords, ref ignoredForMergeModules); + break; case "UI": this.ParseUIElement(child); break; @@ -219,6 +232,33 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { + if (!setCodepage) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = "1252" + }); + } + + if (!setPackageName) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = this.activeName + }); + } + + if (!setKeywords) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = "Installer" + }); + } + var symbol = this.Core.AddSymbol(new ModuleSignatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, this.activeName, this.activeLanguage)) { ModuleID = this.activeName, @@ -226,6 +266,14 @@ namespace WixToolset.Core }); symbol.Set((int)ModuleSignatureSymbolFields.Language, this.activeLanguage); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.PackageCode, + Value = moduleId + }); + + this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform); } } finally diff --git a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs index b038812d..e18990d3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs @@ -34,8 +34,8 @@ namespace WixToolsetTest.CoreIntegration Assert.NotNull(result.Document); Assert.Equal(includeFile, includedFile.Path); Assert.Equal(sourcePath, includedFile.SourceLineNumbers.FileName); - Assert.Equal(2, includedFile.SourceLineNumbers.LineNumber.Value); - Assert.Equal($"{sourcePath}*2", includedFile.SourceLineNumbers.QualifiedFileName); + Assert.Equal(1, includedFile.SourceLineNumbers.LineNumber.Value); + Assert.Equal($"{sourcePath}*1", includedFile.SourceLineNumbers.QualifiedFileName); Assert.Null(includedFile.SourceLineNumbers.Parent); } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs index 26649485..8d1e5de2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs @@ -1,7 +1,5 @@ - - - - + + @@ -16,7 +14,6 @@ - @@ -43,5 +40,5 @@ - + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs index dbce4c71..50282522 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs @@ -1,14 +1,13 @@ - - - - + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs index c1cf55c2..1d7ebb94 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -13,7 +12,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs index 86d41c50..08ced0c2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs @@ -1,7 +1,5 @@ - - - - + + @@ -22,6 +20,5 @@ - - + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs index f3dd9a02..23923426 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -13,7 +12,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs index 28d564e2..0607c718 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs @@ -1,14 +1,13 @@ - - - - + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs index 22036ae5..f87c9387 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs @@ -1,7 +1,5 @@ - - - - + + @@ -30,6 +28,5 @@ - - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs index fd6f81ca..e2557fe1 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs @@ -1,7 +1,5 @@ - - - - + + @@ -16,6 +14,5 @@ - - + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs index b7f5ad07..38ce54b8 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs @@ -1,7 +1,5 @@ - - - - + + @@ -16,6 +14,5 @@ - - + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs index 59eeb027..75707f3d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -11,7 +10,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs index f20f5f73..b3fd3672 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -12,7 +11,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs index 3bd14fbb..91ac6537 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -10,7 +9,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs index 59c8b2b3..6269fe9d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs @@ -1,15 +1,14 @@ - - + - - + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs index e55b3ec6..befab53e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -14,7 +13,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs index 025aaaa3..090c7724 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -15,7 +14,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs index 44b8c2b5..bc7450e3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -17,7 +16,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs index cc873a62..6d9e854d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs @@ -1,8 +1,6 @@ - - - - - + + + @@ -18,7 +16,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs index 4baeb85b..c9dcdd72 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs @@ -1,29 +1,27 @@ - - - + + + - + - - - + + + - - + + - + - - - + + + - - + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs index 7c3cff7e..d39170c0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs @@ -1,23 +1,16 @@ - - + + - + - + - - + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs index ee133ba3..72424986 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs @@ -1,30 +1,28 @@ - - - + + + - - + + - - - + + + - - + + - + - + - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs index 5b26091a..c902c339 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -1,14 +1,13 @@ - - - - + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs index 388a271e..0d2d5032 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs @@ -1,14 +1,13 @@ - - - - + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs index b8adf6e4..08af1950 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs @@ -1,7 +1,5 @@ - - - - + + @@ -17,7 +15,6 @@ - @@ -33,5 +30,5 @@ - + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs index 879fad35..c8289464 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -10,7 +9,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs index 3a9e401c..1b602588 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs @@ -1,7 +1,5 @@ - - - - + + @@ -19,6 +17,5 @@ - - + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs index b04c5d1a..a858b351 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs @@ -1,14 +1,13 @@ - - - - + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs index 260339ba..737ac8df 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs @@ -1,7 +1,6 @@ - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs index 28d564e2..0607c718 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs @@ -1,14 +1,13 @@ - - - - + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs index c21a669c..852d1aed 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -16,7 +15,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs index 8d49c30e..7de55810 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs @@ -1,6 +1,4 @@ - - - + @@ -13,15 +11,15 @@ - - + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs index bf5223c1..d9714e7a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs @@ -1,7 +1,6 @@ - - - - + + + @@ -10,7 +9,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs index 4c36f3cc..b29a785f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs @@ -1,14 +1,13 @@ - - - - + + + - + -- cgit v1.2.3-55-g6feb From 2a8f47e357bfbfe20c962cade4455793e45dae7c Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 16 Nov 2020 22:13:33 -0500 Subject: Support an empty multiString registry value --- src/WixToolset.Core/Compiler_2.cs | 7 +------ src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs | 2 +- .../TestData/Registry/RegistryValueMultiString.wxs | 2 ++ 3 files changed, 4 insertions(+), 7 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index f55264e5..1922a70b 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -1977,16 +1977,11 @@ namespace WixToolset.Core } } - if (multiStringValue == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - this.Core.VerifyNoInnerText(sourceLineNumbers, node); this.Core.ParseForExtensionElements(node); - return (null == value) ? multiStringValue : String.Concat(value, "[~]", multiStringValue); + return null == value ? multiStringValue ?? "[~]" : String.Concat(value, "[~]", multiStringValue); } /// diff --git a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs index 2a1e2a49..699155d4 100644 --- a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs @@ -74,7 +74,7 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(new[] { "Registry:regitq_Wx9LfvJuNSc2un6gIHAzr4A\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMultiStringComponent", - "Registry:regmeTJMpOD41igfxhTcUVZ7kNG1Mo\t2\tPath\\To\\Key\t\ta[~]b[~]c\tMultiStringComponent", + "Registry:regmeTJMpOD41igfxhTcUVZ7kNG1Mo\t2\tPath\\To\\Key\t\ta[~]b[~][~]c[~]\tMultiStringComponent", }, results); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs index d5c680ee..c62c571d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs @@ -6,7 +6,9 @@ + + -- cgit v1.2.3-55-g6feb From 5a874790ba9ec6c2d3c9002699114c2fe4c493ae Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 2 Dec 2020 14:33:15 -0600 Subject: MSI transaction cleanup. --- .../OrderPackagesAndRollbackBoundariesCommand.cs | 36 +++--- .../MsiTransactionFixture.cs | 129 +++++++++++++++++++++ .../TestData/MsiTransaction/FirstX64.wxs | 8 ++ .../TestData/MsiTransaction/FirstX86.wxs | 8 ++ .../TestData/MsiTransaction/SecondX64.wxs | 8 ++ .../TestData/MsiTransaction/SecondX86.wxs | 8 ++ .../TestData/MsiTransaction/X64AfterX86Bundle.wxs | 12 ++ .../TestData/MsiTransaction/X86AfterX64Bundle.wxs | 12 ++ .../WixToolsetTest.CoreIntegration.csproj | 6 + 9 files changed, 212 insertions(+), 15 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs index 9e27d5a3..44299fd5 100644 --- a/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs @@ -44,9 +44,10 @@ namespace WixToolset.Core.Burn.Bundles // We handle uninstall (aka: backwards) rollback boundaries after // we get these install/repair (aka: forward) rollback boundaries // defined. - WixBundleRollbackBoundarySymbol previousRollbackBoundary = null; + WixBundleRollbackBoundarySymbol pendingRollbackBoundary = null; WixBundleRollbackBoundarySymbol lastRollbackBoundary = null; var boundaryHadX86Package = false; + var warnedMsiTransaction = false; foreach (var groupSymbol in this.GroupSymbols) { @@ -54,24 +55,30 @@ namespace WixToolset.Core.Burn.Bundles { if (this.PackageFacades.TryGetValue(groupSymbol.ChildId, out var facade)) { - if (null != previousRollbackBoundary) + var insideMsiTransaction = lastRollbackBoundary != null && lastRollbackBoundary.Transaction.HasValue && lastRollbackBoundary.Transaction.Value; + if (null != pendingRollbackBoundary) { - usedBoundaries.Add(previousRollbackBoundary); - facade.PackageSymbol.RollbackBoundaryRef = previousRollbackBoundary.Id.Id; - previousRollbackBoundary = null; + if (insideMsiTransaction && !warnedMsiTransaction) + { + warnedMsiTransaction = true; + this.Messaging.Write(WarningMessages.MsiTransactionLimitations(pendingRollbackBoundary.SourceLineNumbers)); + } - boundaryHadX86Package = facade.PackageSymbol.Win64; + usedBoundaries.Add(pendingRollbackBoundary); + facade.PackageSymbol.RollbackBoundaryRef = pendingRollbackBoundary.Id.Id; + pendingRollbackBoundary = null; + + boundaryHadX86Package = !facade.PackageSymbol.Win64; } // Error if MSI transaction has x86 package preceding x64 packages - if ((lastRollbackBoundary != null) - && lastRollbackBoundary.Transaction == true + if (insideMsiTransaction && boundaryHadX86Package && facade.PackageSymbol.Win64) { - this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64(lastRollbackBoundary.SourceLineNumbers)); + this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64(facade.PackageSymbol.SourceLineNumbers)); } - boundaryHadX86Package |= facade.PackageSymbol.Win64; + boundaryHadX86Package |= !facade.PackageSymbol.Win64; orderedFacades.Add(facade); } @@ -79,22 +86,21 @@ namespace WixToolset.Core.Burn.Bundles { // Discard the next rollback boundary if we have a previously defined boundary. var nextRollbackBoundary = this.Boundaries[groupSymbol.ChildId]; - if (null != previousRollbackBoundary) + if (null != pendingRollbackBoundary) { this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id.Id)); } else { - previousRollbackBoundary = nextRollbackBoundary; - lastRollbackBoundary = nextRollbackBoundary; + lastRollbackBoundary = pendingRollbackBoundary = nextRollbackBoundary; } } } } - if (null != previousRollbackBoundary) + if (null != pendingRollbackBoundary) { - this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.Id.Id)); + this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(pendingRollbackBoundary.SourceLineNumbers, pendingRollbackBoundary.Id.Id)); } // With the forward rollback boundaries assigned, we can now go diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs new file mode 100644 index 00000000..5a29eb9e --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs @@ -0,0 +1,129 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class MsiTransactionFixture + { + [Fact] + public void CantBuildX64AfterX86Bundle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var exePath = Path.Combine(binFolder, "test.exe"); + + BuildMsiPackages(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "X64AfterX86Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal(390, result.ExitCode); + } + } + + [Fact] + public void CanBuildX86AfterX64Bundle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var exePath = Path.Combine(binFolder, "test.exe"); + + BuildMsiPackages(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "X86AfterX64Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + private static void BuildMsiPackages(string folder, string intermediateFolder, string binFolder) + { + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "FirstX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "SecondX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "SecondX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(binFolder, "FirstX64.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "SecondX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(binFolder, "SecondX64.msi"), + }); + + result.AssertSuccess(); + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs new file mode 100644 index 00000000..8f4fc8bd --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs new file mode 100644 index 00000000..221f06c5 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index fdb56987..8e5a005c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -64,6 +64,12 @@ + + + + + + -- cgit v1.2.3-55-g6feb From 8a67cff70cdfa014cc068dcc7756ed57b2ed1642 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Tue, 8 Dec 2020 14:00:13 -0500 Subject: Decompiling null GUID should set `Guid=""` to match. --- .../Decompile/Decompiler.cs | 2 +- .../DecompileFixture.cs | 6 ++++++ .../TestData/DecompileNullComponent/Expected.wxs | 18 ++++++++++++++++++ .../TestData/DecompileNullComponent/example.cab | Bin 0 -> 137 bytes .../TestData/DecompileNullComponent/example.msi | Bin 0 -> 32768 bytes .../WixToolsetTest.CoreIntegration.csproj | 3 +++ 6 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index 80ee75a5..d11a890c 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -4152,7 +4152,7 @@ namespace WixToolset.Core.WindowsInstaller { var xComponent = new XElement(Names.ComponentElement, new XAttribute("Id", row.FieldAsString(0)), - row.IsColumnEmpty(1) ? null : new XAttribute("Guid", row.FieldAsString(1))); + new XAttribute("Guid", row.FieldAsString(1) ?? String.Empty)); var attributes = row.FieldAsInteger(3); diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs index 15082f2b..b07f5bda 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -72,5 +72,11 @@ namespace WixToolsetTest.CoreIntegration { DecompileAndCompare(@"TestData\Shortcut", "shortcuts.msi", "DecompiledShortcuts.wxs"); } + + [Fact] + public void CanDecompileNullComponent() + { + DecompileAndCompare(@"TestData\DecompileNullComponent", "example.msi", "Expected.wxs"); + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs new file mode 100644 index 00000000..89592150 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab new file mode 100644 index 00000000..125eeb2c Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi new file mode 100644 index 00000000..81335041 Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi differ diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 8e5a005c..6c494169 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -207,6 +207,9 @@ + + + -- cgit v1.2.3-55-g6feb From 0106f99945266b2391b18a8389d056375cfff0b0 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 8 Dec 2020 15:09:31 -0600 Subject: WIXFEAT:6209 - Move BA entry point to its own element. --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 8 +- .../Bind/GenerateManifestDataFromIRCommand.cs | 1 + .../Bundles/CreateBundleExeCommand.cs | 10 +- .../Bundles/CreateNonUXContainers.cs | 10 +- src/WixToolset.Core/Compiler_Bundle.cs | 101 ++++++++++++++------- src/test/Example.Extension/Data/example.wxs | 5 +- .../TestData/BundleWithPackageGroupRef/Bundle.wxs | 5 +- .../TestData/SimpleBundle/Bundle.wxs | 15 +-- .../MultiFileBootstrapperApplication.wxs | 5 +- 9 files changed, 101 insertions(+), 59 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 8522eb3e..2c8231f8 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -101,10 +101,10 @@ namespace WixToolset.Core.Burn bundleSymbol.Attributes |= WixBundleAttributes.PerMachine; // default to per-machine but the first-per user package wil flip the bundle per-user. - // Ensure there is one and only one row in the WixBootstrapperApplication table. + // Ensure there is one and only one row in the WixBootstrapperApplicationDll table. // The compiler and linker behavior should have colluded to get // this behavior. - var bundleApplicationSymbol = this.GetSingleSymbol(); + var bundleApplicationDllSymbol = this.GetSingleSymbol(); // Ensure there is one and only one row in the WixChain table. // The compiler and linker behavior should have colluded to get @@ -440,7 +440,7 @@ namespace WixToolset.Core.Burn IEnumerable uxPayloads; IEnumerable containers; { - var command = new CreateNonUXContainers(this.BackendHelper, section, bundleApplicationSymbol, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); + var command = new CreateNonUXContainers(this.BackendHelper, section, bundleApplicationDllSymbol, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); command.Execute(); fileTransfers.AddRange(command.FileTransfers); @@ -475,7 +475,7 @@ namespace WixToolset.Core.Burn } { - var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationSymbol, bundleSymbol, uxContainer, containers); + var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers); command.Execute(); fileTransfers.Add(command.Transfer); diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs index 29768dff..24a4ae67 100644 --- a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs @@ -61,6 +61,7 @@ namespace WixToolset.Core.Burn.Bind case SymbolDefinitionType.ProvidesDependency: case SymbolDefinitionType.WixApprovedExeForElevation: case SymbolDefinitionType.WixBootstrapperApplication: + case SymbolDefinitionType.WixBootstrapperApplicationDll: case SymbolDefinitionType.WixBundle: case SymbolDefinitionType.WixBundleCatalog: case SymbolDefinitionType.WixBundleContainer: diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs index 0355cdc6..576ac348 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs @@ -17,13 +17,13 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateBundleExeCommand { - public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBootstrapperApplicationSymbol bootstrapperApplicationSymbol, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable containers) + public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable containers) { this.Messaging = messaging; this.BackendHelper = backendHelper; this.IntermediateFolder = intermediateFolder; this.OutputPath = outputPath; - this.BootstrapperApplicationSymbol = bootstrapperApplicationSymbol; + this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; this.BundleSymbol = bundleSymbol; this.UXContainer = uxContainer; this.Containers = containers; @@ -39,7 +39,7 @@ namespace WixToolset.Core.Burn.Bundles private string OutputPath { get; } - private WixBootstrapperApplicationSymbol BootstrapperApplicationSymbol { get; } + private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } private WixBundleSymbol BundleSymbol { get; } @@ -72,7 +72,7 @@ namespace WixToolset.Core.Burn.Bundles var windowsAssemblyVersion = GetWindowsAssemblyVersion(this.BundleSymbol); - var applicationManifestData = GenerateApplicationManifest(this.BundleSymbol, this.BootstrapperApplicationSymbol, this.OutputPath, windowsAssemblyVersion); + var applicationManifestData = GenerateApplicationManifest(this.BundleSymbol, this.BootstrapperApplicationDllSymbol, this.OutputPath, windowsAssemblyVersion); UpdateBurnResources(bundleTempPath, this.OutputPath, this.BundleSymbol, windowsAssemblyVersion, applicationManifestData); @@ -101,7 +101,7 @@ namespace WixToolset.Core.Burn.Bundles } } - private static byte[] GenerateApplicationManifest(WixBundleSymbol bundleSymbol, WixBootstrapperApplicationSymbol bootstrapperApplicationSymbol, string outputPath, Version windowsAssemblyVersion) + private static byte[] GenerateApplicationManifest(WixBundleSymbol bundleSymbol, WixBootstrapperApplicationDllSymbol bootstrapperApplicationSymbol, string outputPath, Version windowsAssemblyVersion) { const string asmv1Namespace = "urn:schemas-microsoft-com:asm.v1"; const string asmv3Namespace = "urn:schemas-microsoft-com:asm.v3"; diff --git a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs index 3e54013a..0dd2ba15 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs @@ -14,11 +14,11 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateNonUXContainers { - public CreateNonUXContainers(IBackendHelper backendHelper, IntermediateSection section, WixBootstrapperApplicationSymbol bootstrapperApplicationSymbol, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) + public CreateNonUXContainers(IBackendHelper backendHelper, IntermediateSection section, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) { this.BackendHelper = backendHelper; this.Section = section; - this.BootstrapperApplicationSymbol = bootstrapperApplicationSymbol; + this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; this.PayloadSymbols = payloadSymbols; this.IntermediateFolder = intermediateFolder; this.LayoutFolder = layoutFolder; @@ -39,7 +39,7 @@ namespace WixToolset.Core.Burn.Bundles private IntermediateSection Section { get; } - private WixBootstrapperApplicationSymbol BootstrapperApplicationSymbol { get; } + private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } private Dictionary PayloadSymbols { get; } @@ -81,9 +81,9 @@ namespace WixToolset.Core.Burn.Bundles container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); container.AttachedContainerIndex = 0; - // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first + // Gather the list of UX payloads but ensure the BootstrapperApplicationDll Payload is the first // in the list since that is the Payload that Burn attempts to load. - var baPayloadId = this.BootstrapperApplicationSymbol.Id.Id; + var baPayloadId = this.BootstrapperApplicationDllSymbol.Id.Id; foreach (var uxPayload in containerPayloads) { diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 68b738fb..00f88c1f 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -647,18 +647,73 @@ namespace WixToolset.Core private void ParseBootstrapperApplicationElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; Identifier previousId = null; var previousType = ComplexReferenceChildType.Unknown; - var dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; - // The BootstrapperApplication element acts like a Payload element so delegate to the "Payload" attribute parsing code to parse and create a Payload entry. - var hasSourceFile = this.ParsePayloadElementContent(node, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId, false, out var id); - if (hasSourceFile) + foreach (var attrib in node.Attributes()) { - previousId = id; - previousType = ComplexReferenceChildType.Payload; + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } } + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "BootstrapperApplicationDll": + previousId = this.ParseBootstrapperApplicationDllElement(child, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (id != null) + { + this.Core.AddSymbol(new WixBootstrapperApplicationSymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parse the BoostrapperApplication element. + /// + /// Element to parse + private Identifier ParseBootstrapperApplicationDllElement(XElement node, ComplexReferenceChildType previousType, Identifier previousId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; + + // The BootstrapperApplicationDll element acts like a Payload element so delegate to the "Payload" attribute parsing code to parse and create a Payload entry. + this.ParsePayloadElementContent(node, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId, true, out var id); + foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) @@ -699,17 +754,9 @@ namespace WixToolset.Core { switch (child.Name.LocalName) { - case "Payload": - previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "PayloadGroupRef": - previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.PayloadGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; + default: + this.Core.UnexpectedElement(node, child); + break; } } else @@ -718,15 +765,6 @@ namespace WixToolset.Core } } - if (null == previousId) - { - // We need *either* or or even just @SourceFile on the BA... - // but we just say there's a missing . - // TODO: Is there a better message for this? - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Payload")); - } - - // Add the application as an attached container and if a SourceFile was provided add the Id as the BA. if (!this.Core.EncounteredError) { this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnUXContainerId) @@ -735,14 +773,13 @@ namespace WixToolset.Core Type = ContainerType.Attached }); - if (hasSourceFile) + this.Core.AddSymbol(new WixBootstrapperApplicationDllSymbol(sourceLineNumbers, id) { - this.Core.AddSymbol(new WixBootstrapperApplicationSymbol(sourceLineNumbers, id) - { - DpiAwareness = dpiAwareness, - }); - } + DpiAwareness = dpiAwareness, + }); } + + return id; } /// diff --git a/src/test/Example.Extension/Data/example.wxs b/src/test/Example.Extension/Data/example.wxs index cd17d478..af5d5086 100644 --- a/src/test/Example.Extension/Data/example.wxs +++ b/src/test/Example.Extension/Data/example.wxs @@ -1,4 +1,3 @@ - @@ -6,7 +5,9 @@ - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs index 207a8de1..e738b407 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs @@ -1,7 +1,8 @@ - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs index 7ef1fc05..21749c07 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs @@ -1,11 +1,12 @@ - - - - - - - + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs index 88c4cf1b..f5fe9885 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs @@ -1,6 +1,7 @@ - - + + + -- cgit v1.2.3-55-g6feb From e2f9e3cdc8e37693121fbc0ce61bdd325cbec626 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Wed, 9 Dec 2020 15:59:21 -0500 Subject: Add test to verify primary feature select in component categories. --- .../MsiQueryFixture.cs | 33 ++++++++++++++++++++ .../TestData/PublishComponent/Package.en-us.wxl | 11 +++++++ .../TestData/PublishComponent/Package.wxs | 36 ++++++++++++++++++++++ .../TestData/PublishComponent/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 3 ++ 5 files changed, 84 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 6409676e..11b1703c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -1004,5 +1004,38 @@ namespace WixToolsetTest.CoreIntegration }, files.Select(f => f.Name).ToArray()); } } + + [Fact] + public void CanPublishComponentWithMultipleFeatureComponents() + { + var folder = TestData.Get(@"TestData\PublishComponent"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "PublishComponent" }); + WixAssert.CompareLineByLine(new[] + { + "PublishComponent:{0A82C8F6-9CE9-4336-B8BE-91A39B5F7081} Qualifier2 Component2 AppData2 ProductFeature2", + "PublishComponent:{BD245B5A-EC33-46ED-98FF-E9D3D416AD04} Qualifier1 Component1 AppData1 ProductFeature1", + }, results); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs new file mode 100644 index 00000000..6a95e9da --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 6c494169..ff04e65f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -210,6 +210,9 @@ + + + -- cgit v1.2.3-55-g6feb From 7ce9de201708eb3b69b7dd8dee7c0b9ded15e905 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 16 Dec 2020 20:39:02 -0600 Subject: Canonicalize Payload/@Name. --- .../Bundles/ProcessPayloadsCommand.cs | 6 +- .../ExtensibilityServices/BackendHelper.cs | 8 ++ .../ExtensibilityServices/ParseHelper.cs | 27 ++++- .../PayloadFixture.cs | 117 +++++++++++++++++++++ .../TestData/Payload/AbsoluteName.wxs | 9 ++ .../TestData/Payload/CanonicalizeName.wxs | 7 ++ .../TestData/Payload/ValidName.wxs | 7 ++ .../WixToolsetTest.CoreIntegration.csproj | 3 + 8 files changed, 174 insertions(+), 10 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs index 475b04b8..69c4d7c2 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs @@ -50,11 +50,7 @@ namespace WixToolset.Core.Burn.Bundles foreach (var payload in this.Payloads) { - var normalizedPath = payload.Name.Replace('\\', '/'); - if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../")) - { - this.Messaging.Write(ErrorMessages.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name)); - } + payload.Name = this.BackendHelper.GetCanonicalRelativePath(payload.SourceLineNumbers, "Payload", "Name", payload.Name); // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden // in the .wixlib). diff --git a/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs index e4b6e959..7b20286c 100644 --- a/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs @@ -15,10 +15,13 @@ namespace WixToolset.Core.ExtensibilityServices public BackendHelper(IWixToolsetServiceProvider serviceProvider) { this.Messaging = serviceProvider.GetService(); + this.ParseHelper = serviceProvider.GetService(); } private IMessaging Messaging { get; } + private IParseHelper ParseHelper { get; } + public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) { var sourceFullPath = this.GetValidatedFullPath(sourceLineNumbers, source); @@ -49,6 +52,11 @@ namespace WixToolset.Core.ExtensibilityServices }; } + public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) + { + return this.ParseHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); + } + public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) { return new TrackedFile(path, type, sourceLineNumbers); diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs index af3f40aa..de5595e1 100644 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs @@ -540,11 +540,7 @@ namespace WixToolset.Core.ExtensibilityServices } else if (allowRelative) { - var normalizedPath = value.Replace('\\', '/'); - if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../")) - { - this.Messaging.Write(ErrorMessages.PayloadMustBeRelativeToCache(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } + value = this.GetCanonicalRelativePath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value); } else if (CompilerCore.IsAmbiguousFilename(value)) { @@ -705,6 +701,27 @@ namespace WixToolset.Core.ExtensibilityServices } } + public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) + { + const string root = @"C:\"; + if (!Path.IsPathRooted(relativePath)) + { + var normalizedPath = Path.GetFullPath(root + relativePath); + if (normalizedPath.StartsWith(root)) + { + var canonicalizedPath = normalizedPath.Substring(root.Length); + if (canonicalizedPath != relativePath) + { + this.Messaging.Write(WarningMessages.PathCanonicalized(sourceLineNumbers, elementName, attributeName, relativePath, canonicalizedPath)); + } + return canonicalizedPath; + } + } + + this.Messaging.Write(ErrorMessages.PayloadMustBeRelativeToCache(sourceLineNumbers, elementName, attributeName, relativePath)); + return relativePath; + } + public SourceLineNumber GetSourceLineNumbers(XElement element) { return Preprocessor.GetSourceLineNumbers(element); diff --git a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs new file mode 100644 index 00000000..4568f93f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs @@ -0,0 +1,117 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class PayloadFixture + { + [Fact] + public void CanParseValidName() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ValidName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + Assert.Empty(result.Messages); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var payloadSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(payloadSymbol); + + var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool + ? field.AsNullableNumber()?.ToString() + : field?.AsString()) + .ToList(); + Assert.Equal(@"dir\file.ext", fields[(int)WixBundlePayloadSymbolFields.Name]); + } + } + + [Fact] + public void CanCanonicalizeName() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CanonicalizeName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + Assert.Single(result.Messages, m => m.Id == (int)WarningMessages.Ids.PathCanonicalized); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var payloadSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(payloadSymbol); + + var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool + ? field.AsNullableNumber()?.ToString() + : field?.AsString()) + .ToList(); + Assert.Equal(@"c\d.exe", fields[(int)WixBundlePayloadSymbolFields.Name]); + } + } + + [Fact] + public void RejectsAbsoluteName() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AbsoluteName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.InRange(result.ExitCode, 2, int.MaxValue); + + Assert.Equal(1, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.IllegalRelativeLongFilename).Count()); + Assert.Equal(2, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.PayloadMustBeRelativeToCache).Count()); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs new file mode 100644 index 00000000..dc94d688 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs new file mode 100644 index 00000000..544b80ec --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs new file mode 100644 index 00000000..9c37a27d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index ff04e65f..9ebf1e5c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -70,6 +70,9 @@ + + + -- cgit v1.2.3-55-g6feb From 149867e176f22fe0af1a57355a7ace820710a791 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 19 Dec 2020 14:23:44 -0600 Subject: Add more failing tests and label them with the issue number. --- .../BundleFixture.cs | 56 +++++++++++++++++++++- .../CustomActionFixture.cs | 2 +- .../RegistryFixture.cs | 39 +++++++++++++-- .../TestData/BadInput/UnscheduledPackage.wxs | 16 +++++++ .../BadInput/UnscheduledRollbackBoundary.wxs | 16 +++++++ .../Registry/RegistryKeyEndingWithBackslash.wxs | 12 +++++ .../WixToolsetTest.CoreIntegration.csproj | 3 ++ 7 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index ad1648e6..1e3f1e24 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -245,7 +245,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] + [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/4628 public void CantBuildWithDuplicateCacheIds() { var folder = TestData.Get(@"TestData"); @@ -271,7 +271,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] + [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/4574 public void CantBuildWithDuplicatePayloadNames() { var folder = TestData.Get(@"TestData"); @@ -296,5 +296,57 @@ namespace WixToolsetTest.CoreIntegration Assert.InRange(result.ExitCode, 2, Int32.MaxValue); } } + + [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/6291 + public void CantBuildWithUnscheduledPackage() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "UnscheduledPackage.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + } + } + + [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/6291 + public void CantBuildWithUnscheduledRollbackBoundary() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "UnscheduledRollbackBoundary.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs index 967c38f8..00088fb9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs @@ -11,7 +11,7 @@ namespace WixToolsetTest.CoreIntegration public class CustomActionFixture { - [Fact(Skip = "Test demonstrates failure")] + [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/6201 public void CanDetectCustomActionCycle() { var folder = TestData.Get(@"TestData"); diff --git a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs index 699155d4..3b13d8f5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs @@ -39,7 +39,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Registry:reg04OIwIchl.9ZTjisTT6NzGSsQSM\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMiscComponent", "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent", @@ -71,7 +71,7 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Registry:regitq_Wx9LfvJuNSc2un6gIHAzr4A\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMultiStringComponent", "Registry:regmeTJMpOD41igfxhTcUVZ7kNG1Mo\t2\tPath\\To\\Key\t\ta[~]b[~][~]c[~]\tMultiStringComponent", @@ -104,11 +104,44 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(msiPath)); var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - Assert.Equal(new[] + WixAssert.CompareLineByLine(new[] { "Registry:RemoveAKeyName\t2\tAKeyName\t-\t\tRemoveRegistryKeyComp", }, results); } } + + [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/4753 + public void PopulatesRegistryTableWithoutExtraBackslash() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RegistryKeyEndingWithBackslash.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Registry:reg1\t2\tSoftware\\WBM\\WB\t*\t\tMiscComponent", + "Registry:reg2\t2\tSoftware\\WBM\\WB\tInstallationPath\t[INSTALLFOLDER]\tMiscComponent", + }, results); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs new file mode 100644 index 00000000..ab86982d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs new file mode 100644 index 00000000..8015ed92 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs new file mode 100644 index 00000000..1fb2e906 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index b6ab1e03..8680bb8a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -22,6 +22,8 @@ + + @@ -87,6 +89,7 @@ + -- cgit v1.2.3-55-g6feb From 85deb61f666f6817c1a137ace4d666c8ae2940fb Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 19 Dec 2020 14:41:19 -0600 Subject: 4862 - Disallow Burn Variables that are Hidden and Persisted. --- src/WixToolset.Core/Compiler_Bundle.cs | 5 +++++ .../BadInputFixture.cs | 23 ++++++++++++++++++++++ .../TestData/BadInput/BundleVariable.wxs | 3 +-- .../BadInput/HiddenPersistedBundleVariable.wxs | 6 ++++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 5 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 0817aef3..86fec16e 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -3178,6 +3178,11 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix")); } + if (hidden && persisted) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "yes", "Persisted")); + } + var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); this.Core.ParseForExtensionElements(node); diff --git a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs index 7a630a36..02422cf7 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs @@ -55,5 +55,28 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(21, result.ExitCode); } } + + [Fact] + public void BundleVariableWithHiddenPersistedIsRejected() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "HiddenPersistedBundleVariable.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(193, result.ExitCode); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs index 293b379f..a2d49b18 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs @@ -1,6 +1,5 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs new file mode 100644 index 00000000..5ebe5472 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs @@ -0,0 +1,6 @@ + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 8680bb8a..a38e89ce 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -21,6 +21,7 @@ + -- cgit v1.2.3-55-g6feb From d085e938317c80f62a3b484d20ed1a6cf89bb59d Mon Sep 17 00:00:00 2001 From: Nir Bar Date: Mon, 21 Dec 2020 05:05:45 -0600 Subject: Add CanExtractBundleWithDetachedContainer test. --- src/WixToolset.Core.Burn/BundleBackend.cs | 3 +- src/WixToolset.Core.TestPackage/BundleExtractor.cs | 17 +++++++ src/WixToolset.Core.TestPackage/WixRunnerResult.cs | 12 ++++- src/WixToolset.Core.WindowsInstaller/Unbinder.cs | 10 +++- .../WixToolsetCoreServiceProviderExtensions.cs | 1 + src/WixToolset.Core/Compiler_Bundle.cs | 12 ++++- src/WixToolset.Core/IUnbinder.cs | 12 +++++ .../BundleExtractionFixture.cs | 57 ++++++++++++++++++++++ .../BundleWithDetachedContainer/Bundle.wxs | 10 ++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 10 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 src/WixToolset.Core/IUnbinder.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/BundleBackend.cs b/src/WixToolset.Core.Burn/BundleBackend.cs index 4a2f44b1..081e4464 100644 --- a/src/WixToolset.Core.Burn/BundleBackend.cs +++ b/src/WixToolset.Core.Burn/BundleBackend.cs @@ -62,8 +62,9 @@ namespace WixToolset.Core.Burn { var uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); var acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); + var messaging = context.ServiceProvider.GetService(); - using (var reader = BurnReader.Open(context.InputFilePath)) + using (var reader = BurnReader.Open(messaging, context.InputFilePath)) { reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder); reader.ExtractAttachedContainer(acExtractPath, context.IntermediateFolder); diff --git a/src/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/WixToolset.Core.TestPackage/BundleExtractor.cs index ad97f113..8c9f31e6 100644 --- a/src/WixToolset.Core.TestPackage/BundleExtractor.cs +++ b/src/WixToolset.Core.TestPackage/BundleExtractor.cs @@ -44,6 +44,23 @@ namespace WixToolset.Core.TestPackage return result; } + /// + /// Extracts the attached container. + /// + /// + /// Path to the bundle. + /// Path to extract to. + /// Temp path for extraction. + /// True if there was an attached container. + public static bool ExtractAttachedContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) + { + Directory.CreateDirectory(tempFolderPath); + using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) + { + return burnReader.ExtractAttachedContainer(destinationFolderPath, tempFolderPath); + } + } + /// /// Gets an for BootstrapperApplicationData.xml with the given prefix assigned to the root namespace. /// diff --git a/src/WixToolset.Core.TestPackage/WixRunnerResult.cs b/src/WixToolset.Core.TestPackage/WixRunnerResult.cs index 13e3a9e0..6a3d714c 100644 --- a/src/WixToolset.Core.TestPackage/WixRunnerResult.cs +++ b/src/WixToolset.Core.TestPackage/WixRunnerResult.cs @@ -28,10 +28,20 @@ namespace WixToolset.Core.TestPackage /// public WixRunnerResult AssertSuccess() { - Assert.True(0 == this.ExitCode, $"\r\n\r\nWixRunner failed with exit code: {this.ExitCode}\r\n Output: {String.Join("\r\n ", FormatMessages(this.Messages))}\r\n"); + AssertSuccess(this.ExitCode, this.Messages); return this; } + /// + /// + /// + /// + /// + public static void AssertSuccess(int exitCode, IEnumerable messages) + { + Assert.True(0 == exitCode, $"\r\n\r\nWixRunner failed with exit code: {exitCode}\r\n Output: {String.Join("\r\n ", FormatMessages(messages))}\r\n"); + } + private static IEnumerable FormatMessages(IEnumerable messages) { foreach (var message in messages) diff --git a/src/WixToolset.Core.WindowsInstaller/Unbinder.cs b/src/WixToolset.Core.WindowsInstaller/Unbinder.cs index a2f02269..99caaba9 100644 --- a/src/WixToolset.Core.WindowsInstaller/Unbinder.cs +++ b/src/WixToolset.Core.WindowsInstaller/Unbinder.cs @@ -11,8 +11,16 @@ namespace WixToolset.Core /// /// Unbinder core of the WiX toolset. /// - internal sealed class Unbinder + internal sealed class Unbinder : IUnbinder { + public Unbinder(IWixToolsetServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + var extensionManager = this.ServiceProvider.GetService(); + this.BackendFactories = extensionManager.GetServices(); + } + public IEnumerable BackendFactories { get; } /// diff --git a/src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs b/src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs index c69f1af1..e013cefb 100644 --- a/src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs +++ b/src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs @@ -31,6 +31,7 @@ namespace WixToolset.Core.WindowsInstaller { // Singletons. coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new WindowsInstallerBackendHelper())); + coreProvider.AddService((provider, singletons) => new Unbinder(provider)); } private static T AddSingleton(Dictionary singletons, T service) where T : class diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 86fec16e..b8386138 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -566,9 +566,17 @@ namespace WixToolset.Core break; case "Type": var typeString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (!Enum.TryParse(typeString, out type)) + switch (typeString) { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached")); + case "attached": + type = ContainerType.Attached; + break; + case "detached": + type = ContainerType.Detached; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached")); + break; } break; default: diff --git a/src/WixToolset.Core/IUnbinder.cs b/src/WixToolset.Core/IUnbinder.cs new file mode 100644 index 00000000..2b4daaa5 --- /dev/null +++ b/src/WixToolset.Core/IUnbinder.cs @@ -0,0 +1,12 @@ +// 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 +{ + using WixToolset.Data; + +#pragma warning disable 1591 // TODO: add documentation, move into Extensibility + public interface IUnbinder + { + Intermediate Unbind(string file, OutputType outputType, string exportBasePath); + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs new file mode 100644 index 00000000..5c37c25b --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs @@ -0,0 +1,57 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class BundleExtractionFixture + { + [Fact] + public void CanExtractBundleWithDetachedContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + var baFolderPath = Path.Combine(extractFolderPath, "UX"); + var attachedContainerFolderPath = Path.Combine(extractFolderPath, "AttachedContainer"); + + // TODO: use WixRunner.Execute(string[]) to always go through the command line. + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleWithDetachedContainer", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }, serviceProvider, out var messages).Result; + + WixRunnerResult.AssertSuccess(result, messages); + Assert.Empty(messages.Where(m => m.Level == MessageLevel.Warning)); + + Assert.True(File.Exists(exePath)); + + var unbinder = serviceProvider.GetService(); + unbinder.Unbind(exePath, OutputType.Bundle, extractFolderPath); + + Assert.True(File.Exists(Path.Combine(baFolderPath, "manifest.xml"))); + Assert.False(Directory.Exists(attachedContainerFolderPath)); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs new file mode 100644 index 00000000..a93b23ef --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index a38e89ce..918635e9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -32,6 +32,7 @@ + -- cgit v1.2.3-55-g6feb From d5e31dec3a753f98955f3cde3d49a653cfc4aed0 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 2 Jan 2021 23:20:25 -0600 Subject: Add failing tests. --- .../BundleFixture.cs | 8 +- .../ContainerFixture.cs | 140 +++++++++++++++++++++ .../CustomActionFixture.cs | 2 +- .../PayloadFixture.cs | 27 ++++ .../RegistryFixture.cs | 2 +- .../RollbackBoundaryFixture.cs | 41 ++++++ .../Container/HarvestIntoDetachedContainer.wxs | 15 +++ .../Container/MultipleAttachedContainers.wxs | 15 +++ .../Payload/SharedBAAndPackagePayloadBundle.wxs | 16 +++ .../TestData/RollbackBoundary/BeginningOfChain.wxs | 9 ++ .../WixToolsetTest.CoreIntegration.csproj | 4 + 11 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index fae2ff4c..1e314281 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -216,7 +216,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/4628 + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/4628")] public void CantBuildWithDuplicateCacheIds() { var folder = TestData.Get(@"TestData"); @@ -242,7 +242,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/4574 + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/4574")] public void CantBuildWithDuplicatePayloadNames() { var folder = TestData.Get(@"TestData"); @@ -268,7 +268,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/6291 + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6291")] public void CantBuildWithUnscheduledPackage() { var folder = TestData.Get(@"TestData"); @@ -294,7 +294,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/6291 + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6291")] public void CantBuildWithUnscheduledRollbackBoundary() { var folder = TestData.Get(@"TestData"); diff --git a/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs new file mode 100644 index 00000000..0799cc24 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs @@ -0,0 +1,140 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class ContainerFixture + { + [Fact] + public void HarvestedPayloadsArePutInCorrectContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "FirstX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "FirstX64.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); + Assert.Equal(4, payloads.Count); + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + Assert.Equal(@"", payloads[0].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[1].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[2].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[3].GetTestXml(ignoreAttributes)); + } + } + + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6144")] + public void MultipleAttachedContainersAreNotCurrentlySupported() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "FirstX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "FirstX64.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "MultipleAttachedContainers.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs index 00088fb9..65f4be31 100644 --- a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs @@ -11,7 +11,7 @@ namespace WixToolsetTest.CoreIntegration public class CustomActionFixture { - [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/6201 + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6201")] public void CanDetectCustomActionCycle() { var folder = TestData.Get(@"TestData"); diff --git a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs index 4fc57c76..5b6bbeb5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs @@ -3,6 +3,7 @@ namespace WixToolsetTest.CoreIntegration { using System; + using System.Collections.Generic; using System.IO; using System.Linq; using WixBuildTools.TestSupport; @@ -114,5 +115,31 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal(expectedPayloadMustBeRelativeToCache, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.PayloadMustBeRelativeToCache).Count()); } } + + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/5273")] + public void RejectsPayloadSharedBetweenPackageAndBA() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Payload", "SharedBAAndPackagePayloadBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + Assert.InRange(result.ExitCode, 2, int.MaxValue); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs index 3b13d8f5..a7d6edb4 100644 --- a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs @@ -111,7 +111,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "Test demonstrates failure")] //https://github.com/wixtoolset/issues/issues/4753 + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/4753")] public void PopulatesRegistryTableWithoutExtraBackslash() { var folder = TestData.Get(@"TestData"); diff --git a/src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs new file mode 100644 index 00000000..b713920c --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs @@ -0,0 +1,41 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class RollbackBoundaryFixture + { + [Fact(Skip = "Test demonstrates failure")] + public void CanStartChainWithRollbackBoundary() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "RollbackBoundary", "BeginningOfChain.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs new file mode 100644 index 00000000..e175a18f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs new file mode 100644 index 00000000..28900e55 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs new file mode 100644 index 00000000..4cfeb99f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs new file mode 100644 index 00000000..ecfccfcb --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 918635e9..595c8a39 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -38,6 +38,8 @@ + + @@ -72,6 +74,7 @@ + @@ -187,6 +190,7 @@ + -- cgit v1.2.3-55-g6feb From da9e593a5c044cf5430b071dd194b9d94877c4e1 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 3 Jan 2021 14:06:13 -0600 Subject: Fix parsing of DpiAwareness attribute. --- src/WixToolset.Core/Compiler_Bundle.cs | 2 +- .../BootstrapperApplicationFixture.cs | 46 ++++++++++++++++++++++ .../BootstrapperApplication/DpiAwareness.wxs | 7 ++++ .../WixToolsetTest.CoreIntegration.csproj | 1 + 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 482232c7..40b44bbd 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -1404,7 +1404,7 @@ namespace WixToolset.Core enableSignatureVerification = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; case "DpiAwareness": - if (node.Name.LocalName != "BootstrapperApplication") + if (node.Name.LocalName != "BootstrapperApplicationDll") { this.Core.UnexpectedAttribute(node, attrib); } diff --git a/src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs new file mode 100644 index 00000000..9bdc9496 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs @@ -0,0 +1,46 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class BootstrapperApplicationFixture + { + [Fact] + public void CanSetBootstrapperApplicationDllDpiAwareness() + { + var folder = TestData.Get(@"TestData\BootstrapperApplication"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DpiAwareness.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var baDllSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(baDllSymbol); + + Assert.Equal(WixBootstrapperApplicationDpiAwarenessType.GdiScaled, baDllSymbol.DpiAwareness); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs new file mode 100644 index 00000000..5b41e807 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 595c8a39..b39b5cfb 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -27,6 +27,7 @@ + -- cgit v1.2.3-55-g6feb From b00c72ed0ef19d2e46f60361fa06821b0bd5ec94 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 5 Jan 2021 15:13:04 -0800 Subject: Fix up tests to work well under NCrunch plus whitespace clean up --- .../WixToolset.Core.v3.ncrunchproject | 7 + .../CompileCoreTestExtensionWixlib.csproj | 21 +- .../Example.Extension/Example.Extension.csproj | 28 +-- .../MsiQueryFixture.cs | 2 - .../ProductWithComponentGroupRef/Product.wxs | 1 - .../WixToolsetTest.CoreIntegration.csproj | 214 +-------------------- 6 files changed, 32 insertions(+), 241 deletions(-) create mode 100644 src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject b/src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject new file mode 100644 index 00000000..c6001ebe --- /dev/null +++ b/src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + ..\..\version.json + + + \ No newline at end of file diff --git a/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj b/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj index 41c10d40..02597b18 100644 --- a/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj +++ b/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj @@ -4,10 +4,29 @@ netcoreapp2.1 + false Exe - \ No newline at end of file + + + + $(BaseOutputPath)TestData\$(Configuration)\example.wixlib + + + + + + + + + + diff --git a/src/test/Example.Extension/Example.Extension.csproj b/src/test/Example.Extension/Example.Extension.csproj index d7a3b729..f8a09164 100644 --- a/src/test/Example.Extension/Example.Extension.csproj +++ b/src/test/Example.Extension/Example.Extension.csproj @@ -6,39 +6,19 @@ netcoreapp2.1 false embedded - $(OutputPath)netcoreapp2.1\CompileCoreTestExtensionWixlib.dll - - - false - - - false - - + + - + - + - - - $(IntermediateOutputPath)Example.wixlib - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index b71b62cb..00738965 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -341,8 +341,6 @@ namespace WixToolsetTest.CoreIntegration } } - - [Fact] public void PopulatesDirectoryTableWithValidDefaultDir() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs index c902c339..f297c9e9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -1,6 +1,5 @@  - diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index b39b5cfb..c12a7e22 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -9,219 +9,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + -- cgit v1.2.3-55-g6feb From 0d1851c79901ba6ddbba9bb63f758760fe5be994 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 5 Jan 2021 15:13:37 -0800 Subject: Fix handling of duplicate directories --- .../Bind/AddRequiredStandardDirectories.cs | 16 ++++----- .../Link/FindEntrySectionAndLoadSymbolsCommand.cs | 12 +++++-- .../Link/IntermediateSymbolExtensions.cs | 6 ++-- .../Link/ResolveReferencesCommand.cs | 4 +-- src/WixToolset.Core/Linker.cs | 5 +++ .../DirectoryFixture.cs | 39 ++++++++++++++++++++++ .../TestData/DuplicateDir/DuplicateDir.wxs | 25 ++++++++++++++ 7 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs index 4597639b..ee3bcc91 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs @@ -27,7 +27,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind public void Execute() { var directories = this.Section.Symbols.OfType().ToList(); - var directoriesById = directories.ToDictionary(d => d.Id.Id); + var directoryIds = new SortedSet(directories.Select(d => d.Id.Id)); foreach (var directory in directories) { @@ -42,20 +42,20 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else { - this.EnsureStandardDirectoryAdded(directoriesById, parentDirectoryId, directory.SourceLineNumbers); + this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, directory.SourceLineNumbers); } } - if (!directoriesById.ContainsKey("TARGETDIR") && WindowsInstallerStandard.TryGetStandardDirectory("TARGETDIR", out var targetDir)) + if (!directoryIds.Contains("TARGETDIR") && WindowsInstallerStandard.TryGetStandardDirectory("TARGETDIR", out var targetDir)) { - directoriesById.Add(targetDir.Id.Id, targetDir); + directoryIds.Add(targetDir.Id.Id); this.Section.AddSymbol(targetDir); } } - private void EnsureStandardDirectoryAdded(Dictionary directoriesById, string directoryId, SourceLineNumber sourceLineNumbers) + private void EnsureStandardDirectoryAdded(ISet directoryIds, string directoryId, SourceLineNumber sourceLineNumbers) { - if (!directoriesById.ContainsKey(directoryId) && WindowsInstallerStandard.TryGetStandardDirectory(directoryId, out var standardDirectory)) + if (!directoryIds.Contains(directoryId) && WindowsInstallerStandard.TryGetStandardDirectory(directoryId, out var standardDirectory)) { var parentDirectoryId = this.GetStandardDirectoryParent(directoryId); @@ -65,12 +65,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind ParentDirectoryRef = parentDirectoryId, }; - directoriesById.Add(directory.Id.Id, directory); + directoryIds.Add(directory.Id.Id); this.Section.AddSymbol(directory); if (!String.IsNullOrEmpty(parentDirectoryId)) { - this.EnsureStandardDirectoryAdded(directoriesById, parentDirectoryId, sourceLineNumbers); + this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, sourceLineNumbers); } } } diff --git a/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs index 1c2ca8eb..a4b2bee3 100644 --- a/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs +++ b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs @@ -38,10 +38,17 @@ namespace WixToolset.Core.Link /// public IEnumerable PossibleConflicts { get; private set; } + /// + /// Gets the collection of redundant symbols that should not be included + /// in the final output. + /// + public ISet RedundantSymbols { get; private set; } + public void Execute() { var symbolsByName = new Dictionary(); var possibleConflicts = new HashSet(); + var redundantSymbols = new HashSet(); if (!Enum.TryParse(this.ExpectedOutputType.ToString(), out SectionType expectedEntrySectionType)) { @@ -91,13 +98,13 @@ namespace WixToolset.Core.Link // Ensure identical symbol's symbol is marked redundant to ensure (should the symbol be // referenced into the final output) it will not add duplicate primary keys during // the .IDT importing. - //symbol.Row.Redundant = true; - TODO: remove this existingSymbol.AddRedundant(symbolWithSection); + redundantSymbols.Add(symbolWithSection.Symbol); } else { existingSymbol.AddPossibleConflict(symbolWithSection); - possibleConflicts.Add(existingSymbol); + possibleConflicts.Add(symbolWithSection); } } } @@ -105,6 +112,7 @@ namespace WixToolset.Core.Link this.SymbolsByName = symbolsByName; this.PossibleConflicts = possibleConflicts; + this.RedundantSymbols = redundantSymbols; } } } diff --git a/src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs b/src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs index db53f1ce..cbf48abe 100644 --- a/src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs +++ b/src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs @@ -1,4 +1,4 @@ -// 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. +// 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.Link { @@ -9,10 +9,10 @@ namespace WixToolset.Core.Link public static bool IsIdentical(this IntermediateSymbol first, IntermediateSymbol second) { var identical = (first.Definition.Type == second.Definition.Type && - first.Definition.Name == second.Definition.Name && + (first.Definition.Type != SymbolDefinitionType.MustBeFromAnExtension || first.Definition.Name == second.Definition.Name) && first.Definition.FieldDefinitions.Length == second.Definition.FieldDefinitions.Length); - for (int i = 0; identical && i < first.Definition.FieldDefinitions.Length; ++i) + for (var i = 0; identical && i < first.Definition.FieldDefinitions.Length; ++i) { var firstField = first[i]; var secondField = second[i]; diff --git a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs index 90b61e8b..2bdd5646 100644 --- a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs +++ b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs @@ -170,9 +170,9 @@ namespace WixToolset.Core.Link case AccessModifier.Public: return true; case AccessModifier.Internal: - return symbolWithSection.Section.CompilationId.Equals(referencingSection.CompilationId) || (null != symbolWithSection.Section.LibraryId && symbolWithSection.Section.LibraryId.Equals(referencingSection.LibraryId)); + return symbolWithSection.Section.CompilationId == referencingSection.CompilationId || (null != symbolWithSection.Section.LibraryId && symbolWithSection.Section.LibraryId == referencingSection.LibraryId); case AccessModifier.Protected: - return symbolWithSection.Section.CompilationId.Equals(referencingSection.CompilationId); + return symbolWithSection.Section.CompilationId == referencingSection.CompilationId; case AccessModifier.Private: return referencingSection == symbolWithSection.Section; default: diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index e9f9554c..431ba4c7 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -226,6 +226,11 @@ namespace WixToolset.Core foreach (var symbol in section.Symbols) { + if (find.RedundantSymbols.Contains(symbol)) + { + continue; + } + var copySymbol = true; // by default, copy symbols. // handle special tables diff --git a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs index 83f2f2bb..2d6e4802 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs @@ -85,5 +85,44 @@ namespace WixToolsetTest.CoreIntegration }, dirSymbols.Select(d => d.Id.Id).ToArray()); } } + + [Fact] + public void CanGetDuplicateDir() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "DuplicateDir", "DuplicateDir.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "dirZsSsu81KcG46xXTwc4mTSZO5Zx4", + "INSTALLFOLDER", + "ProgramFiles6432Folder", + "ProgramFiles64Folder", + "TARGETDIR" + }, dirSymbols.Select(d => d.Id.Id).ToArray()); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs new file mode 100644 index 00000000..ffee969d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 093e1dd144b260b58a0ae46d722d1dbc4019d9d5 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 6 Jan 2021 15:15:35 -0800 Subject: Implement improved file sequence optimization First ensures files are grouped by DiskId. Then files are sequenced by target directory order to optimize MSI installation behavior. Finally, files are alphabetized in the directory. Additional optimizations could be considered in the future from here. Fixes wixtoolset/issues#4409 Fixes wixtoolset/issues#4708 --- .../Bind/BindDatabaseCommand.cs | 2 +- .../Bind/OptimizeFileFacadesOrderCommand.cs | 92 ++++++++++++++++++++-- src/WixToolset.Core/Linker.cs | 6 +- .../WixToolsetTest.CoreIntegration/CabFixture.cs | 2 +- .../WixToolsetTest.CoreIntegration/MediaFixture.cs | 62 +++++++++++++++ .../TestData/Media/MultiMedia.wxs | 28 +++++++ .../TestData/Media/data/a1.txt | 1 + .../TestData/Media/data/a2.txt | 1 + .../TestData/Media/data/b1.txt | 1 + .../TestData/Media/data/b2.txt | 1 + 10 files changed, 185 insertions(+), 11 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 93c617d9..4b3d554a 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -364,7 +364,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind Dictionary> filesByCabinetMedia; IEnumerable uncompressedFiles; { - var order = new OptimizeFileFacadesOrderCommand(fileFacades); + var order = new OptimizeFileFacadesOrderCommand(this.BackendHelper, this.PathResolver, section, platform, fileFacades); order.Execute(); fileFacades = order.FileFacades; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs index 6943d345..e96dfd91 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs @@ -4,34 +4,112 @@ namespace WixToolset.Core.WindowsInstaller.Bind { using System; using System.Collections.Generic; + using System.Linq; using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; internal class OptimizeFileFacadesOrderCommand { - public OptimizeFileFacadesOrderCommand(List fileFacades) + public OptimizeFileFacadesOrderCommand(IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform, List fileFacades) { + this.BackendHelper = helper; + this.PathResolver = pathResolver; + this.Section = section; + this.Platform = platform; this.FileFacades = fileFacades; } public List FileFacades { get; private set; } + private IBackendHelper BackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private IntermediateSection Section { get; } + + private Platform Platform { get; } + public List Execute() { - this.FileFacades.Sort(FileFacadeOptimizer.Instance); + var canonicalComponentTargetPaths = this.ComponentTargetPaths(); + + this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths)); return this.FileFacades; } + private Dictionary ComponentTargetPaths() + { + var directories = this.ResolveDirectories(); + + var canonicalPathsByDirectoryId = new Dictionary(); + foreach (var component in this.Section.Symbols.OfType()) + { + var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(directories, null, component.DirectoryRef, this.Platform); + canonicalPathsByDirectoryId.Add(component.Id.Id, directoryPath); + } + + return canonicalPathsByDirectoryId; + } + + private Dictionary ResolveDirectories() + { + var targetPathsByDirectoryId = new Dictionary(); + + // Get the target paths for all directories. + foreach (var directory in this.Section.Symbols.OfType()) + { + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); + targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); + } + + return targetPathsByDirectoryId; + } + private class FileFacadeOptimizer : IComparer { - public static readonly FileFacadeOptimizer Instance = new FileFacadeOptimizer(); + public FileFacadeOptimizer(Dictionary componentTargetPaths) + { + this.ComponentTargetPaths = componentTargetPaths; + } + + private Dictionary ComponentTargetPaths { get; } public int Compare(FileFacade x, FileFacade y) { - // TODO: Sort these facades even smarter by directory path and component id - // and maybe file size or file extension and other creative ideas to - // get optimal install speed out of MSI. - return String.Compare(x.ComponentRef, y.ComponentRef, StringComparison.Ordinal); + // First group files by DiskId. + var compare = x.DiskId.CompareTo(y.DiskId); + + if (compare != 0) + { + return compare; + } + + // Next try to group files by target install directory. + if (this.ComponentTargetPaths.TryGetValue(x.ComponentRef, out var canonicalX) && + this.ComponentTargetPaths.TryGetValue(y.ComponentRef, out var canonicalY)) + { + compare = String.Compare(canonicalX, canonicalY, StringComparison.Ordinal); + + if (compare != 0) + { + return compare; + } + } + + // TODO: Consider sorting these facades even smarter by file size or file extension + // or other creative ideas to get optimal install speed out of MSI. + compare = String.Compare(x.FileName, y.FileName, StringComparison.Ordinal); + + if (compare != 0) + { + return compare; + } + + return String.Compare(x.Id, y.Id, StringComparison.Ordinal); } } } diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index 431ba4c7..e0af89ba 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -198,8 +198,10 @@ namespace WixToolset.Core } // Report duplicates that would ultimately end up being primary key collisions. - var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections); - reportDupes.Execute(); + { + var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections); + reportDupes.Execute(); + } if (this.Messaging.EncounteredError) { diff --git a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs index 5aef148e..ad62dea6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs @@ -42,7 +42,7 @@ namespace WixToolsetTest.CoreIntegration var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray()); - Assert.Equal(new[] { "test.txt", "Notepad.exe" }, fileRows.Select(f => f.Name).ToArray()); + Assert.Equal(new[] { "Notepad.exe", "test.txt" }, fileRows.Select(f => f.Name).ToArray()); var files = Query.GetCabinetFiles(cabPath); Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); diff --git a/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs new file mode 100644 index 00000000..de18e30c --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs @@ -0,0 +1,62 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class MediaFixture + { + [Fact] + public void CanBuildMultiMedia() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Media", "MultiMedia.wxs"), + "-bindpath", Path.Combine(folder, "Media", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var mediaSymbols = section.Symbols.OfType().OrderBy(m => m.DiskId).ToList(); + var fileSymbols = section.Symbols.OfType().OrderBy(f => f.Sequence).ToList(); + Assert.Equal(1, mediaSymbols[0].DiskId); + Assert.Equal(2, mediaSymbols[0].LastSequence); + Assert.Equal(2, mediaSymbols[1].DiskId); + Assert.Equal(4, mediaSymbols[1].LastSequence); + Assert.Equal(new[] + { + "a1.txt", + "a2.txt", + "b1.txt", + "b2.txt", + }, fileSymbols.Select(f => f.Name).ToArray()); + Assert.Equal(new[] + { + 1, + 2, + 3, + 4, + }, fileSymbols.Select(f => f.Sequence).ToArray()); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs new file mode 100644 index 00000000..8a555bda --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt new file mode 100644 index 00000000..ad9cdcb5 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt @@ -0,0 +1 @@ +This is a1.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt new file mode 100644 index 00000000..d5de23de --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt @@ -0,0 +1 @@ +This is a2.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt new file mode 100644 index 00000000..88bc4a56 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt @@ -0,0 +1 @@ +This is b1.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt new file mode 100644 index 00000000..38525276 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt @@ -0,0 +1 @@ +This is b2.txt \ No newline at end of file -- cgit v1.2.3-55-g6feb From 1b10d394bc88c2840b355bb72c1a502181c28ca2 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 7 Jan 2021 14:50:40 -0800 Subject: Add test for invalid ids Closes wixtoolset/issues#5464 --- .../BadInputFixture.cs | 23 ++++++++++++++++++++++ .../TestData/BadInput/InvalidIds.wxs | 8 ++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs index 874151e4..c5168856 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs @@ -25,6 +25,29 @@ namespace WixToolsetTest.CoreIntegration //Assert.Equal((int)ErrorMessages.Ids.ExpectedArgument, result.ExitCode); } + [Fact] + public void HandleInvalidIds() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "InvalidIds.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(330, result.ExitCode); + } + } + [Fact(Skip = "Test demonstrates failure")] public void CantBuildSingleExeBundleWithInvalidArgument() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs new file mode 100644 index 00000000..78f3ebd3 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs @@ -0,0 +1,8 @@ + + + + + + + + -- cgit v1.2.3-55-g6feb From a36c59a4911a7db525f6b03dc98fac5adde163b4 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 7 Jan 2021 15:12:13 -0800 Subject: Support environment variables with parens in the preprocessor Fixes wixtoolset/issues#4484 --- .../ExtensibilityServices/PreprocessHelper.cs | 8 ++++++++ .../PreprocessorFixture.cs | 18 ++++++++++++++++++ .../TestData/Preprocessor/EnvParens.wxs | 4 ++++ 3 files changed, 30 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs index df301196..041c7d5d 100644 --- a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs @@ -386,6 +386,14 @@ namespace WixToolset.Core.ExtensibilityServices } } + // Environment variables may contain parens so if it looks + // like a function, check to see if the environment variable + // prefix was explicitly provided. + if (isFunction && remainder.StartsWith("(env.", StringComparison.Ordinal)) + { + isFunction = false; + } + // move the currentPosition to the closing paren currentPosition += closingParenPosition; diff --git a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs index a6504cb9..aad3ed73 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs @@ -39,6 +39,24 @@ namespace WixToolsetTest.CoreIntegration Assert.Null(includedFile.SourceLineNumbers.Parent); } + [Fact] + /// + /// This test will fail on 32-bit operating systems because it depends on "CommonProgramFiles(x86)" + /// which is only defined on 64-bit Windows. + /// + public void SupportParensInEnvironmentVariables() + { + var folder = TestData.Get(@"TestData", "Preprocessor"); + + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var context = serviceProvider.GetService(); + context.SourcePath = Path.Combine(folder, "EnvParens.wxs"); + + var preprocessor = serviceProvider.GetService(); + var result = preprocessor.Preprocess(context); + Assert.NotNull(result.Document); + } + [Fact] public void VariableRedefinitionIsAWarning() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs new file mode 100644 index 00000000..68d115c5 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs @@ -0,0 +1,4 @@ + + + + -- cgit v1.2.3-55-g6feb From c1605aa577e304fe0fb4c57056b58754bfaf3666 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 8 Jan 2021 13:46:36 -0800 Subject: Require or recommend ExePackage/@DetectCondition Fixes wixtoolset/issues#6197 --- src/WixToolset.Core/Compiler_Bundle.cs | 16 ++++++- .../ExePackageFixture.cs | 52 ++++++++++++++++++++++ .../TestData/ExePackage/MissingDetectCondition.wxs | 9 ++++ .../TestData/ExePackage/RequireDetectCondition.wxs | 11 +++++ .../SingleExeBundle/SingleExePackageGroup.wxs | 2 +- 5 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 40b44bbd..1bee3823 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -2232,7 +2232,7 @@ namespace WixToolset.Core allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msp); break; case "DetectCondition": - detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu); break; case "Protocol": @@ -2394,6 +2394,20 @@ namespace WixToolset.Core perMachine = YesNoDefaultType.Default; } + // Detect condition is recommended or required for Exe and Msu packages + // (depending on whether uninstall arguments were provided). + if ((packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu) && String.IsNullOrEmpty(detectCondition)) + { + if (String.IsNullOrEmpty(uninstallCommand)) + { + this.Core.Write(WarningMessages.DetectConditionRecommended(sourceLineNumbers, node.Name.LocalName)); + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeWithValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DetectCondition", "UninstallCommand")); + } + } + // Now that the package ID is known, we can parse the extension attributes... var contextValues = new Dictionary() { { "PackageId", id.Id } }; foreach (var attribute in extensionAttributes) diff --git a/src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs new file mode 100644 index 00000000..e2306dcd --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs @@ -0,0 +1,52 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ExePackageFixture + { + [Fact] + public void ErrorWhenMissingDetectCondition() + { + var folder = TestData.Get(@"TestData", "ExePackage"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MissingDetectCondition.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(1153, result.ExitCode); + } + } + + [Fact] + public void ErrorWhenRequireDetectCondition() + { + var folder = TestData.Get(@"TestData", "ExePackage"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "RequireDetectCondition.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(401, result.ExitCode); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs new file mode 100644 index 00000000..21b4269b --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs new file mode 100644 index 00000000..42253f18 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs index 9d7a9511..cad1f049 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs @@ -2,7 +2,7 @@ - + -- cgit v1.2.3-55-g6feb From 4362960feec4a770f665419eaebd7b4ed14ecb9e Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 8 Jan 2021 14:18:26 -0800 Subject: Validate MSU package build Closes wixtoolset/issues#6006 --- .../MsuPackageFixture.cs | 36 ++++++++++++++++++++++ .../TestData/MsuPackage/Bundle.wxs | 11 +++++++ .../TestData/MsuPackage/data/fakeba.dll | 1 + .../TestData/MsuPackage/data/test.msu | 1 + 4 files changed, 49 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs new file mode 100644 index 00000000..475afcf0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs @@ -0,0 +1,36 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class MsuPackageFixture + { + [Fact] + public void CanBuildBundleWithMsuPackage() + { + var folder = TestData.Get(@"TestData", "MsuPackage"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, "bin", "test.exe") + }); + + result.AssertSuccess(); + Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.exe"))); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs new file mode 100644 index 00000000..dbca3393 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll new file mode 100644 index 00000000..b3cf17d8 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll @@ -0,0 +1 @@ +This is a fake BA DLL diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu new file mode 100644 index 00000000..d63da4be --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu @@ -0,0 +1 @@ +This is a fake MSU package -- cgit v1.2.3-55-g6feb From 4de24fe6e542391a9cdd55b00df9a1664273c5e2 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 8 Jan 2021 15:32:36 -0800 Subject: Rename ExePackage/@XxxCommand attributes to @XxxArguments Fixes wixtoolset/issues#6245 --- src/WixToolset.Core/Compiler_Bundle.cs | 40 +++++++++++----------- .../TestData/ExePackage/MissingDetectCondition.wxs | 2 +- .../TestData/ExePackage/RequireDetectCondition.wxs | 4 +-- .../SingleExeBundle/SingleExeRemotePayload.wxs | 6 ++-- 4 files changed, 26 insertions(+), 26 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 1bee3823..f0060a3e 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -2112,9 +2112,9 @@ namespace WixToolset.Core var permanent = YesNoType.NotSet; var visible = YesNoType.NotSet; var vital = YesNoType.Yes; - string installCommand = null; - string repairCommand = null; - string uninstallCommand = null; + string installArguments = null; + string repairArguments = null; + string uninstallArguments = null; var perMachine = YesNoDefaultType.NotSet; string detectCondition = null; string protocol = null; @@ -2215,16 +2215,16 @@ namespace WixToolset.Core case "Vital": vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; - case "InstallCommand": - installCommand = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + case "InstallArguments": + installArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Exe); break; - case "RepairCommand": - repairCommand = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + case "RepairArguments": + repairArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); allowed = (packageType == WixBundlePackageType.Exe); break; - case "UninstallCommand": - uninstallCommand = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + case "UninstallArguments": + uninstallArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Exe); break; case "PerMachine": @@ -2371,19 +2371,19 @@ namespace WixToolset.Core { foreach (var expectedArgument in expectedNetFx4Args) { - if (null == installCommand || -1 == installCommand.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) + if (null == installArguments || -1 == installArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) { - this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallCommand", installCommand, expectedArgument, "Protocol", "netfx4")); + this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallArguments", installArguments, expectedArgument, "Protocol", "netfx4")); } - if (!String.IsNullOrEmpty(repairCommand) && -1 == repairCommand.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) + if (!String.IsNullOrEmpty(repairArguments) && -1 == repairArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) { - this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairCommand", repairCommand, expectedArgument, "Protocol", "netfx4")); + this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairArguments", repairArguments, expectedArgument, "Protocol", "netfx4")); } - if (!String.IsNullOrEmpty(uninstallCommand) && -1 == uninstallCommand.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) + if (!String.IsNullOrEmpty(uninstallArguments) && -1 == uninstallArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) { - this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallCommand", uninstallCommand, expectedArgument, "Protocol", "netfx4")); + this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallArguments", uninstallArguments, expectedArgument, "Protocol", "netfx4")); } } } @@ -2398,13 +2398,13 @@ namespace WixToolset.Core // (depending on whether uninstall arguments were provided). if ((packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu) && String.IsNullOrEmpty(detectCondition)) { - if (String.IsNullOrEmpty(uninstallCommand)) + if (String.IsNullOrEmpty(uninstallArguments)) { this.Core.Write(WarningMessages.DetectConditionRecommended(sourceLineNumbers, node.Name.LocalName)); } else { - this.Core.Write(ErrorMessages.ExpectedAttributeWithValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DetectCondition", "UninstallCommand")); + this.Core.Write(ErrorMessages.ExpectedAttributeWithValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DetectCondition", "UninstallArguments")); } } @@ -2526,9 +2526,9 @@ namespace WixToolset.Core { Attributes = WixBundleExePackageAttributes.None, DetectCondition = detectCondition, - InstallCommand = installCommand, - RepairCommand = repairCommand, - UninstallCommand = uninstallCommand, + InstallCommand = installArguments, + RepairCommand = repairArguments, + UninstallCommand = uninstallArguments, ExeProtocol = protocol }); break; diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs index 21b4269b..e57180f7 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs @@ -2,7 +2,7 @@ - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs index 42253f18..0b094860 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs @@ -3,8 +3,8 @@ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs index 709dc9e7..6c6903b1 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs @@ -3,9 +3,9 @@ Date: Wed, 27 Jan 2021 20:06:23 -0600 Subject: Add patch test from old repo and get it passing. Fixes #6341. --- .../Bind/CalculateComponentGuids.cs | 2 +- .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 79 +++++++++++++++++++++- .../TestData/PatchNonSpecific/BundleA/Bundle.wxs | 7 ++ .../TestData/PatchNonSpecific/BundleB/Bundle.wxs | 8 +++ .../TestData/PatchNonSpecific/BundleC/Bundle.wxs | 9 +++ .../TestData/PatchNonSpecific/PackageA/Package.wxs | 46 +++++++++++++ .../TestData/PatchNonSpecific/PatchA/Patch.wxs | 12 ++++ .../TestData/PatchNonSpecific/PatchB/Patch.wxs | 14 ++++ .../TestData/PatchNonSpecific/PatchC/Patch.wxs | 14 ++++ 9 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs index 02336cae..55cda9ea 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs @@ -67,7 +67,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (registryKeyRows.TryGetValue(componentSymbol.KeyPath, out var foundRow)) { var bitness = componentSymbol.Win64 ? "64" : String.Empty; - var regkey = String.Concat(bitness, foundRow.AsString(1), "\\", foundRow.AsString(2), "\\", foundRow.AsString(3)); + var regkey = String.Concat(bitness, foundRow.Root, "\\", foundRow.Key, "\\", foundRow.Name); componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs index 3616bcab..f1d0ea58 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs @@ -2,14 +2,18 @@ namespace WixToolsetTest.CoreIntegration { + using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; + using System.Xml; using System.Xml.Linq; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Burn; using Xunit; public class PatchFixture @@ -28,7 +32,6 @@ namespace WixToolsetTest.CoreIntegration var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); - var baselinePath = Path.ChangeExtension(baselinePdb, ".msp"); var patchPath = Path.ChangeExtension(patchPdb, ".msp"); Assert.True(File.Exists(baselinePdb)); @@ -49,6 +52,57 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildBundleWithNonSpecificPatches() + { + var folder = TestData.Get(@"TestData\PatchNonSpecific"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.0", "A", "B"); + var updatePdb = BuildMsi("Update.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.1", "A", "B"); + var patchAPdb = BuildMsp("PatchA.msp", Path.Combine(folder, "PatchA"), tempFolder, "1.0.1", true); + var patchBPdb = BuildMsp("PatchB.msp", Path.Combine(folder, "PatchB"), tempFolder, "1.0.1", true); + var patchCPdb = BuildMsp("PatchC.msp", Path.Combine(folder, "PatchC"), tempFolder, "1.0.1", true); + var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); + var bundleBPdb = BuildBundle("BundleB.exe", Path.Combine(folder, "BundleB"), tempFolder); + var bundleCPdb = BuildBundle("BundleC.exe", Path.Combine(folder, "BundleC"), tempFolder); + + VerifyPatchTargetCodes(bundleAPdb, new[] + { + "", + }); + VerifyPatchTargetCodes(bundleBPdb, new[] + { + "", + "", + }); + VerifyPatchTargetCodes(bundleCPdb, new string[0]); + } + } + + private static void VerifyPatchTargetCodes(string pdbPath, string[] expected) + { + using (var wixOutput = WixOutput.Read(pdbPath)) + { + var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); + var doc = new XmlDocument(); + doc.LoadXml(manifestData); + var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); + var patchTargetCodes = doc.SelectNodes("/w:BurnManifest/w:PatchTargetCode", nsmgr); + + var actual = new List(); + foreach (XmlNode patchTargetCodeNode in patchTargetCodes) + { + actual.Add(patchTargetCodeNode.GetTestXml()); + } + + WixAssert.CompareLineByLine(expected, actual.ToArray()); + } + } + private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB) { var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); @@ -70,13 +124,14 @@ namespace WixToolsetTest.CoreIntegration return Path.ChangeExtension(outputPath, ".wixpdb"); } - private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV) + private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, bool hasNoFiles = false) { var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); var result = WixRunner.Execute(new[] { "build", + hasNoFiles ? "-sw1079" : " ", Path.Combine(sourceFolder, @"Patch.wxs"), "-d", "V=" + defineV, "-bindpath", Path.Combine(baseFolder, "bin"), @@ -89,6 +144,26 @@ namespace WixToolsetTest.CoreIntegration return Path.ChangeExtension(outputPath, ".wixpdb"); } + private static string BuildBundle(string outputName, string sourceFolder, string baseFolder) + { + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(sourceFolder, @"Bundle.wxs"), + Path.Combine(sourceFolder, "..", "..", "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(sourceFolder, "..", "..", "SimpleBundle", "data"), + "-bindpath", Path.Combine(baseFolder, "bin"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", outputPath + }); + + result.AssertSuccess(); + + return Path.ChangeExtension(outputPath, ".wixpdb"); + } + private static XDocument GetExtractPatchXml(string path) { var buffer = new StringBuilder(65535); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs new file mode 100644 index 00000000..4a8f5630 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs new file mode 100644 index 00000000..7fb3cb56 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs new file mode 100644 index 00000000..201d177b --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs new file mode 100644 index 00000000..2d5fbc6d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs new file mode 100644 index 00000000..1b01774c --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs new file mode 100644 index 00000000..f0630ead --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs new file mode 100644 index 00000000..f9d2a55a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 0878f2e11bafb2f1e6f992fff6c49d859a4e569f Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Sun, 31 Jan 2021 19:23:33 -0500 Subject: Remove Burn Authenticode Fixes https://github.com/wixtoolset/issues/issues/6301 --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 11 +- .../Bind/GenerateManifestDataFromIRCommand.cs | 1 - .../Bundles/CreateBurnManifestCommand.cs | 32 +---- .../Bundles/ProcessMsiPackageCommand.cs | 2 - .../Bundles/ProcessPayloadsCommand.cs | 34 ----- .../Bundles/VerifyPayloadsWithCatalogCommand.cs | 158 --------------------- src/WixToolset.Core.Burn/VerifyInterop.cs | 66 --------- src/WixToolset.Core/Compiler_Bundle.cs | 84 +---------- .../SingleExeBundle/SingleExeRemotePayload.wxs | 2 - 9 files changed, 5 insertions(+), 385 deletions(-) delete mode 100644 src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs delete mode 100644 src/WixToolset.Core.Burn/VerifyInterop.cs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 2c8231f8..dea1f47d 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -340,15 +340,6 @@ namespace WixToolset.Core.Burn command.Execute(); } - // If catalog files exist, non-embedded payloads should validate with the catalogs. - var catalogs = section.Symbols.OfType().ToList(); - - if (catalogs.Count > 0) - { - var command = new VerifyPayloadsWithCatalogCommand(this.Messaging, catalogs, payloadSymbols.Values); - command.Execute(); - } - if (this.Messaging.EncounteredError) { return; @@ -456,7 +447,7 @@ namespace WixToolset.Core.Burn { var executableName = Path.GetFileName(this.OutputPath); - var command = new CreateBurnManifestCommand(this.Messaging, this.BackendExtensions, executableName, section, bundleSymbol, containers, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, orderedSearches, catalogs, this.IntermediateFolder); + var command = new CreateBurnManifestCommand(this.Messaging, this.BackendExtensions, executableName, section, bundleSymbol, containers, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, orderedSearches, this.IntermediateFolder); command.Execute(); manifestPath = command.OutputPath; diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs index 24a4ae67..93a1a0bc 100644 --- a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs @@ -63,7 +63,6 @@ namespace WixToolset.Core.Burn.Bind case SymbolDefinitionType.WixBootstrapperApplication: case SymbolDefinitionType.WixBootstrapperApplicationDll: case SymbolDefinitionType.WixBundle: - case SymbolDefinitionType.WixBundleCatalog: case SymbolDefinitionType.WixBundleContainer: case SymbolDefinitionType.WixBundleCustomDataAttribute: case SymbolDefinitionType.WixBundleExePackage: diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 6eafcdd9..d12f00d1 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -18,7 +18,7 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateBurnManifestCommand { - public CreateBurnManifestCommand(IMessaging messaging, IEnumerable backendExtensions, string executableName, IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable containers, WixChainSymbol chainSymbol, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, IEnumerable orderedSearches, IEnumerable catalogs, string intermediateFolder) + public CreateBurnManifestCommand(IMessaging messaging, IEnumerable backendExtensions, string executableName, IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable containers, WixChainSymbol chainSymbol, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, IEnumerable orderedSearches, string intermediateFolder) { this.Messaging = messaging; this.BackendExtensions = backendExtensions; @@ -32,7 +32,6 @@ namespace WixToolset.Core.Burn.Bundles this.UXContainerPayloads = uxPayloads; this.Payloads = allPayloadsById; this.OrderedSearches = orderedSearches; - this.Catalogs = catalogs; this.IntermediateFolder = intermediateFolder; } @@ -62,8 +61,6 @@ namespace WixToolset.Core.Burn.Bundles private IEnumerable UXContainerPayloads { get; } - private IEnumerable Catalogs { get; } - private string IntermediateFolder { get; } public void Execute() @@ -179,18 +176,6 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); // - // write the catalog elements - if (this.Catalogs.Any()) - { - foreach (var catalog in this.Catalogs) - { - writer.WriteStartElement("Catalog"); - writer.WriteAttributeString("Id", catalog.Id.Id); - writer.WriteAttributeString("Payload", catalog.PayloadRef); - writer.WriteEndElement(); - } - } - foreach (var container in this.Containers) { if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) @@ -698,16 +683,6 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteAttributeString("LayoutOnly", "yes"); } - if (!String.IsNullOrEmpty(payload.PublicKey)) - { - writer.WriteAttributeString("CertificateRootPublicKeyIdentifier", payload.PublicKey); - } - - if (!String.IsNullOrEmpty(payload.Thumbprint)) - { - writer.WriteAttributeString("CertificateRootThumbprint", payload.Thumbprint); - } - switch (payload.Packaging) { case PackagingType.Embedded: // this means it's in a container. @@ -742,11 +717,6 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteAttributeString("SourcePath", payload.Name); break; } - - if (!String.IsNullOrEmpty(payload.CatalogRef)) - { - writer.WriteAttributeString("Catalog", payload.CatalogRef); - } } private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName) diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs index 7adbfcfd..e13561bc 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs @@ -406,7 +406,6 @@ namespace WixToolset.Core.Burn.Bundles PackageRef = packagePayload.PackageRef, ContainerRef = packagePayload.ContainerRef, ContentFile = true, - EnableSignatureValidation = packagePayload.EnableSignatureValidation, Packaging = packagePayload.Packaging, ParentPackagePayloadRef = packagePayload.Id.Id, }); @@ -484,7 +483,6 @@ namespace WixToolset.Core.Burn.Bundles PackageRef = packagePayload.PackageRef, ContainerRef = packagePayload.ContainerRef, ContentFile = true, - EnableSignatureValidation = packagePayload.EnableSignatureValidation, Packaging = packagePayload.Packaging, ParentPackagePayloadRef = packagePayload.Id.Id, }); diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs index 69c4d7c2..8811c301 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs @@ -6,9 +6,6 @@ namespace WixToolset.Core.Burn.Bundles using System.Collections.Generic; using System.Diagnostics; using System.IO; - using System.Security.Cryptography; - using System.Security.Cryptography.X509Certificates; - using System.Text; using WixToolset.Data; using WixToolset.Data.Burn; using WixToolset.Data.Symbols; @@ -123,37 +120,6 @@ namespace WixToolset.Core.Burn.Bundles payload.FileSize = (int)fileInfo.Length; payload.Hash = BundleHashAlgorithm.Hash(fileInfo); - - // Try to get the certificate if the payload is a signed file and we're not suppressing signature validation. - if (payload.EnableSignatureValidation) - { - X509Certificate2 certificate = null; - try - { - certificate = new X509Certificate2(fileInfo.FullName); - } - catch (CryptographicException) // we don't care about non-signed files. - { - } - - // If there is a certificate, remember its hashed public key identifier and thumbprint. - if (null != certificate) - { - byte[] publicKeyIdentifierHash = new byte[128]; - uint publicKeyIdentifierHashSize = (uint)publicKeyIdentifierHash.Length; - - Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize); - - var sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2); - for (var i = 0; i < publicKeyIdentifierHashSize; ++i) - { - sb.AppendFormat("{0:X2}", publicKeyIdentifierHash[i]); - } - - payload.PublicKey = sb.ToString(); - payload.Thumbprint = certificate.Thumbprint; - } - } } else { diff --git a/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs b/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs deleted file mode 100644 index e7c97ea7..00000000 --- a/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs +++ /dev/null @@ -1,158 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class VerifyPayloadsWithCatalogCommand - { - public VerifyPayloadsWithCatalogCommand(IMessaging messaging, IEnumerable catalogs, IEnumerable payloads) - { - this.Messaging = messaging; - this.Catalogs = catalogs; - this.Payloads = payloads; - } - - private IMessaging Messaging { get; } - - private IEnumerable Catalogs { get; } - - private IEnumerable Payloads { get; } - - public void Execute() - { - var catalogIdsWithPaths = this.Catalogs - .Join(this.Payloads, - catalog => catalog.PayloadRef, - payload => payload.Id.Id, - (catalog, payload) => new CatalogIdWithPath() { Id = catalog.Id.Id, FullPath = Path.GetFullPath(payload.SourceFile.Path) }) - .ToList(); - - foreach (var payloadInfo in this.Payloads) - { - // Payloads that are not embedded should be verfied. - if (String.IsNullOrEmpty(payloadInfo.EmbeddedId)) - { - var sourceFile = payloadInfo.SourceFile.Path; - var validated = false; - - foreach (var catalog in catalogIdsWithPaths) - { - if (!validated) - { - // Get the file hash - uint cryptHashSize = 20; - byte[] cryptHashBytes = new byte[cryptHashSize]; - int error; - using (var payloadStream = File.OpenRead(sourceFile)) - { - // Get the file handle - var fileHandle = payloadStream.SafeFileHandle.DangerousGetHandle(); - - // 20 bytes is usually the hash size. Future hashes may be bigger - if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) - { - error = Marshal.GetLastWin32Error(); - - if (VerifyInterop.ErrorInsufficientBuffer == error) - { - error = 0; - cryptHashBytes = new byte[cryptHashSize]; - if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) - { - error = Marshal.GetLastWin32Error(); - } - } - - if (0 != error) - { - this.Messaging.Write(ErrorMessages.CatalogFileHashFailed(sourceFile, error)); - } - } - } - - VerifyInterop.WinTrustCatalogInfo catalogData = new VerifyInterop.WinTrustCatalogInfo(); - VerifyInterop.WinTrustData trustData = new VerifyInterop.WinTrustData(); - try - { - // Create WINTRUST_CATALOG_INFO structure - catalogData.cbStruct = (uint)Marshal.SizeOf(catalogData); - catalogData.cbCalculatedFileHash = cryptHashSize; - catalogData.pbCalculatedFileHash = Marshal.AllocCoTaskMem((int)cryptHashSize); - Marshal.Copy(cryptHashBytes, 0, catalogData.pbCalculatedFileHash, (int)cryptHashSize); - - var hashString = new StringBuilder(); - foreach (var hashByte in cryptHashBytes) - { - hashString.Append(hashByte.ToString("X2")); - } - catalogData.pcwszMemberTag = hashString.ToString(); - - // The file names need to be lower case for older OSes - catalogData.pcwszMemberFilePath = sourceFile.ToLowerInvariant(); - catalogData.pcwszCatalogFilePath = catalog.FullPath.ToLowerInvariant(); - - // Create WINTRUST_DATA structure - trustData.cbStruct = (uint)Marshal.SizeOf(trustData); - trustData.dwUIChoice = VerifyInterop.WTD_UI_NONE; - trustData.fdwRevocationChecks = VerifyInterop.WTD_REVOKE_NONE; - trustData.dwUnionChoice = VerifyInterop.WTD_CHOICE_CATALOG; - trustData.dwStateAction = VerifyInterop.WTD_STATEACTION_VERIFY; - trustData.dwProvFlags = VerifyInterop.WTD_REVOCATION_CHECK_NONE; - - // Create the structure pointers for unmanaged - trustData.pCatalog = Marshal.AllocCoTaskMem(Marshal.SizeOf(catalogData)); - Marshal.StructureToPtr(catalogData, trustData.pCatalog, false); - - // Call WinTrustVerify to validate the file with the catalog - IntPtr noWindow = new IntPtr(-1); - Guid verifyGuid = new Guid(VerifyInterop.GenericVerify2); - long verifyResult = VerifyInterop.WinVerifyTrust(noWindow, ref verifyGuid, ref trustData); - if (0 == verifyResult) - { - payloadInfo.CatalogRef = catalog.Id; - validated = true; - break; - } - } - finally - { - // Free the structure memory - if (IntPtr.Zero != trustData.pCatalog) - { - Marshal.FreeCoTaskMem(trustData.pCatalog); - } - - if (IntPtr.Zero != catalogData.pbCalculatedFileHash) - { - Marshal.FreeCoTaskMem(catalogData.pbCalculatedFileHash); - } - } - } - } - - // Error message if the file was not validated by one of the catalogs - if (!validated) - { - this.Messaging.Write(ErrorMessages.CatalogVerificationFailed(sourceFile)); - } - } - } - } - - private class CatalogIdWithPath - { - public string Id { get; set; } - - public string FullPath { get; set; } - } - } -} diff --git a/src/WixToolset.Core.Burn/VerifyInterop.cs b/src/WixToolset.Core.Burn/VerifyInterop.cs deleted file mode 100644 index f021f1d0..00000000 --- a/src/WixToolset.Core.Burn/VerifyInterop.cs +++ /dev/null @@ -1,66 +0,0 @@ -// 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 -{ - using System; - using System.Runtime.InteropServices; - - internal class VerifyInterop - { - internal const string GenericVerify2 = "00AAC56B-CD44-11d0-8CC2-00C04FC295EE"; - internal const uint WTD_UI_NONE = 2; - internal const uint WTD_REVOKE_NONE = 0; - internal const uint WTD_CHOICE_CATALOG = 2; - internal const uint WTD_STATEACTION_VERIFY = 1; - internal const uint WTD_REVOCATION_CHECK_NONE = 0x10; - internal const int ErrorInsufficientBuffer = 122; - - [StructLayout(LayoutKind.Sequential)] - internal struct WinTrustData - { - internal uint cbStruct; - internal IntPtr pPolicyCallbackData; - internal IntPtr pSIPClientData; - internal uint dwUIChoice; - internal uint fdwRevocationChecks; - internal uint dwUnionChoice; - internal IntPtr pCatalog; - internal uint dwStateAction; - internal IntPtr hWVTStateData; - [MarshalAs(UnmanagedType.LPWStr)] - internal string pwszURLReference; - internal uint dwProvFlags; - internal uint dwUIContext; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct WinTrustCatalogInfo - { - internal uint cbStruct; - internal uint dwCatalogVersion; - [MarshalAs(UnmanagedType.LPWStr)] - internal string pcwszCatalogFilePath; - [MarshalAs(UnmanagedType.LPWStr)] - internal string pcwszMemberTag; - [MarshalAs(UnmanagedType.LPWStr)] - internal string pcwszMemberFilePath; - internal IntPtr hMemberFile; - internal IntPtr pbCalculatedFileHash; - internal uint cbCalculatedFileHash; - internal IntPtr pcCatalogContext; - } - - [DllImport("wintrust.dll", SetLastError = true)] - internal static extern long WinVerifyTrust(IntPtr windowHandle, ref Guid actionGuid, ref WinTrustData trustData); - - [DllImport("wintrust.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CryptCATAdminCalcHashFromFileHandle( - IntPtr fileHandle, - [In, Out] - ref uint hashSize, - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] - byte[] hashBytes, - uint flags); - } -} diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index f0060a3e..944f089e 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -292,9 +292,6 @@ namespace WixToolset.Core case "OptionalUpdateRegistration": this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name); break; - case "Catalog": - this.ParseCatalogElement(child); - break; case "Chain": if (chainSeen) { @@ -484,59 +481,6 @@ namespace WixToolset.Core return YesNoType.Yes == disableLog ? null : String.Join(":", variable, logPrefix, logExtension); } - /// - /// Parse a Catalog element. - /// - /// Element to parse - private void ParseCatalogElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - this.Core.ParseForExtensionElements(node); - - // Create catalog row - if (!this.Core.EncounteredError) - { - this.CreatePayloadRow(sourceLineNumbers, id, Path.GetFileName(sourceFile), sourceFile, null, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, ComplexReferenceChildType.Unknown, null, YesNoDefaultType.Yes, YesNoType.Yes, null, null, null); - - this.Core.AddSymbol(new WixBundleCatalogSymbol(sourceLineNumbers, id) - { - PayloadRef = id.Id, - }); - } - } - /// /// Parse a Container element. /// @@ -1369,7 +1313,6 @@ namespace WixToolset.Core var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var compressed = YesNoDefaultType.Default; - var enableSignatureVerification = YesNoType.No; id = null; string name = null; string sourceFile = null; @@ -1400,9 +1343,6 @@ namespace WixToolset.Core case "DownloadUrl": downloadUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; - case "EnableSignatureVerification": - enableSignatureVerification = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; case "DpiAwareness": if (node.Name.LocalName != "BootstrapperApplicationDll") { @@ -1457,7 +1397,7 @@ namespace WixToolset.Core return false; } - this.CreatePayloadRow(sourceLineNumbers, id, name, sourceFile, downloadUrl, parentType, parentId, previousType, previousId, compressed, enableSignatureVerification, null, null, null); + this.CreatePayloadRow(sourceLineNumbers, id, name, sourceFile, downloadUrl, parentType, parentId, previousType, previousId, compressed, null, null, null); return true; } @@ -1473,12 +1413,6 @@ namespace WixToolset.Core { switch (attrib.Name.LocalName) { - case "CertificatePublicKey": - remotePayload.CertificatePublicKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "CertificateThumbprint": - remotePayload.CertificateThumbprint = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; case "Description": remotePayload.Description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; @@ -1546,13 +1480,12 @@ namespace WixToolset.Core /// /// /// - /// /// /// /// /// private WixBundlePayloadSymbol CreatePayloadRow(SourceLineNumber sourceLineNumbers, Identifier id, string name, string sourceFile, string downloadUrl, ComplexReferenceParentType parentType, - Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId, YesNoDefaultType compressed, YesNoType enableSignatureVerification, string displayName, string description, + Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId, YesNoDefaultType compressed, string displayName, string description, RemotePayload remotePayload) { WixBundlePayloadSymbol symbol = null; @@ -1568,7 +1501,6 @@ namespace WixToolset.Core UnresolvedSourceFile = sourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. DisplayName = displayName, Description = description, - EnableSignatureValidation = (YesNoType.Yes == enableSignatureVerification) }); if (null != remotePayload) @@ -1576,8 +1508,6 @@ namespace WixToolset.Core symbol.Description = remotePayload.Description; symbol.DisplayName = remotePayload.ProductName; symbol.Hash = remotePayload.Hash; - symbol.PublicKey = remotePayload.CertificatePublicKey; - symbol.Thumbprint = remotePayload.CertificateThumbprint; symbol.FileSize = remotePayload.Size; symbol.Version = remotePayload.Version; } @@ -2120,7 +2050,6 @@ namespace WixToolset.Core string protocol = null; var installSize = CompilerConstants.IntegerNotSet; string msuKB = null; - var enableSignatureVerification = YesNoType.No; var compressed = YesNoDefaultType.Default; var enableFeatureSelection = YesNoType.NotSet; var forcePerMachine = YesNoType.NotSet; @@ -2249,9 +2178,6 @@ namespace WixToolset.Core case "Compressed": compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); break; - case "EnableSignatureVerification": - enableSignatureVerification = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; case "Slipstream": slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); allowed = (packageType == WixBundlePackageType.Msp); @@ -2480,7 +2406,7 @@ namespace WixToolset.Core { // We create the package contents as a payload with this package as the parent this.CreatePayloadRow(sourceLineNumbers, id, name, sourceFile, downloadUrl, ComplexReferenceParentType.Package, id, - ComplexReferenceChildType.Unknown, null, compressed, enableSignatureVerification, displayName, description, remotePayload); + ComplexReferenceChildType.Unknown, null, compressed, displayName, description, remotePayload); this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); @@ -3301,10 +3227,6 @@ namespace WixToolset.Core private class RemotePayload { - public string CertificatePublicKey { get; set; } - - public string CertificateThumbprint { get; set; } - public string Description { get; set; } public string Hash { get; set; } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs index 6c6903b1..fcb9dd8d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs @@ -18,8 +18,6 @@ Compressed="no" Name="NDP462-KB3151802-Web.exe"> Date: Sun, 7 Feb 2021 19:06:11 -0500 Subject: Improve duplicate-id reporting. --- .../Link/FindEntrySectionAndLoadSymbolsCommand.cs | 1 + .../RegistryFixture.cs | 26 ++++++++++++++++++++++ .../Registry/DuplicateRegistryValueIds.wxs | 14 ++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs index a4b2bee3..a52d0d63 100644 --- a/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs +++ b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs @@ -103,6 +103,7 @@ namespace WixToolset.Core.Link } else { + symbolWithSection.AddPossibleConflict(existingSymbol); existingSymbol.AddPossibleConflict(symbolWithSection); possibleConflicts.Add(symbolWithSection); } diff --git a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs index 654b8740..e4d95b5d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs @@ -79,6 +79,32 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void DuplicateRegistryValueIdsAreDetectedSmoothly() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "DuplicateRegistryValueIds.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }, out var messages); + + Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol).Count()); + Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol2).Count()); + } + } + [Fact] public void PopulatesRegistryTableFromRemoveRegistryKey() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs new file mode 100644 index 00000000..452aea69 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 12db1999ea040ea0b1a51b1dfb5c5f92fc8087c4 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 10 Feb 2021 16:12:33 -0600 Subject: SlipstreamMsp belongs under the MsiPackage, not the MspPackage. --- .../Bundles/CreateBurnManifestCommand.cs | 2 +- .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 27 ++++++++++++++++++++++ .../TestData/PatchSingle/BundleA/Bundle.wxs | 10 ++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index d12f00d1..994e02f8 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -332,7 +332,7 @@ namespace WixToolset.Core.Burn.Bundles var msiPropertiesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); var payloadsByPackage = this.Payloads.Values.ToLookup(p => p.PackageRef); var relatedPackagesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); - var slipstreamMspsByPackage = this.Section.Symbols.OfType().ToLookup(r => r.MspPackageRef); + var slipstreamMspsByPackage = this.Section.Symbols.OfType().ToLookup(r => r.TargetPackageRef); var exitCodesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.ChainPackageId); var commandLinesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.WixBundlePackageRef); diff --git a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs index f1d0ea58..dda4ca28 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs @@ -83,6 +83,33 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildBundleWithSlipstreamPatch() + { + var folder = TestData.Get(@"TestData\PatchSingle"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); + var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); + + using (var wixOutput = WixOutput.Read(bundleAPdb)) + { + var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); + var doc = new XmlDocument(); + doc.LoadXml(manifestData); + var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); + var slipstreamMspNodes = doc.SelectNodes("/w:BurnManifest/w:Chain/w:MsiPackage/w:SlipstreamMsp", nsmgr); + Assert.Equal(1, slipstreamMspNodes.Count); + Assert.Equal("", slipstreamMspNodes[0].GetTestXml()); + } + } + } + private static void VerifyPatchTargetCodes(string pdbPath, string[] expected) { using (var wixOutput = WixOutput.Read(pdbPath)) diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs new file mode 100644 index 00000000..bc460636 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + -- cgit v1.2.3-55-g6feb From c6e2213e818b869c44c1af7355fc06f45ebf1a1f Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 11 Feb 2021 13:46:45 -0800 Subject: Replace Win64 attribute with Bitness attribute Closes wixtoolset/#4707 --- .../Decompile/Decompiler.cs | 12 +-- src/WixToolset.Core/CompileContext.cs | 2 + src/WixToolset.Core/Compiler.cs | 105 ++++++++++++--------- src/WixToolset.Core/Compiler_Bundle.cs | 25 ++++- .../ApprovedExeFixture.cs | 64 +++++++++++++ .../MsiQueryFixture.cs | 34 +++++++ .../DecompiledNestedDirSearchUnderRegSearch.wxs | 12 +-- .../TestData/AppSearch/RegistrySearch64.wxs | 12 +++ .../TestData/BundleWithApprovedExe/Bundle.wxs | 5 + .../TestData/BundleWithApprovedExe/Bundle64.wxs | 5 + .../TestData/Class/DecompiledOldClassTableDef.wxs | 4 +- .../TestData/CustomTable/CustomTable-Expected.wxs | 4 +- .../TestData/DecompileNullComponent/Expected.wxs | 4 +- .../DecompileSingleFileCompressed/Expected.wxs | 4 +- .../DecompileSingleFileCompressed64/Expected.wxs | 4 +- .../SequenceTables/DecompiledSequenceTables.wxs | 4 +- .../TestData/Shortcut/DecompiledShortcuts.wxs | 4 +- 17 files changed, 228 insertions(+), 76 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index 29e15c91..c62e8153 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -3950,12 +3950,12 @@ namespace WixToolset.Core.WindowsInstaller if (WindowsInstallerConstants.MsidbCustomActionType64BitScript == (type & WindowsInstallerConstants.MsidbCustomActionType64BitScript)) { - xCustomAction.SetAttributeValue("Win64", "yes"); + xCustomAction.SetAttributeValue("Bitness", "always64"); } else if (WindowsInstallerConstants.MsidbCustomActionTypeVBScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) || WindowsInstallerConstants.MsidbCustomActionTypeJScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeJScript)) { - xCustomAction.SetAttributeValue("Win64", "no"); + xCustomAction.SetAttributeValue("Bitness", "always32"); } switch (type & WindowsInstallerConstants.MsidbCustomActionTypeExecuteBits) @@ -4191,11 +4191,11 @@ namespace WixToolset.Core.WindowsInstaller if (WindowsInstallerConstants.MsidbComponentAttributes64bit == (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit)) { - xComponent.SetAttributeValue("Win64", "yes"); + xComponent.SetAttributeValue("Bitness", "always64"); } else { - xComponent.SetAttributeValue("Win64", "no"); + xComponent.SetAttributeValue("Bitness", "always32"); } if (WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection == (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection)) @@ -6479,12 +6479,12 @@ namespace WixToolset.Core.WindowsInstaller if (WindowsInstallerConstants.MsidbLocatorType64bit == (type & WindowsInstallerConstants.MsidbLocatorType64bit)) { - xRegistrySearch.SetAttributeValue("Win64", "yes"); + xRegistrySearch.SetAttributeValue("Bitness", "always64"); type &= ~WindowsInstallerConstants.MsidbLocatorType64bit; } else { - xRegistrySearch.SetAttributeValue("Win64", "no"); + xRegistrySearch.SetAttributeValue("Bitness", "always32"); } switch (type) diff --git a/src/WixToolset.Core/CompileContext.cs b/src/WixToolset.Core/CompileContext.cs index 44da6c8a..e781b692 100644 --- a/src/WixToolset.Core/CompileContext.cs +++ b/src/WixToolset.Core/CompileContext.cs @@ -25,6 +25,8 @@ namespace WixToolset.Core public Platform Platform { get; set; } + public bool IsCurrentPlatform64Bit => this.Platform == Platform.ARM64 || this.Platform == Platform.X64; + public XDocument Source { get; set; } public CancellationToken CancellationToken { get; set; } diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 5267a232..85261cce 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -59,13 +59,9 @@ namespace WixToolset.Core internal Compiler(IWixToolsetServiceProvider serviceProvider) { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); } - private IWixToolsetServiceProvider ServiceProvider { get; } - public IMessaging Messaging { get; } private ICompileContext Context { get; set; } @@ -78,12 +74,6 @@ namespace WixToolset.Core /// The platform which the compiler will use when defaulting 64-bit attributes and elements. public Platform CurrentPlatform => this.Context.Platform; - /// - /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements. - /// - /// The platform which the compiler will use when defaulting 64-bit attributes and elements. - public bool IsCurrentPlatform64Bit => this.Context.Platform == Platform.ARM64 || this.Context.Platform == Platform.X64; - /// /// Gets or sets the option to show pedantic messages. /// @@ -1724,14 +1714,12 @@ namespace WixToolset.Core private string ParseRegistrySearchElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var explicitWin64 = false; Identifier id = null; string key = null; string name = null; - string signature = null; RegistryRootType? root = null; RegLocatorType? type = null; - var search64bit = false; + var search64bit = this.Context.IsCurrentPlatform64Bit; foreach (var attrib in node.Attributes()) { @@ -1742,6 +1730,24 @@ namespace WixToolset.Core case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + search64bit = false; + break; + case "always64": + search64bit = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; case "Key": key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; @@ -1771,10 +1777,6 @@ namespace WixToolset.Core break; } break; - case "Win64": - explicitWin64 = true; - search64bit = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; default: this.Core.UnexpectedAttribute(node, attrib); break; @@ -1786,11 +1788,6 @@ namespace WixToolset.Core } } - if (!explicitWin64 && this.IsCurrentPlatform64Bit) - { - search64bit = true; - } - if (null == id) { id = this.Core.CreateIdentifier("reg", root.ToString(), key, name, type.ToString(), search64bit.ToString()); @@ -1811,7 +1808,7 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); } - signature = id.Id; + var signature = id.Id; var oneChild = false; foreach (var child in node.Elements()) { @@ -2125,8 +2122,7 @@ namespace WixToolset.Core var sharedDllRefCount = false; var transitive = false; var uninstallWhenSuperseded = false; - var explicitWin64 = false; - var win64 = false; + var win64 = this.Context.IsCurrentPlatform64Bit; var multiInstance = false; var symbols = new List(); @@ -2141,6 +2137,24 @@ namespace WixToolset.Core case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; case "ComPlusFlags": comPlusBits = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); break; @@ -2240,15 +2254,6 @@ namespace WixToolset.Core // bits |= MsiInterop.MsidbComponentAttributesUninstallOnSupersedence; //} break; - case "Win64": - explicitWin64 = true; - win64 = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - //{ - // bits |= MsiInterop.MsidbComponentAttributes64bit; - // win64 = true; - //} - break; default: this.Core.UnexpectedAttribute(node, attrib); break; @@ -2260,12 +2265,6 @@ namespace WixToolset.Core } } - if (!explicitWin64 && this.IsCurrentPlatform64Bit) - { - //bits |= MsiInterop.MsidbComponentAttributes64bit; - win64 = true; - } - if (id == null) { // Placeholder id for defaulting Component/@Id to keypath id. @@ -3157,6 +3156,26 @@ namespace WixToolset.Core sourceType = CustomActionSourceType.Binary; this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + explicitWin64 = true; + win64 = false; + break; + case "always64": + explicitWin64 = true; + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; case "Directory": if (null != source) { @@ -3345,10 +3364,6 @@ namespace WixToolset.Core target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid targetType = CustomActionTargetType.VBScript; break; - case "Win64": - explicitWin64 = true; - win64 = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; default: this.Core.UnexpectedAttribute(node, attrib); break; @@ -3366,7 +3381,7 @@ namespace WixToolset.Core id = Identifier.Invalid; } - if (!explicitWin64 && this.IsCurrentPlatform64Bit && (CustomActionTargetType.VBScript == targetType || CustomActionTargetType.JScript == targetType)) + if (!explicitWin64 && this.Context.IsCurrentPlatform64Bit && (CustomActionTargetType.VBScript == targetType || CustomActionTargetType.JScript == targetType)) { win64 = true; } diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 944f089e..7a386de7 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -33,7 +33,7 @@ namespace WixToolset.Core Identifier id = null; string key = null; string valueName = null; - var win64 = YesNoType.NotSet; + var win64 = this.Context.IsCurrentPlatform64Bit; foreach (var attrib in node.Attributes()) { @@ -44,15 +44,30 @@ namespace WixToolset.Core case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; case "Key": key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Value": valueName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; - case "Win64": - win64 = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; default: this.Core.UnexpectedAttribute(node, attrib); break; @@ -76,7 +91,7 @@ namespace WixToolset.Core var attributes = WixApprovedExeForElevationAttributes.None; - if (win64 == YesNoType.Yes) + if (win64) { attributes |= WixApprovedExeForElevationAttributes.Win64; } diff --git a/src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs new file mode 100644 index 00000000..47b47ef5 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs @@ -0,0 +1,64 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ApprovedExeFixture + { + [Fact] + public void CanBuildWithApprovedExe() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleWithApprovedExe", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.NotEqual(0, result.ExitCode); + Assert.False(File.Exists(exePath)); + } + } + + [Fact] + public void CanBuildWithApprovedExe64() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleWithApprovedExe", "Bundle64.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.NotEqual(0, result.ExitCode); + Assert.False(File.Exists(exePath)); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 00738965..8c3487f0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -185,6 +185,40 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void PopulatesAppSearchTablesFromRegistrySearch64() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "RegistrySearch64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", + "RegLocator:SampleRegSearch\t2\tSampleReg\t\t18", + }, results); + } + } + [Fact] public void PopulatesClassTablesWhenIconIndexIsZero() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs index 8d1e5de2..b67abbde 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs @@ -1,9 +1,9 @@ - + - + @@ -15,10 +15,10 @@ - + - + @@ -29,12 +29,12 @@ - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs new file mode 100644 index 00000000..8be5abb2 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs new file mode 100644 index 00000000..78e754c1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs new file mode 100644 index 00000000..18cdfd32 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs index 08ced0c2..43af2ffe 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs @@ -1,9 +1,9 @@ - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs index f87c9387..935cc8b3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs @@ -1,4 +1,4 @@ - + @@ -16,7 +16,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs index 89592150..050adbd5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs @@ -1,9 +1,9 @@ - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs index e2557fe1..e1fb426e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs @@ -1,9 +1,9 @@ - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs index 38ce54b8..fed69a1e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs @@ -1,9 +1,9 @@ - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs index 08af1950..6924f37a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs @@ -1,10 +1,10 @@ - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs index 1b602588..13412b50 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs @@ -1,9 +1,9 @@ - + - + -- cgit v1.2.3-55-g6feb From 0ce3a9b66da80c505fd8901df88d31b0ad43dac6 Mon Sep 17 00:00:00 2001 From: Alex Kubiesa Date: Mon, 15 Feb 2021 20:08:21 +0000 Subject: Fix file size overflow error in ProcessPayloadsCommand. Fixes #4008 --- .../Bundles/ProcessPayloadsCommand.cs | 2 +- .../BundleFixture.cs | 52 +++++++++++++++++++++ .../TestData/LargePayload/Bundle.en-us.wxl | 10 ++++ .../TestData/LargePayload/Bundle.wxs | 13 ++++++ .../LargePayload/data/MsiPackage/Shared.dll | 1 + .../TestData/LargePayload/data/MsiPackage/test.txt | 1 + .../TestData/LargePayload/data/fakeba.dll | 1 + .../TestData/LargePayload/data/large_file.dat | 2 + .../TestData/LargePayload/data/test.msi | Bin 0 -> 32768 bytes 9 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/Shared.dll create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/test.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/fakeba.dll create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/large_file.dat create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/test.msi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs index 8811c301..db5b03fb 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs @@ -117,7 +117,7 @@ namespace WixToolset.Core.Burn.Bundles if (null != fileInfo) { - payload.FileSize = (int)fileInfo.Length; + payload.FileSize = fileInfo.Length; payload.Hash = BundleHashAlgorithm.Hash(fileInfo); } diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index 094e4df2..0660dd7b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -366,5 +366,57 @@ namespace WixToolsetTest.CoreIntegration Assert.InRange(result.ExitCode, 2, Int32.MaxValue); } } + + [Fact] + public void CanBuildBundleWithLargePayload() + { + var folder = TestData.Get(@"TestData\LargePayload"); + + // Overwrite the payload with a 2.5 GiB file. We do this dynamically to avoid committing such + // a large file to source control. + var largeFile = Path.Combine(folder, "data", "large_file.dat"); + const long TwoAndAHalfGigabytes = 2_684_354_560; + using (var stream = File.Create(largeFile)) + { + stream.Seek(TwoAndAHalfGigabytes - 1, SeekOrigin.Begin); + stream.WriteByte(1); + } + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Bundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + Assert.Empty(result.Messages.Where(m => m.Level == MessageLevel.Warning)); + + Assert.True(File.Exists(exePath)); + Assert.True(File.Exists(pdbPath)); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\large_file.dat"))); + + using (var wixOutput = WixOutput.Read(pdbPath)) + { + var intermediate = Intermediate.Load(wixOutput); + var section = intermediate.Sections.Single(); + + var payloadSymbol = section.Symbols.OfType().Where(x => x.Name == "large_file.dat").Single(); + Assert.Equal(TwoAndAHalfGigabytes, payloadSymbol.FileSize); + } + } + + File.Delete(largeFile); + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.en-us.wxl new file mode 100644 index 00000000..bc1dee83 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.en-us.wxl @@ -0,0 +1,10 @@ + + + + + + ~TestBundle + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.wxs new file mode 100644 index 00000000..5c7735b5 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/Shared.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/Shared.dll new file mode 100644 index 00000000..0e461ba8 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/Shared.dll @@ -0,0 +1 @@ +This is Shared.dll. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/test.txt new file mode 100644 index 00000000..8b986220 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/test.txt @@ -0,0 +1 @@ +This is test.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/fakeba.dll new file mode 100644 index 00000000..970efdf0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/fakeba.dll @@ -0,0 +1 @@ +This is a fakeba.dll \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/large_file.dat b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/large_file.dat new file mode 100644 index 00000000..8115cc60 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/large_file.dat @@ -0,0 +1,2 @@ +When running the tests, this file will be overwritten with 2.5GB of data to test how Wix handles large files. We've avoided +committing such a large file to Git as it would bloat the repo. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/test.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/test.msi new file mode 100644 index 00000000..0722d60e Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/test.msi differ -- cgit v1.2.3-55-g6feb From 9dd02e0c1bb6463de5d79f14ce06d078ef876a32 Mon Sep 17 00:00:00 2001 From: Ronald Martin Date: Fri, 12 Feb 2021 22:37:15 -0500 Subject: Checkks all custom actions for cycles. Version 3. Fixes wixtoolset/issues#6201 --- .../Bind/SequenceActionsCommand.cs | 78 +++++++++++++++++----- .../CustomActionFixture.cs | 37 ++++++++-- .../CustomAction/CustomActionCycleWithTail.wxs | 20 ++++++ 3 files changed, 114 insertions(+), 21 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs index d33836c3..056b129b 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs @@ -67,7 +67,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else // not a supported unscheduled action. { - throw new InvalidOperationException("Found an action with no Sequence, Before, or After column set."); + throw new InvalidOperationException($"Found an action [{actionSymbol.Id.Id}] at [{actionSymbol.SourceLineNumbers}] with no Sequence, Before, or After column set."); } } @@ -129,6 +129,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } + // A dictionary used for detecting cyclic references among action symbols. + var firstReference = new Dictionary(); + // Build up dependency trees of the relatively scheduled actions. // Use ToList() to create a copy of the required action symbols so that new symbols can // be added while enumerating. @@ -142,7 +145,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.Messaging.Write(ErrorMessages.StandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); } - this.SequenceActionSymbol(actionSymbol, requiredActionSymbols); + this.SequenceActionSymbol(actionSymbol, requiredActionSymbols, firstReference); } else if (SectionType.Module == this.Section.Type && 0 < actionSymbol.Sequence && !WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) // check for custom actions and dialogs that have a sequence number { @@ -566,7 +569,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// /// The action symbol to be sequenced. /// Collection of actions which must be included. - private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols) + private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) { var after = false; @@ -576,7 +579,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else if (actionSymbol.Before == null) { - throw new InvalidOperationException("Found an action with no Sequence, Before, or After column set."); + throw new InvalidOperationException($"Found an action [{actionSymbol.Id.Id}] at [{actionSymbol.SourceLineNumbers}] with no Sequence, Before, or After column set."); } var parentActionName = (after ? actionSymbol.After : actionSymbol.Before); @@ -597,10 +600,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Found an action with a non-existent {0} action: {1}.", (after ? "After" : "Before"), parentActionName)); } } - else if (actionSymbol == parentActionSymbol || this.ContainsChildActionSymbol(actionSymbol, parentActionSymbol)) // cycle detected - { - throw new WixException(ErrorMessages.ActionCircularDependency(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, parentActionSymbol.Action)); - } + + this.CheckForCircularActionReference(actionSymbol, requiredActionSymbols, firstReference); // Add this action to the appropriate list of dependent action symbols. var relativeActions = this.GetRelativeActions(parentActionSymbol); @@ -608,19 +609,66 @@ namespace WixToolset.Core.WindowsInstaller.Bind relatedSymbols.Add(actionSymbol); } - private bool ContainsChildActionSymbol(WixActionSymbol childSymbol, WixActionSymbol parentSymbol) + /// + /// Check the specified action symbol to see if it leads to a cycle. + /// + /// Use the provided dictionary to note the initial action symbol that first led to each action + /// symbol. Any action symbol encountered that has already been encountered starting from a different + /// initial action symbol inherits the loop characteristics of that initial action symbol, and thus is + /// also not part of a cycle. However, any action symbol encountered that has already been encountered + /// starting from the same initial action symbol is an indication that the current action symbol is + /// part of a cycle. + /// + /// The action symbol to be checked. + /// Collection of actions which must be included. + /// The first encountered action symbol that led to each action symbol. + private void CheckForCircularActionReference(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) { - var result = false; + WixActionSymbol currentActionSymbol = null; + var parentActionSymbol = actionSymbol; - if (this.RelativeActionsForActions.TryGetValue(childSymbol.Id.Id, out var relativeActions)) + do { - result = relativeActions.NextActions.Any(a => a.SequenceTable == parentSymbol.SequenceTable && a.Id.Id == parentSymbol.Id.Id) || - relativeActions.PreviousActions.Any(a => a.SequenceTable == parentSymbol.SequenceTable && a.Id.Id == parentSymbol.Id.Id); - } + var previousActionSymbol = currentActionSymbol ?? parentActionSymbol; + currentActionSymbol = parentActionSymbol; - return result; + if (!firstReference.TryGetValue(currentActionSymbol, out var existingInitialActionSymbol)) + { + firstReference[currentActionSymbol] = actionSymbol; + } + else if (existingInitialActionSymbol == actionSymbol) + { + throw new WixException(ErrorMessages.ActionCircularDependency(currentActionSymbol.SourceLineNumbers, currentActionSymbol.SequenceTable.ToString(), currentActionSymbol.Action, previousActionSymbol.Action)); + } + + parentActionSymbol = this.GetParentActionSymbol(currentActionSymbol, requiredActionSymbols); + } while (null != parentActionSymbol); } + /// + /// Get the action symbol that is the parent of the given action symbol. + /// + /// The given action symbol. + /// Collection of actions which must be included. + /// Null if there is no parent. Used for loop termination. + private WixActionSymbol GetParentActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols) + { + if (null == actionSymbol.Before && null == actionSymbol.After) + { + return null; + } + + var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + (actionSymbol.After ?? actionSymbol.Before); + + if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) + { + WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol); + } + + return parentActionSymbol; + } + + private RelativeActions GetRelativeActions(WixActionSymbol action) { if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions)) diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs index 65f4be31..7980ea61 100644 --- a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs @@ -11,7 +11,7 @@ namespace WixToolsetTest.CoreIntegration public class CustomActionFixture { - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6201")] + [Fact] public void CanDetectCustomActionCycle() { var folder = TestData.Get(@"TestData"); @@ -22,8 +22,8 @@ namespace WixToolsetTest.CoreIntegration var intermediateFolder = Path.Combine(baseFolder, "obj"); var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - var result = WixRunner.Execute(new[] - { + var exception = Assert.Throws(() => WixRunner.Execute(new[] + { "build", Path.Combine(folder, "CustomAction", "CustomActionCycle.wxs"), Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), @@ -31,10 +31,35 @@ namespace WixToolsetTest.CoreIntegration "-bindpath", Path.Combine(folder, "SingleFile", "data"), "-intermediateFolder", intermediateFolder, "-o", msiPath - }); + })); + + Assert.Equal("The InstallExecuteSequence table contains an action 'Action1' that is scheduled to come before or after action 'Action3', which is also scheduled to come before or after action 'Action1'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", exception.Message); + } + } + + [Fact] + public void CanDetectCustomActionCycleWithTail() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var exception = Assert.Throws(() => WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomAction", "CustomActionCycleWithTail.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + })); - var warnings = result.Messages.Where(m => m.Level == MessageLevel.Warning).ToArray(); - Assert.False(result.ExitCode == 0 && warnings.Length == 0); + Assert.Equal("The InstallExecuteSequence table contains an action 'Action2' that is scheduled to come before or after action 'Action4', which is also scheduled to come before or after action 'Action2'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", exception.Message); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs new file mode 100644 index 00000000..c64ef143 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3-55-g6feb From 760fb810ba5ecc3c6ce752a9bfa3755f7b7c0f6a Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 25 Feb 2021 09:58:20 -0800 Subject: Absorb Tag.wixext into Core as SoftwareTag element Resolves wixtoolset/issues#5949 --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 7 + .../Bind/GenerateManifestDataFromIRCommand.cs | 1 + .../Bind/ProcessBundleSoftwareTagsCommand.cs | 142 ++++++++++ .../Bundles/CreateBurnManifestCommand.cs | 12 +- .../Bind/BindDatabaseCommand.cs | 12 + .../Bind/CreateOutputFromIRCommand.cs | 3 +- .../Bind/ProcessPackageSoftwareTagsCommand.cs | 131 +++++++++ .../Bind/UpdateFileFacadesCommand.cs | 2 +- src/WixToolset.Core/Bind/FileFacade.cs | 2 +- src/WixToolset.Core/CompilerCore.cs | 1 - src/WixToolset.Core/CompilerErrors.cs | 30 ++ src/WixToolset.Core/Compiler_2.cs | 3 + src/WixToolset.Core/Compiler_Bundle.cs | 3 + src/WixToolset.Core/Compiler_Patch.cs | 3 + src/WixToolset.Core/Compiler_Tag.cs | 315 +++++++++++++++++++++ .../SoftwareTagFixture.cs | 100 +++++++ .../TestData/BundleTag/BundleWithTag.wxs | 15 + .../TestData/BundleTag/fakeba.dll | 1 + .../TestData/ProductTag/Package.en-us.wxl | 11 + .../TestData/ProductTag/PackageComponents.wxs | 10 + .../TestData/ProductTag/PackageWithTag.wxs | 20 ++ .../TestData/ProductTag/example.txt | 1 + 22 files changed, 814 insertions(+), 11 deletions(-) create mode 100644 src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs create mode 100644 src/WixToolset.Core/CompilerErrors.cs create mode 100644 src/WixToolset.Core/Compiler_Tag.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 93620e1b..c9a111c6 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -380,6 +380,13 @@ namespace WixToolset.Core.Burn // Update the bundle per-machine/per-user scope based on the chained packages. this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades); + var softwareTags = section.Symbols.OfType().ToList(); + if (softwareTags.Any()) + { + var command = new ProcessBundleSoftwareTagsCommand(section, softwareTags); + command.Execute(); + } + // Give the extension one last hook before generating the output files. foreach (var extension in this.BackendExtensions) { diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs index c51d380c..36ced6cf 100644 --- a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs @@ -82,6 +82,7 @@ namespace WixToolset.Core.Burn.Bind case SymbolDefinitionType.WixBundleRelatedPackage: case SymbolDefinitionType.WixBundleRollbackBoundary: case SymbolDefinitionType.WixBundleSlipstreamMsp: + case SymbolDefinitionType.WixBundleTag: case SymbolDefinitionType.WixBundleUpdate: case SymbolDefinitionType.WixBundleVariable: case SymbolDefinitionType.WixBuildInfo: diff --git a/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs b/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs new file mode 100644 index 00000000..8584d2a4 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs @@ -0,0 +1,142 @@ +// 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.Burn.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Dtf.WindowsInstaller; + + internal class ProcessBundleSoftwareTagsCommand + { + public ProcessBundleSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags) + { + this.Section = section; + this.SoftwareTags = softwareTags; + } + + private IntermediateSection Section { get; } + + private IEnumerable SoftwareTags { get; } + + public void Execute() + { + var bundleInfo = this.Section.Symbols.OfType().FirstOrDefault(); + var bundleId = NormalizeGuid(bundleInfo.BundleId); + var upgradeCode = NormalizeGuid(bundleInfo.UpgradeCode); + + var uniqueId = String.Concat("wix:bundle/", bundleId); + var persistentId = String.Concat("wix:bundle.upgrade/", upgradeCode); + + // Try to collect all the software id tags from all the child packages. + var containedTags = CollectPackageTags(this.Section); + + foreach (var bundleTag in this.SoftwareTags) + { + using (var ms = new MemoryStream()) + { + CreateTagFile(ms, uniqueId, bundleInfo.Name, bundleInfo.Version, bundleTag.Regid, bundleInfo.Manufacturer, persistentId, containedTags); + bundleTag.Xml = Encoding.UTF8.GetString(ms.ToArray()); + } + } + } + + private static string NormalizeGuid(string guidString) + { + if (Guid.TryParse(guidString, out var guid)) + { + return guid.ToString("D").ToUpperInvariant(); + } + + return guidString; + } + + private static IEnumerable CollectPackageTags(IntermediateSection section) + { + var tags = new List(); + + var msiPackages = section.Symbols.OfType().Where(s => s.Type == WixBundlePackageType.Msi).ToList(); + if (msiPackages.Any()) + { + var payloadSymbolsById = section.Symbols.OfType().ToDictionary(s => s.Id.Id); + + foreach (var msiPackage in msiPackages) + { + var payload = payloadSymbolsById[msiPackage.PayloadRef]; + + using (var db = new Database(payload.SourceFile.Path)) + { + using (var view = db.OpenView("SELECT `Regid`, `TagId` FROM `SoftwareIdentificationTag`")) + { + view.Execute(); + while (true) + { + using (var record = view.Fetch()) + { + if (null == record) + { + break; + } + + tags.Add(new SoftwareTag { Regid = record.GetString(1), Id = record.GetString(2) }); + } + } + } + } + } + } + + return tags; + } + + private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId, IEnumerable containedTags) + { + var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; + + using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true})) + { + writer.WriteStartDocument(); + writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); + writer.WriteAttributeString("tagId", uniqueId); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("version", version); + writer.WriteAttributeString("versionScheme", versionScheme); + + writer.WriteStartElement("Entity"); + writer.WriteAttributeString("name", manufacturer); + writer.WriteAttributeString("regid", regid); + writer.WriteAttributeString("role", "softwareCreator tagCreator"); + writer.WriteEndElement(); // + + if (!String.IsNullOrEmpty(persistendId)) + { + writer.WriteStartElement("Meta"); + writer.WriteAttributeString("persistentId", persistendId); + writer.WriteEndElement(); // + } + + foreach (var containedTag in containedTags) + { + writer.WriteStartElement("Link"); + writer.WriteAttributeString("rel", "component"); + writer.WriteAttributeString("href", String.Concat("swid:", containedTag.Id)); + writer.WriteEndElement(); // + } + + writer.WriteEndElement(); // + } + } + + private class SoftwareTag + { + public string Regid { get; set; } + + public string Id { get; set; } + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 71bc0229..3bc6bf1b 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -295,17 +295,15 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); // } -#if TODO // Handle SWID Tags - var bundleTags = this.Output.Tables["WixBundleTag"].RowsAs(); - foreach (var row in bundleTags) + foreach (var bundleTagSymbol in this.Section.Symbols.OfType()) { writer.WriteStartElement("SoftwareTag"); - writer.WriteAttributeString("Filename", (string)row[0]); - writer.WriteAttributeString("Regid", (string)row[1]); - writer.WriteCData((string)row[4]); + writer.WriteAttributeString("Filename", bundleTagSymbol.Filename); + writer.WriteAttributeString("Regid", bundleTagSymbol.Regid); + writer.WriteAttributeString("Path", bundleTagSymbol.InstallPath); + writer.WriteCData(bundleTagSymbol.Xml); writer.WriteEndElement(); } -#endif writer.WriteEndElement(); // diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 012c7c4c..25a093fd 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -282,6 +282,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind return null; } + // Process SoftwareTags in MSI packages. + if (SectionType.Product == section.Type) + { + var softwareTags = section.Symbols.OfType().ToList(); + + if (softwareTags.Any()) + { + var command = new ProcessPackageSoftwareTagsCommand(section, softwareTags, this.IntermediateFolder); + command.Execute(); + } + } + // Gather information about files that do not come from merge modules. { var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index b52ff434..37383caa 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -231,6 +231,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind case SymbolDefinitionType.WixPatchRef: case SymbolDefinitionType.WixPatchTarget: case SymbolDefinitionType.WixProperty: + case SymbolDefinitionType.WixProductTag: case SymbolDefinitionType.WixSimpleReference: case SymbolDefinitionType.WixSuppressAction: case SymbolDefinitionType.WixSuppressModularization: @@ -456,7 +457,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void AddDirectorySymbol(DirectorySymbol symbol) { - if (String.IsNullOrEmpty(symbol.ShortName) && !symbol.Name.Equals(".") && !symbol.Name.Equals("SourceDir") && !Common.IsValidShortFilename(symbol.Name, false)) + if (String.IsNullOrEmpty(symbol.ShortName) && symbol.Name != null && !symbol.Name.Equals(".") && !symbol.Name.Equals("SourceDir") && !Common.IsValidShortFilename(symbol.Name, false)) { symbol.ShortName = CreateShortName(symbol.Name, false, false, "Directory", symbol.ParentDirectoryRef); } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs new file mode 100644 index 00000000..9a068603 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs @@ -0,0 +1,131 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class ProcessPackageSoftwareTagsCommand + { + public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags, string intermediateFolder) + { + this.Section = section; + this.SoftwareTags = softwareTags; + this.IntermediateFolder = intermediateFolder; + } + + private string IntermediateFolder { get; } + + private IntermediateSection Section { get; } + + private IEnumerable SoftwareTags { get; } + + public void Execute() + { + string productName = null; + string productVersion = null; + string manufacturer = null; + string upgradeCode = null; + + var summaryInfo = this.Section.Symbols.OfType().FirstOrDefault(s => s.PropertyId == SummaryInformationType.PackageCode); + var packageCode = NormalizeGuid(summaryInfo?.Value); + + foreach (var property in this.Section.Symbols.OfType()) + { + switch (property.Id.Id) + { + case "ProductName": + productName = property.Value; + break; + case "ProductVersion": + productVersion = property.Value; + break; + case "Manufacturer": + manufacturer = property.Value; + break; + case "UpgradeCode": + upgradeCode = NormalizeGuid(property.Value); + break; + } + } + + var fileSymbolsById = this.Section.Symbols.OfType().Where(f => f.Id != null).ToDictionary(f => f.Id.Id); + + var workingFolder = Path.Combine(this.IntermediateFolder, "_swidtag"); + + Directory.CreateDirectory(workingFolder); + + foreach (var tagRow in this.SoftwareTags) + { + if (fileSymbolsById.TryGetValue(tagRow.FileRef, out var fileSymbol)) + { + var uniqueId = String.Concat("msi:package/", packageCode); + var persistentId = String.IsNullOrEmpty(upgradeCode) ? null : String.Concat("msi:upgrade/", upgradeCode); + + // Write the tag file. + fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(workingFolder, fileSymbol.Name) }; + + using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create)) + { + CreateTagFile(fs, uniqueId, productName, productVersion, tagRow.Regid, manufacturer, persistentId); + } + + // Ensure the matching "SoftwareIdentificationTag" row exists and + // is populated correctly. + this.Section.AddSymbol(new SoftwareIdentificationTagSymbol(tagRow.SourceLineNumbers, tagRow.Id) + { + FileRef = fileSymbol.Id.Id, + Regid = tagRow.Regid, + TagId = uniqueId, + PersistentId = persistentId + }); + } + } + } + + private static string NormalizeGuid(string guidString) + { + if (Guid.TryParse(guidString, out var guid)) + { + return guid.ToString("D").ToUpperInvariant(); + } + + return guidString; + } + + private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId) + { + var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; + + using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) + { + writer.WriteStartDocument(); + writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); + writer.WriteAttributeString("tagId", uniqueId); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("version", version); + writer.WriteAttributeString("versionScheme", versionScheme); + + writer.WriteStartElement("Entity"); + writer.WriteAttributeString("name", manufacturer); + writer.WriteAttributeString("regid", regid); + writer.WriteAttributeString("role", "softwareCreator tagCreator"); + writer.WriteEndElement(); // + + if (!String.IsNullOrEmpty(persistendId)) + { + writer.WriteStartElement("Meta"); + writer.WriteAttributeString("persistentId", persistendId); + writer.WriteEndElement(); // + } + + writer.WriteEndElement(); // + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs index 938627ed..d5bdc797 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -45,7 +45,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var assemblyNameSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - foreach (var file in this.UpdateFileFacades) + foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) { this.UpdateFileFacade(file, assemblyNameSymbols); } diff --git a/src/WixToolset.Core/Bind/FileFacade.cs b/src/WixToolset.Core/Bind/FileFacade.cs index ec4e9725..9705cd01 100644 --- a/src/WixToolset.Core/Bind/FileFacade.cs +++ b/src/WixToolset.Core/Bind/FileFacade.cs @@ -125,7 +125,7 @@ namespace WixToolset.Core.Bind public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers; - public string SourcePath => this.FileRow == null ? this.FileSymbol.Source.Path : this.FileRow.Source; + public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source; public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index 1f6d6329..53e0f3fc 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -6,7 +6,6 @@ namespace WixToolset.Core using System.Collections; using System.Collections.Generic; using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; using System.Text; diff --git a/src/WixToolset.Core/CompilerErrors.cs b/src/WixToolset.Core/CompilerErrors.cs new file mode 100644 index 00000000..da64c376 --- /dev/null +++ b/src/WixToolset.Core/CompilerErrors.cs @@ -0,0 +1,30 @@ +// 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 +{ + using WixToolset.Data; + + internal static class CompilerErrors + { + public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name) + { + return Message(sourceLineNumbers, Ids.IllegalName, "The Tag/@Name attribute value, '{1}', contains invalid filename identifiers. The Tag/@Name may have defaulted from the {0}/@Name attrbute. If so, use the Tag/@Name attribute to provide a valid filename. Any character except for the follow may be used: \\ ? | > < : / * \".", parentElement, name); + } + + public static Message ExampleRegid(SourceLineNumber sourceLineNumbers, string regid) + { + return Message(sourceLineNumbers, Ids.ExampleRegid, "Regid '{0}' is a placeholder that must be replaced with an appropriate value for your installation. Use the simplified URI for your organization or project.", regid); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + IllegalName = 6601, + ExampleRegid = 6602, + } + } +} diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index 09d56e49..295392c8 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -316,6 +316,9 @@ namespace WixToolset.Core string parentName = null; this.ParseSFPCatalogElement(child, ref parentName); break; + case "SoftwareTag": + this.ParsePackageTagElement(child); + break; case "SummaryInformation": this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); break; diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 7a386de7..1ee09166 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -346,6 +346,9 @@ namespace WixToolset.Core case "SetVariableRef": this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); break; + case "SoftwareTag": + this.ParseBundleTagElement(child); + break; case "Update": this.ParseUpdateElement(child); break; diff --git a/src/WixToolset.Core/Compiler_Patch.cs b/src/WixToolset.Core/Compiler_Patch.cs index 2fb1affb..83737c43 100644 --- a/src/WixToolset.Core/Compiler_Patch.cs +++ b/src/WixToolset.Core/Compiler_Patch.cs @@ -410,6 +410,9 @@ namespace WixToolset.Core case "PropertyRef": this.ParsePatchChildRefElement(child, "Property"); break; + case "SoftwareTagRef": + this.ParseTagRefElement(child); + break; case "UIRef": this.ParsePatchChildRefElement(child, "WixUI"); break; diff --git a/src/WixToolset.Core/Compiler_Tag.cs b/src/WixToolset.Core/Compiler_Tag.cs new file mode 100644 index 00000000..2b3523c8 --- /dev/null +++ b/src/WixToolset.Core/Compiler_Tag.cs @@ -0,0 +1,315 @@ +// 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 +{ + using System; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a Tag element for Software Id Tag registration under a Bundle element. + /// + /// The element to parse. + private void ParseBundleTagElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string name = null; + string regid = null; + string installPath = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "InstallDirectory": + case "Bitness": + this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Package")); + break; + case "InstallPath": + installPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(name)) + { + name = node.Parent?.Attribute("Name")?.Value; + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + } + + if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) + { + this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); + } + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + } + + if (String.IsNullOrEmpty(installPath)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPath")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleTagSymbol(sourceLineNumbers) + { + Filename = String.Concat(name, ".swidtag"), + Regid = regid, + Name = name, + InstallPath = installPath + }); + } + } + + /// + /// Parses a Tag element for Software Id Tag registration under a Package element. + /// + /// The element to parse. + private void ParsePackageTagElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string name = null; + string regid = null; + string feature = null; + string installDirectory = null; + var win64 = this.Context.IsCurrentPlatform64Bit; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Feature": + feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "InstallDirectory": + installDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "InstallPath": + this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bundle")); + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(name)) + { + name = node.Parent?.Attribute("Name")?.Value; + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + } + + if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) + { + this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); + } + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + return; + } + else if (id == null) + { + id = this.CreateTagId(regid); + } + + if (String.IsNullOrEmpty(installDirectory)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallDirectory")); + } + + if (!this.Core.EncounteredError) + { + var fileName = String.Concat(name, ".swidtag"); + + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, installDirectory); + this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) + { + Name = "swidtag", + ParentDirectoryRef = installDirectory, + ComponentGuidGenerationSeed = "4BAD0C8B-3AF0-BFE3-CC83-094749A1C4B1" + }); + + this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) + { + ComponentId = "*", + DirectoryRef = id.Id, + KeyPath = id.Id, + KeyPathType = ComponentKeyPathType.File, + Location = ComponentLocation.LocalOnly, + Win64 = win64 + }); + + this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) + { + ComponentRef = id.Id, + Name = fileName, + DiskId = 1, + Attributes = FileSymbolAttributes.ReadOnly, + }); + + if (!String.IsNullOrEmpty(feature)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); + } + else + { + feature = "WixSwidTag"; + this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Private, feature)) + { + Title = "ISO/IEC 19770-2", + Level = 1, + InstallDefault = FeatureInstallDefault.Local, + Display = 0, + DisallowAdvertise = true, + DisallowAbsent = true, + }); + } + this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); + + this.Core.EnsureTable(sourceLineNumbers, "SoftwareIdentificationTag"); + this.Core.AddSymbol(new WixProductTagSymbol(sourceLineNumbers, id) + { + FileRef = id.Id, + Regid = regid, + Name = name + }); + } + } + + /// + /// Parses a TagRef element for Software Id Tag registration under a PatchFamily element. + /// + /// The element to parse. + private void ParseTagRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string regid = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + } + + if (!this.Core.EncounteredError) + { + var id = this.CreateTagId(regid); + + this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers, id) + { + Table = SymbolDefinitions.Component.Name, + PrimaryKeys = id.Id + }); + } + } + + private Identifier CreateTagId(string regid) => this.Core.CreateIdentifier("tag", regid, ".product.tag"); + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs b/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs new file mode 100644 index 00000000..15276b18 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs @@ -0,0 +1,100 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using System.Xml.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class SoftwareTagFixture + { + private static readonly XNamespace BurnManifestNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; + private static readonly XNamespace SwidTagNamespace = "http://standards.iso.org/iso/19770/-2/2009/schema.xsd"; + + [Fact] + public void CanBuildPackageWithTag() + { + var folder = TestData.Get(@"TestData\ProductTag"); + var build = new Builder(folder, null, new[] { folder }); + + var results = build.BuildAndQuery(Build, "File", "SoftwareIdentificationTag"); + + var replacePackageCodeStart = results[2].IndexOf("\tmsi:package/") + "\tmsi:package/".Length; + var replacePackageCodeEnd = results[2].IndexOf("\t", replacePackageCodeStart); + results[2] = results[2].Substring(0, replacePackageCodeStart) + "???" + results[2].Substring(replacePackageCodeEnd); + WixAssert.CompareLineByLine(new[] + { + "File:filF5_pLhBuF5b4N9XEo52g_hUM5Lo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\texample.txt\t20\t\t\t512\t1", + "File:tagEYRYWwOt95punO7qPPAQ9p1GBpY\ttagEYRYWwOt95punO7qPPAQ9p1GBpY\trdcfonyt.swi|~TagTestPackage.swidtag\t449\t\t\t1\t2", + "SoftwareIdentificationTag:tagEYRYWwOt95punO7qPPAQ9p1GBpY\twixtoolset.org\tmsi:package/???\tmsi:upgrade/047730A5-30FE-4A62-A520-DA9381B8226A\t" + }, results.ToArray()); + } + + [Fact] + public void CanBuildBundleWithTag() + { + var testDataFolder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(testDataFolder, "ProductTag", "PackageWithTag.wxs"), + Path.Combine(testDataFolder, "ProductTag", "PackageComponents.wxs"), + "-loc", Path.Combine(testDataFolder, "ProductTag", "Package.en-us.wxl"), + "-bindpath", Path.Combine(testDataFolder, "ProductTag"), + "-intermediateFolder", Path.Combine(intermediateFolder, "package"), + "-o", Path.Combine(baseFolder, "package", @"test.msi") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(testDataFolder, "BundleTag", "BundleWithTag.wxs"), + "-bindpath", Path.Combine(testDataFolder, "BundleTag"), + "-bindpath", Path.Combine(baseFolder, "package"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + + using (var ouput = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) + { + var badata = ouput.GetDataStream("wix-burndata.xml"); + var doc = XDocument.Load(badata); + + var swidTag = doc.Root.Element(BurnManifestNamespace + "Registration").Element(BurnManifestNamespace + "SoftwareTag").Value; + + var swidTagPath = Path.Combine(baseFolder, "test.swidtag"); + File.WriteAllText(swidTagPath, swidTag); + + var docTag = XDocument.Load(swidTagPath); + var title = docTag.Root.Attribute("name").Value; + var version = docTag.Root.Attribute("version").Value; + Assert.Equal("~TagTestBundle", title); + Assert.Equal("4.3.2.1", version); + } + } + } + + private static void Build(string[] args) + { + var result = WixRunner.Execute(args) + .AssertSuccess(); + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs new file mode 100644 index 00000000..f44fb7bc --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll new file mode 100644 index 00000000..64061ea0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll @@ -0,0 +1 @@ +This is fakeba.dll. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs new file mode 100644 index 00000000..37a2c462 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs new file mode 100644 index 00000000..17543c1a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file -- cgit v1.2.3-55-g6feb From 5fd1b7ff82f17d55c8357fe76898a1bdc5953476 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 26 Feb 2021 11:24:10 -0800 Subject: Absorb Dependency.wixext into Core Partly resolves wixtoolset/issues#5949 --- .../Bind/BindDatabaseCommand.cs | 12 + .../Bind/ProcessDependencyReferencesCommand.cs | 111 + src/WixToolset.Core/Compiler.cs | 13 + src/WixToolset.Core/CompilerErrors.cs | 13 + src/WixToolset.Core/CompilerWarnings.cs | 53 + src/WixToolset.Core/Compiler_2.cs | 4972 ------------------- src/WixToolset.Core/Compiler_Bundle.cs | 9 + src/WixToolset.Core/Compiler_Dependency.cs | 385 ++ src/WixToolset.Core/Compiler_Module.cs | 3 + src/WixToolset.Core/Compiler_Package.cs | 4975 ++++++++++++++++++++ .../DependencyExtensionFixture.cs | 30 + .../TestData/UsingProvides/Package.en-us.wxl | 11 + .../TestData/UsingProvides/Package.wxs | 14 + .../TestData/UsingProvides/PackageComponents.wxs | 10 + .../TestData/UsingProvides/example.txt | 1 + 15 files changed, 5640 insertions(+), 4972 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs create mode 100644 src/WixToolset.Core/CompilerWarnings.cs delete mode 100644 src/WixToolset.Core/Compiler_2.cs create mode 100644 src/WixToolset.Core/Compiler_Dependency.cs create mode 100644 src/WixToolset.Core/Compiler_Package.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 25a093fd..a3f2da94 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -325,6 +325,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind command.Execute(); } + // Process dependency references. + if (SectionType.Product == section.Type || SectionType.Module == section.Type) + { + var dependencyRefs = section.Symbols.OfType().ToList(); + + if (dependencyRefs.Any()) + { + var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs); + command.Execute(); + } + } + // If there are any backend extensions, give them the opportunity to process // the section now that the fields have all be resolved. // diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs new file mode 100644 index 00000000..899d06e1 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs @@ -0,0 +1,111 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class ProcessDependencyReferencesCommand + { + // The root registry key for the dependency extension. We write to Software\Classes explicitly + // based on the current security context instead of HKCR. See + // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. + private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; + private const string RegistryDependents = "Dependents"; + + public ProcessDependencyReferencesCommand(IWindowsInstallerBackendHelper backendHelper, IntermediateSection section, IEnumerable dependencyRefSymbols) + { + this.Section = section; + this.DependencyRefSymbols = dependencyRefSymbols; + } + + private IntermediateSection Section { get; } + + private IEnumerable DependencyRefSymbols { get; } + + public void Execute() + { + var wixDependencyRows = this.Section.Symbols.OfType().ToDictionary(d => d.Id.Id); + var wixDependencyProviderRows = this.Section.Symbols.OfType().ToDictionary(d => d.Id.Id); + + // For each relationship, get the provides and requires rows to generate registry values. + foreach (var wixDependencyRefRow in this.DependencyRefSymbols) + { + var providesId = wixDependencyRefRow.WixDependencyProviderRef; + var requiresId = wixDependencyRefRow.WixDependencyRef; + + // If we do not find both symbols, skip the registry key generation. + if (!wixDependencyRows.TryGetValue(requiresId, out var wixDependencyRow)) + { + continue; + } + + if (!wixDependencyProviderRows.TryGetValue(providesId, out var wixDependencyProviderRow)) + { + continue; + } + + // Format the root registry key using the required provider key and the current provider key. + var requiresKey = wixDependencyRow.Id.Id; + var providesKey = wixDependencyRow.ProviderKey; + var keyRequires = String.Format(@"{0}{1}\{2}\{3}", DependencyRegistryRoot, requiresKey, RegistryDependents, providesKey); + + // Get the component ID from the provider. + var componentId = wixDependencyProviderRow.ComponentRef; + + var id = Common.GenerateIdentifier("reg", providesId, requiresId, "(Default)"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Private, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "*", + }); + + if (!String.IsNullOrEmpty(wixDependencyRow.MinVersion)) + { + id = Common.GenerateIdentifier("reg", providesId, requiresId, "MinVersion"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Private, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "MinVersion", + Value = wixDependencyRow.MinVersion + }); + } + + string maxVersion = (string)wixDependencyRow[3]; + if (!String.IsNullOrEmpty(wixDependencyRow.MaxVersion)) + { + id = Common.GenerateIdentifier("reg", providesId, requiresId, "MaxVersion"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Private, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "MaxVersion", + Value = wixDependencyRow.MaxVersion + }); + } + + if (wixDependencyRow.Attributes != WixDependencySymbolAttributes.None) + { + id = Common.GenerateIdentifier("reg", providesId, requiresId, "Attributes"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Private, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "Attributes", + Value = String.Concat("#", (int)wixDependencyRow.Attributes) + }); + } + } + } + } +} diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 85261cce..ac99a8a1 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -2366,6 +2366,16 @@ namespace WixToolset.Core var foundExtension = false; this.ParseProgIdElement(child, id.Id, YesNoType.NotSet, null, null, null, ref foundExtension, YesNoType.NotSet); break; + case "Provides": + if (win64) + { + this.Messaging.Write(CompilerWarnings.Win64Component(sourceLineNumbers, id.Id)); + } + + keyPathSet = this.ParseProvidesElement(child, null, id.Id, out keyPossible); + keyBit = ComponentKeyPathType.Registry; + break; + case "RegistryKey": keyPathSet = this.ParseRegistryKeyElement(child, id.Id, null, null, win64, out keyPossible); keyBit = ComponentKeyPathType.Registry; @@ -6290,6 +6300,9 @@ namespace WixToolset.Core case "RelatedBundle": this.ParseRelatedBundleElement(child); break; + case "Requires": + this.ParseRequiresElement(child, null, false); + break; case "SetDirectory": this.ParseSetDirectoryElement(child); break; diff --git a/src/WixToolset.Core/CompilerErrors.cs b/src/WixToolset.Core/CompilerErrors.cs index da64c376..9b3d85b9 100644 --- a/src/WixToolset.Core/CompilerErrors.cs +++ b/src/WixToolset.Core/CompilerErrors.cs @@ -6,6 +6,16 @@ namespace WixToolset.Core internal static class CompilerErrors { + public static Message IllegalCharactersInProvider(SourceLineNumber sourceLineNumbers, string attributeName, char illegalChar, string illegalChars) + { + return Message(sourceLineNumbers, Ids.IllegalCharactersInProvider, "The provider key authored into the {0} attribute contains an illegal character, '{1}'. Please author the provider key without any of the following characters: {2}", attributeName, illegalChar, illegalChars); + } + + public static Message ReservedValue(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string attributeValue) + { + return Message(sourceLineNumbers, Ids.ReservedValue, "The {0}/@{1} attribute value '{2}' is reserved and cannot be used here. Please choose a different value.", elementName, attributeName, attributeValue); + } + public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name) { return Message(sourceLineNumbers, Ids.IllegalName, "The Tag/@Name attribute value, '{1}', contains invalid filename identifiers. The Tag/@Name may have defaulted from the {0}/@Name attrbute. If so, use the Tag/@Name attribute to provide a valid filename. Any character except for the follow may be used: \\ ? | > < : / * \".", parentElement, name); @@ -23,6 +33,9 @@ namespace WixToolset.Core public enum Ids { + IllegalCharactersInProvider = 5400, + ReservedValue = 5401, + IllegalName = 6601, ExampleRegid = 6602, } diff --git a/src/WixToolset.Core/CompilerWarnings.cs b/src/WixToolset.Core/CompilerWarnings.cs new file mode 100644 index 00000000..3b9666dd --- /dev/null +++ b/src/WixToolset.Core/CompilerWarnings.cs @@ -0,0 +1,53 @@ +// 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 +{ + using WixToolset.Data; + + internal static class CompilerWarnings + { + public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified in an MSI package. The ProductVersion will be used by default."); + } + + public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers, string id) + { + return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified for MSI package {0}. The ProductVersion will be used by default.", id); + } + + public static Message PropertyRemoved(string name) + { + return Message(null, Ids.PropertyRemoved, "The property {0} was authored in the package with a value and will be removed. The property should not be authored.", name); + } + + public static Message ProvidesKeyNotFound(SourceLineNumber sourceLineNumbers, string id) + { + return Message(sourceLineNumbers, Ids.ProvidesKeyNotFound, "The provider key with identifier {0} was not found in the WixDependencyProvider table. Related registry rows will not be removed from authoring.", id); + } + + public static Message RequiresKeyNotFound(SourceLineNumber sourceLineNumbers, string id) + { + return Message(sourceLineNumbers, Ids.RequiresKeyNotFound, "The dependency key with identifier {0} was not found in the WixDependency table. Related registry rows will not be removed from authoring.", id); + } + + public static Message Win64Component(SourceLineNumber sourceLineNumbers, string componentId) + { + return Message(sourceLineNumbers, Ids.Win64Component, "The Provides element should not be authored in the 64-bit component with identifier {0}. The dependency feature may not work if installing this package on 64-bit Windows operating systems prior to Windows 7 and Windows Server 2008 R2. Set the Component/@Bitness attribute to \"always32\" to ensure the dependency feature works correctly on legacy operating systems.", componentId); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); + } + + public enum Ids + { + ProvidesKeyNotFound = 5431, + RequiresKeyNotFound = 5432, + PropertyRemoved = 5433, + DiscouragedVersionAttribute = 5434, + Win64Component = 5435, + } + } +} diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs deleted file mode 100644 index 295392c8..00000000 --- a/src/WixToolset.Core/Compiler_2.cs +++ /dev/null @@ -1,4972 +0,0 @@ -// 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 -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Globalization; - using System.IO; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses a product element. - /// - /// Element to parse. - private void ParsePackageElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compressed = YesNoDefaultType.Default; - var sourceBits = 0; - var codepage = 65001; - var productCode = "*"; - var isPerMachine = true; - string installScope = null; - string upgradeCode = null; - string manufacturer = null; - string version = null; - string symbols = null; - var isCodepageSet = false; - var isPackageNameSet = false; - var isKeywordsSet = false; - var isPackageAuthorSet = false; - - this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); - - this.activeName = null; - this.activeLanguage = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Codepage": - codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); - break; - case "Compressed": - compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - break; - case "InstallerVersion": - msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "Language": - this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Manufacturer": - manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); - if ("PUT-COMPANY-NAME-HERE" == manufacturer) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer)); - } - break; - case "Name": - this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); - if ("PUT-PRODUCT-NAME-HERE" == this.activeName) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); - } - break; - case "ProductCode": - productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); - break; - case "Scope": - installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (installScope) - { - case "perMachine": - // handled below after we create the section. - break; - case "perUser": - isPerMachine = false; - sourceBits |= 8; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); - break; - } - break; - case "ShortNames": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - sourceBits |= 1; - } - break; - case "UpgradeCode": - upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Version": // if the attribute is valid version, use the attribute value as is (so "1.0000.01.01" would *not* get translated to "1.0.1.1"). - var verifiedVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - if (!String.IsNullOrEmpty(verifiedVersion)) - { - version = attrib.Value; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == productCode) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == this.activeLanguage) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); - } - - if (null == manufacturer) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); - } - - if (null == this.activeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == upgradeCode) - { - this.Core.Write(WarningMessages.MissingUpgradeCode(sourceLineNumbers)); - } - - if (null == version) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - else if (!CompilerCore.IsValidProductVersion(version)) - { - this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); - } - - if (compressed != YesNoDefaultType.No) - { - sourceBits |= 2; - } - - if (this.Core.EncounteredError) - { - return; - } - - try - { - this.compilingProduct = true; - this.Core.CreateActiveSection(productCode, SectionType.Product, codepage, this.Context.CompilationId); - - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "Manufacturer"), manufacturer, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductCode"), productCode, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductLanguage"), this.activeLanguage, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductName"), this.activeName, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductVersion"), version, false, false, false, true); - if (null != upgradeCode) - { - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "UpgradeCode"), upgradeCode, false, false, false, true); - } - - if (isPerMachine) - { - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ALLUSERS"), "1", false, false, false, false); - } - - this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WordCount, - Value = sourceBits.ToString(CultureInfo.InvariantCulture) - }); - - var contextValues = new Dictionary - { - ["ProductLanguage"] = this.activeLanguage, - ["ProductVersion"] = version, - ["UpgradeCode"] = upgradeCode - }; - - var featureDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "_locDefinition": - break; - case "AdminExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); - break; - case "AdminUISequence": - this.ParseSequenceElement(child, SequenceTable.AdminUISequence); - break; - case "AdvertiseExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); - break; - case "InstallExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); - break; - case "InstallUISequence": - this.ParseSequenceElement(child, SequenceTable.InstallUISequence); - break; - case "AppId": - this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); - break; - case "Binary": - this.ParseBinaryElement(child); - break; - case "ComplianceCheck": - this.ParseComplianceCheckElement(child); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "ComponentGroup": - this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); - break; - case "CustomAction": - this.ParseCustomActionElement(child); - break; - case "CustomActionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); - break; - case "CustomTable": - this.ParseCustomTableElement(child); - break; - case "CustomTableRef": - this.ParseCustomTableRefElement(child); - break; - case "Directory": - this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); - break; - case "DirectoryRef": - this.ParseDirectoryRefElement(child); - break; - case "EmbeddedChainer": - this.ParseEmbeddedChainerElement(child); - break; - case "EmbeddedChainerRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); - break; - case "EnsureTable": - this.ParseEnsureTableElement(child); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode); - break; - case "FeatureGroupRef": - this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode); - break; - case "Icon": - this.ParseIconElement(child); - break; - case "InstanceTransforms": - this.ParseInstanceTransformsElement(child); - break; - case "Launch": - this.ParseLaunchElement(child); - break; - case "MajorUpgrade": - this.ParseMajorUpgradeElement(child, contextValues); - break; - case "Media": - this.ParseMediaElement(child, null); - break; - case "MediaTemplate": - this.ParseMediaTemplateElement(child, null); - break; - case "PackageCertificates": - case "PatchCertificates": - this.ParseCertificatesElement(child); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "PropertyRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Property); - break; - case "SetDirectory": - this.ParseSetDirectoryElement(child); - break; - case "SetProperty": - this.ParseSetPropertyElement(child); - break; - case "SFPCatalog": - string parentName = null; - this.ParseSFPCatalogElement(child, ref parentName); - break; - case "SoftwareTag": - this.ParsePackageTagElement(child); - break; - case "SummaryInformation": - this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); - break; - case "SymbolPath": - if (null != symbols) - { - symbols += ";" + this.ParseSymbolPathElement(child); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - case "UI": - this.ParseUIElement(child); - break; - case "UIRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); - break; - case "Upgrade": - this.ParseUpgradeElement(child); - break; - case "WixVariable": - this.ParseWixVariableElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - if (!isCodepageSet) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Codepage, - Value = "1252" - }); - } - - if (!isPackageNameSet) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = this.activeName - }); - } - - if (!isPackageAuthorSet) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Author, - Value = manufacturer - }); - } - - if (!isKeywordsSet) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = "Installer" - }); - } - - if (null != symbols) - { - this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers) - { - SymbolId = productCode, - SymbolType = SymbolPathType.Product, - SymbolPaths = symbols, - }); - } - } - } - finally - { - this.compilingProduct = false; - } - } - - private void GetDefaultPlatformAndInstallerVersion(out string platform, out int msiVersion) - { - // Let's default to a modern version of MSI. Users can override, - // of course, subject to platform-specific limitations. - msiVersion = 500; - - switch (this.CurrentPlatform) - { - case Platform.X86: - platform = "Intel"; - break; - case Platform.X64: - platform = "x64"; - break; - case Platform.ARM64: - platform = "Arm64"; - break; - default: - throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString()); - } - } - - private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform) - { - if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) - { - msiVersion = 200; - this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers)); - } - - if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion) - { - msiVersion = 500; - this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers)); - } - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Comments, - Value = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Title, - Value = "Installation Database" - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.PlatformAndLanguage, - Value = String.Format(CultureInfo.InvariantCulture, "{0};{1}", platform, this.activeLanguage) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WindowsInstallerVersion, - Value = msiVersion.ToString(CultureInfo.InvariantCulture) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Security, - Value = "2" - }); - - } - - /// - /// Parses an odbc driver or translator element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Default identifer for driver/translator file. - /// Symbol type we're processing for. - private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, SymbolDefinitionType symbolDefinitionType) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var driver = fileId; - string name = null; - var setup = fileId; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "File": - driver = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, driver); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SetupFile": - setup = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, setup); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("odb", name, fileId, setup); - } - - // drivers have a few possible children - if (SymbolDefinitionType.ODBCDriver == symbolDefinitionType) - { - // process any data sources for the driver - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ODBCDataSource": - string ignoredKeyPath = null; - this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath); - break; - case "Property": - this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCAttribute); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - else - { - this.Core.ParseForExtensionElements(node); - } - - if (!this.Core.EncounteredError) - { - switch (symbolDefinitionType) - { - case SymbolDefinitionType.ODBCDriver: - this.Core.AddSymbol(new ODBCDriverSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Description = name, - FileRef = driver, - SetupFileRef = setup, - }); - break; - case SymbolDefinitionType.ODBCTranslator: - this.Core.AddSymbol(new ODBCTranslatorSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Description = name, - FileRef = driver, - SetupFileRef = setup, - }); - break; - default: - throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); - } - } - } - - /// - /// Parses a Property element underneath an ODBC driver or translator. - /// - /// Element to parse. - /// Identifier of parent driver or translator. - /// Name of the table to create property in. - private void ParseODBCProperty(XElement node, string parentId, SymbolDefinitionType symbolDefinitionType) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string propertyValue = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - propertyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var identifier = new Identifier(AccessModifier.Private, parentId, id); - switch (symbolDefinitionType) - { - case SymbolDefinitionType.ODBCAttribute: - this.Core.AddSymbol(new ODBCAttributeSymbol(sourceLineNumbers, identifier) - { - DriverRef = parentId, - Attribute = id, - Value = propertyValue, - }); - break; - case SymbolDefinitionType.ODBCSourceAttribute: - this.Core.AddSymbol(new ODBCSourceAttributeSymbol(sourceLineNumbers, identifier) - { - DataSourceRef = parentId, - Attribute = id, - Value = propertyValue, - }); - break; - default: - throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); - } - } - } - - /// - /// Parse an odbc data source element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Default name of driver. - /// Identifier of this element in case it is a keypath. - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var keyPath = YesNoType.NotSet; - string name = null; - var registration = CompilerConstants.IntegerNotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "DriverName": - driverName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "KeyPath": - keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Registration": - var registrationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (registrationValue) - { - case "machine": - registration = 0; - break; - case "user": - registration = 1; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (CompilerConstants.IntegerNotSet == registration) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration")); - registration = CompilerConstants.IllegalInteger; - } - - if (null == id) - { - id = this.Core.CreateIdentifier("odc", name, driverName, registration.ToString()); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Property": - this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCSourceAttribute); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ODBCDataSourceSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Description = name, - DriverDescription = driverName, - Registration = registration - }); - } - - possibleKeyPath = id.Id; - return keyPath; - } - - /// - /// Parses a package element. - /// - /// Element to parse. - /// - /// - /// - /// - private void ParseSummaryInformationElement(XElement node, ref bool isCodepageSet, ref bool isPackageNameSet, ref bool isKeywordsSet, ref bool isPackageAuthorSet) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string codepage = null; - string packageName = null; - string keywords = null; - string packageAuthor = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Codepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); - break; - case "Description": - packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Keywords": - keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Manufacturer": - packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if ("PUT-COMPANY-NAME-HERE" == packageAuthor) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor)); - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - if (null != codepage) - { - isCodepageSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Codepage, - Value = codepage - }); - } - - if (null != packageName) - { - isPackageNameSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = packageName - }); - } - - if (null != packageAuthor) - { - isPackageAuthorSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Author, - Value = packageAuthor - }); - } - - if (null != keywords) - { - isKeywordsSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = keywords - }); - } - } - } - - /// - /// Parses a patch information element. - /// - /// Element to parse. - private void ParsePatchInformationElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var codepage = "1252"; - string comments = null; - var keywords = "Installer,Patching,PCP,Database"; - var msiVersion = 1; // Should always be 1 for patches - string packageAuthor = null; - var packageName = this.activeName; - var security = YesNoDefaultType.Default; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AdminImage": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "Comments": - comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Compressed": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "Description": - packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Keywords": - keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Languages": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "Manufacturer": - packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Platforms": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "ReadOnly": - security = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - break; - case "ShortNames": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "SummaryCodepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Codepage, - Value = codepage - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Title, - Value = "Patch" - }); - - if (null != packageName) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = packageName - }); - } - - if (null != packageAuthor) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Author, - Value = packageAuthor - }); - } - - if (null != keywords) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = keywords - }); - } - - if (null != comments) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Comments, - Value = comments - }); - } - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WindowsInstallerVersion, - Value = msiVersion.ToString(CultureInfo.InvariantCulture) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WordCount, - Value = "0" - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Security, - Value = YesNoDefaultType.No == security ? "0" : YesNoDefaultType.Yes == security ? "4" : "2" - }); - } - } - - /// - /// Parses a permission element. - /// - /// Element to parse. - /// Identifier of object to be secured. - /// Name of table that contains objectId. - private void ParsePermissionElement(XElement node, string objectId, string tableName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var bits = new BitArray(32); - string domain = null; - string[] specialPermissions = null; - string user = null; - - switch (tableName) - { - case "CreateFolder": - specialPermissions = Common.FolderPermissions; - break; - case "File": - specialPermissions = Common.FilePermissions; - break; - case "Registry": - specialPermissions = Common.RegistryPermissions; - break; - default: - this.Core.UnexpectedElement(node.Parent, node); - return; // stop processing this element since no valid permissions are available - } - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Domain": - domain = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "User": - user = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "FileAllRights": - // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127) - bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[16] = bits[17] = bits[18] = bits[19] = bits[20] = true; - break; - case "SpecificRightsAll": - // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111) - bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[9] = bits[10] = bits[11] = bits[12] = bits[13] = bits[14] = bits[15] = true; - break; - default: - var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - if (!this.Core.TrySetBitFromName(Common.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16)) - { - if (!this.Core.TrySetBitFromName(Common.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28)) - { - if (!this.Core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0)) - { - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == user) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User")); - } - - var permission = this.Core.CreateIntegerFromBitArray(bits); - - if (Int32.MinValue == permission) // just GENERIC_READ, which is MSI_NULL - { - this.Core.Write(ErrorMessages.GenericReadNotAllowed(sourceLineNumbers)); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new LockPermissionsSymbol(sourceLineNumbers) - { - LockObject = objectId, - Table = tableName, - Domain = domain, - User = user, - Permission = permission - }); - } - } - - /// - /// Parses an extended permission element. - /// - /// Element to parse. - /// Identifier of object to be secured. - /// Name of table that contains objectId. - private void ParsePermissionExElement(XElement node, string objectId, string tableName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string condition = null; - Identifier id = null; - string sddl = null; - - switch (tableName) - { - case "CreateFolder": - case "File": - case "Registry": - case "ServiceInstall": - break; - default: - this.Core.UnexpectedElement(node.Parent, node); - return; // stop processing this element since nothing will be valid. - } - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Sddl": - sddl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.VerifyNoInnerText(sourceLineNumbers, node); - - if (null == sddl) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl")); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("pme", objectId, tableName, sddl); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiLockPermissionsExSymbol(sourceLineNumbers, id) - { - LockObject = objectId, - Table = tableName, - SDDLText = sddl, - Condition = condition - }); - } - } - - /// - /// Parses a progid element - /// - /// Element to parse. - /// Identifier of parent component. - /// Flag if progid is advertised. - /// CLSID related to ProgId. - /// Default description of ProgId - /// Optional parent ProgId - /// Set to true if an extension is found; used for error-checking. - /// Whether or not this ProgId is the first one found in the parent class. - /// This element's Id. - private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string icon = null; - var iconIndex = CompilerConstants.IntegerNotSet; - string noOpen = null; - string progId = null; - var progIdAdvertise = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - progId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Advertise": - progIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Icon": - icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "IconIndex": - iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); - break; - case "NoOpen": - noOpen = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise)) - { - this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString())); - } - else if (YesNoType.NotSet != progIdAdvertise) - { - advertise = progIdAdvertise; - } - - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex)) - { - this.Core.Write(ErrorMessages.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers)); - } - - var firstProgIdForNestedClass = YesNoType.Yes; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Extension": - this.ParseExtensionElement(child, componentId, advertise, progId); - foundExtension = true; - break; - case "ProgId": - // Only allow one nested ProgId. If we have a child, we should not have a parent. - if (null == parent) - { - if (YesNoType.Yes == advertise) - { - this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass); - } - else if (YesNoType.No == advertise) - { - this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass); - } - - firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first. - } - else - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.ProgIdNestedTooDeep(childSourceLineNumbers)); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (YesNoType.Yes == advertise) - { - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ProgIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, progId)) - { - ProgId = progId, - ParentProgIdRef = parent, - ClassRef = classId, - Description = description, - }); - - if (null != icon) - { - symbol.IconRef = icon; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); - } - - if (CompilerConstants.IntegerNotSet != iconIndex) - { - symbol.IconIndex = iconIndex; - } - - this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Class); - } - } - else if (YesNoType.No == advertise) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, String.Empty, description, componentId); - if (null != classId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId); - if (null != parent) // if this is a version independent ProgId - { - if (YesNoType.Yes == firstProgIdForClass) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId); - } - else - { - if (YesNoType.Yes == firstProgIdForClass) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId); - } - } - } - - if (null != icon) // ProgId's Default Icon - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); - - icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); - - if (CompilerConstants.IntegerNotSet != iconIndex) - { - icon = String.Concat(icon, ",", iconIndex); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId); - } - } - - if (null != noOpen) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name - } - - // raise an error for an orphaned ProgId - if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId) - { - this.Core.Write(WarningMessages.OrphanedProgId(sourceLineNumbers, progId)); - } - - return progId; - } - - /// - /// Parses a property element. - /// - /// Element to parse. - private void ParsePropertyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var admin = false; - var complianceCheck = false; - var hidden = false; - var secure = false; - var suppressModularization = YesNoType.NotSet; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Admin": - admin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ComplianceCheck": - complianceCheck = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Hidden": - hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Secure": - secure = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SuppressModularization": - suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if ("ProductID" == id.Id) - { - this.Core.Write(WarningMessages.ProductIdAuthored(sourceLineNumbers)); - } - else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id) - { - this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); - } - - this.Core.VerifyNoInnerText(sourceLineNumbers, node); - - if ("ErrorDialog" == id.Id) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, value); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - { - switch (child.Name.LocalName) - { - case "ProductSearch": - this.ParseProductSearchElement(child, id.Id); - secure = true; - break; - default: - // let ParseSearchSignatures handle standard AppSearch children and unknown elements - break; - } - } - } - } - - // see if this property is used for appSearch - var signatures = this.ParseSearchSignatures(node); - - // If we're doing CCP then there must be a signature. - if (complianceCheck && 0 == signatures.Count) - { - this.Core.Write(ErrorMessages.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes")); - } - - foreach (var sig in signatures) - { - if (complianceCheck && !this.Core.EncounteredError) - { - this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Private, sig))); - } - - this.AddAppSearch(sourceLineNumbers, id, sig); - } - - // If we're doing AppSearch get that setup. - if (0 < signatures.Count) - { - this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); - } - else // just a normal old property. - { - // If the property value is empty and none of the flags are set, print out a warning that we're ignoring - // the element. - if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden) - { - this.Core.Write(WarningMessages.PropertyUseless(sourceLineNumbers, id.Id)); - } - else // there is a value and/or a flag set, do that. - { - this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); - } - } - - if (!this.Core.EncounteredError && YesNoType.Yes == suppressModularization) - { - this.Core.Write(WarningMessages.PropertyModularizationSuppressed(sourceLineNumbers)); - - this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers, id)); - } - } - - /// - /// Parses a RegistryKey element. - /// - /// Element to parse. - /// Identifier for parent component. - /// Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet. - /// Parent key for this Registry element when nested. - /// true if the component is 64-bit. - /// Identifier of this registry key since it could be the component's keypath. - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseRegistryKeyElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var key = parentKey; // default to parent key path - var forceCreateOnInstall = false; - var forceDeleteOnUninstall = false; - var keyPath = YesNoType.NotSet; - - possibleKeyPath = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - this.Core.Write(WarningMessages.DeprecatedRegistryKeyActionAttribute(sourceLineNumbers)); - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "create": - forceCreateOnInstall = true; - break; - case "createAndRemoveOnUninstall": - forceCreateOnInstall = true; - forceDeleteOnUninstall = true; - break; - case "none": - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "create", "createAndRemoveOnUninstall", "none")); - break; - } - break; - case "ForceCreateOnInstall": - forceCreateOnInstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ForceDeleteOnUninstall": - forceDeleteOnUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (null != parentKey) - { - key = Path.Combine(parentKey, key); - } - key = key?.TrimEnd('\\'); - break; - case "Root": - if (root.HasValue) - { - this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); - } - - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - var name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null); - - if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present - { - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); - } - } - else // does not generate a Registry row, so no Id should be present - { - if (null != id) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true)); - } - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - key = String.Empty; // set the key to something to prevent null reference exceptions - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - string possibleChildKeyPath = null; - - switch (child.Name.LocalName) - { - case "RegistryKey": - if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) - { - if (YesNoType.Yes == keyPath) - { - this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); - } - - possibleKeyPath = possibleChildKeyPath; // the child is the key path - keyPath = YesNoType.Yes; - } - else if (null == possibleKeyPath && null != possibleChildKeyPath) - { - possibleKeyPath = possibleChildKeyPath; - } - break; - case "RegistryValue": - if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) - { - if (YesNoType.Yes == keyPath) - { - this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); - } - - possibleKeyPath = possibleChildKeyPath; // the child is the key path - keyPath = YesNoType.Yes; - } - else if (null == possibleKeyPath && null != possibleChildKeyPath) - { - possibleKeyPath = possibleChildKeyPath; - } - break; - case "Permission": - if (!forceCreateOnInstall) - { - this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); - } - this.ParsePermissionElement(child, id.Id, "Registry"); - break; - case "PermissionEx": - if (!forceCreateOnInstall) - { - this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); - } - this.ParsePermissionExElement(child, id.Id, "Registry"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (!this.Core.EncounteredError && null != name) - { - this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - ComponentRef = componentId, - }); - } - - return keyPath; - } - - /// - /// Parses a RegistryValue element. - /// - /// Element to parse. - /// Identifier for parent component. - /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. - /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. - /// true if the component is 64-bit. - /// Identifier of this registry key since it could be the component's keypath. - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseRegistryValueElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var key = parentKey; // default to parent key path - string name = null; - string value = null; - string action = null; - var valueType = RegistryValueType.String; - var actionType = RegistryValueActionType.Write; - var keyPath = YesNoType.NotSet; - - possibleKeyPath = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "append": - actionType = RegistryValueActionType.Append; - break; - case "prepend": - actionType = RegistryValueActionType.Prepend; - break; - case "write": - actionType = RegistryValueActionType.Write; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "append", "prepend", "write")); - break; - } - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (null != parentKey) - { - if (parentKey.EndsWith("\\", StringComparison.Ordinal)) - { - key = String.Concat(parentKey, key); - } - else - { - key = String.Concat(parentKey, "\\", key); - } - } - break; - case "KeyPath": - keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - if (root.HasValue) - { - this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); - } - - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "binary": - valueType = RegistryValueType.Binary; - break; - case "expandable": - valueType = RegistryValueType.Expandable; - break; - case "integer": - valueType = RegistryValueType.Integer; - break; - case "multiString": - valueType = RegistryValueType.MultiString; - break; - case "string": - valueType = RegistryValueType.String; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "binary", "expandable", "integer", "multiString", "string")); - break; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)(root ?? RegistryRootType.Unknown)).ToString(), LowercaseOrNull(key), LowercaseOrNull(name)); - } - - if (RegistryValueType.MultiString != valueType && (RegistryValueActionType.Append == actionType || RegistryValueActionType.Prepend == actionType)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "MultiString": - case "MultiStringValue": - if (RegistryValueType.MultiString != valueType && null != value) - { - this.Core.Write(ErrorMessages.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type")); - } - else - { - value = this.ParseRegistryMultiStringElement(child, value); - } - break; - case "Permission": - this.ParsePermissionElement(child, id.Id, "Registry"); - break; - case "PermissionEx": - this.ParsePermissionExElement(child, id.Id, "Registry"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - //switch (typeType) - //{ - //case Wix.RegistryValue.TypeType.binary: - // value = String.Concat("#x", value); - // break; - //case Wix.RegistryValue.TypeType.expandable: - // value = String.Concat("#%", value); - // break; - //case Wix.RegistryValue.TypeType.integer: - // value = String.Concat("#", value); - // break; - //case Wix.RegistryValue.TypeType.multiString: - // switch (actionType) - // { - // case Wix.RegistryValue.ActionType.append: - // value = String.Concat("[~]", value); - // break; - // case Wix.RegistryValue.ActionType.prepend: - // value = String.Concat(value, "[~]"); - // break; - // case Wix.RegistryValue.ActionType.write: - // default: - // if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) - // { - // value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); - // } - // break; - // } - // break; - //case Wix.RegistryValue.TypeType.@string: - // // escape the leading '#' character for string registry keys - // if (null != value && value.StartsWith("#", StringComparison.Ordinal)) - // { - // value = String.Concat("#", value); - // } - // break; - //} - - // value may be set by child MultiStringValue elements, so it must be checked here - if (null == value && valueType != RegistryValueType.Binary) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - else if (0 == value?.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values - { - this.Core.Write(ErrorMessages.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name)); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - Value = value, - ValueType = valueType, - ValueAction = actionType, - ComponentRef = componentId, - }); - } - - // If this was just a regular registry key (that could be the key path) - // and no child registry key set the possible key path, let's make this - // Registry/@Id a possible key path. - if (null == possibleKeyPath) - { - possibleKeyPath = id.Id; - } - - return keyPath; - } - - private string ParseRegistryMultiStringElement(XElement node, string value) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string multiStringValue = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Value": - multiStringValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - this.Core.VerifyNoInnerText(sourceLineNumbers, node); - - this.Core.ParseForExtensionElements(node); - - return null == value ? multiStringValue ?? "[~]" : String.Concat(value, "[~]", multiStringValue); - } - - /// - /// Parses a RemoveRegistryKey element. - /// - /// The element to parse. - /// The component identifier of the parent element. - private void ParseRemoveRegistryKeyElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - RemoveRegistryActionType? actionType = null; - string key = null; - var name = "-"; - RegistryRootType? root = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "removeOnInstall": - actionType = RemoveRegistryActionType.RemoveOnInstall; - break; - case "removeOnUninstall": - actionType = RemoveRegistryActionType.RemoveOnUninstall; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "removeOnInstall", "removeOnUninstall")); - break; - } - //if (0 < action.Length) - //{ - // if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType)) - // { - // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall")); - // } - //} - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (!actionType.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - Action = actionType.Value, - ComponentRef = componentId, - }); - } - } - - /// - /// Parses a RemoveRegistryValue element. - /// - /// The element to parse. - /// The component identifier of the parent element. - private void ParseRemoveRegistryValueElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string name = null; - RegistryRootType? root = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - ComponentRef = componentId - }); - } - } - - /// - /// Parses a remove file element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of the parent component's directory. - private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string directory = null; - string name = null; - bool? onInstall = null; - bool? onUninstall = null; - string property = null; - string shortName = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, true); - break; - case "On": - var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (onValue) - { - case "install": - onInstall = true; - break; - case "uninstall": - onUninstall = true; - break; - case "both": - onInstall = true; - onUninstall = true; - break; - } - break; - case "Property": - property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (!onInstall.HasValue && !onUninstall.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); - } - - if (null != directory && null != property) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory)); - } - - if (null == id) - { - var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; - id = this.Core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - FileName = name, - ShortFileName = shortName, - DirPropertyRef = directory ?? property ?? parentDirectory, - OnInstall = onInstall, - OnUninstall = onUninstall, - }); - } - } - - /// - /// Parses a RemoveFolder element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of parent component's directory. - private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string directory = null; - bool? onInstall = null; - bool? onUninstall = null; - string property = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory); - break; - case "On": - var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (onValue) - { - case "install": - onInstall = true; - break; - case "uninstall": - onUninstall = true; - break; - case "both": - onInstall = true; - onUninstall = true; - break; - } - break; - case "Property": - property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (!onInstall.HasValue && !onUninstall.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); - } - - if (null != directory && null != property) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory)); - } - - if (null == id) - { - var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; - id = this.Core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, on.ToString()); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - DirPropertyRef = directory ?? property ?? parentDirectory, - OnInstall = onInstall, - OnUninstall = onUninstall - }); - } - } - - /// - /// Parses a reserve cost element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional and default identifier of referenced directory. - private void ParseReserveCostElement(XElement node, string componentId, string directoryId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var runFromSource = CompilerConstants.IntegerNotSet; - var runLocal = CompilerConstants.IntegerNotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, directoryId); - break; - case "RunFromSource": - runFromSource = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "RunLocal": - runLocal = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("rc", componentId, directoryId); - } - - if (CompilerConstants.IntegerNotSet == runFromSource) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource")); - } - - if (CompilerConstants.IntegerNotSet == runLocal) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ReserveCostSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - ReserveFolder = directoryId, - ReserveLocal = runLocal, - ReserveSource = runFromSource - }); - } - } - - /// - /// Parses a sequence element. - /// - /// Element to parse. - /// Name of sequence table. - private void ParseSequenceElement(XElement node, SequenceTable sequenceTable) - { - // Parse each action in the sequence. - foreach (var child in node.Elements()) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - var actionName = child.Name.LocalName; - string afterAction = null; - string beforeAction = null; - string condition = null; - var customAction = "Custom" == actionName; - var overridable = false; - var exitSequence = CompilerConstants.IntegerNotSet; - var sequence = CompilerConstants.IntegerNotSet; - var showDialog = "Show" == actionName; - var specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName; - var specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName; - var suppress = false; - - foreach (var attrib in child.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - if (customAction) - { - actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.CustomAction, actionName); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "After": - if (customAction || showDialog || specialAction || specialStandardAction) - { - afterAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), afterAction); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "Before": - if (customAction || showDialog || specialAction || specialStandardAction) - { - beforeAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), beforeAction); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "Condition": - condition = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - case "Dialog": - if (showDialog) - { - actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.Dialog, actionName); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "OnExit": - if (customAction || showDialog || specialAction) - { - var exitValue = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - switch (exitValue) - { - case "success": - exitSequence = -1; - break; - case "cancel": - exitSequence = -2; - break; - case "error": - exitSequence = -3; - break; - case "suspend": - exitSequence = -4; - break; - } - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "Overridable": - overridable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); - break; - case "Sequence": - sequence = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "Suppress": - suppress = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.VerifyNoInnerText(childSourceLineNumbers, node); - - if (customAction && "Custom" == actionName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); - } - else if (showDialog && "Show" == actionName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog")); - } - - if (CompilerConstants.IntegerNotSet != sequence) - { - if (CompilerConstants.IntegerNotSet != exitSequence) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit")); - } - else if (null != beforeAction || null != afterAction) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After")); - } - } - else // sequence not specified use OnExit (which may also be not set). - { - sequence = exitSequence; - } - - if (null != beforeAction && null != afterAction) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before")); - } - else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction) - { - this.Core.Write(ErrorMessages.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName)); - } - - // action that is scheduled to occur before/after itself - if (beforeAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction)); - } - else if (afterAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction)); - } - - // normal standard actions cannot be set overridable by the user (since they are overridable by default) - if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction) - { - this.Core.Write(ErrorMessages.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable")); - } - - // suppress cannot be specified at the same time as Before, After, or Sequence - if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable")); - } - - this.Core.ParseForExtensionElements(child); - - // add the row and any references needed - if (!this.Core.EncounteredError) - { - if (suppress) - { - this.Core.AddSymbol(new WixSuppressActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Public, sequenceTable, actionName)) - { - SequenceTable = sequenceTable, - Action = actionName - }); - } - else - { - var symbol = this.Core.AddSymbol(new WixActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Public, sequenceTable, actionName)) - { - SequenceTable = sequenceTable, - Action = actionName, - Condition = condition, - Before = beforeAction, - After = afterAction, - Overridable = overridable, - }); - - if (CompilerConstants.IntegerNotSet != sequence) - { - symbol.Sequence = sequence; - } - } - } - } - } - - - /// - /// Parses a service config element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional element containing parent's service name. - private void ParseServiceConfigElement(XElement node, string componentId, string serviceName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string delayedAutoStart = null; - string failureActionsWhen = null; - var name = serviceName; - var install = false; - var reinstall = false; - var uninstall = false; - string preShutdownDelay = null; - string requiredPrivileges = null; - string sid = null; - - this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "DelayedAutoStart": - delayedAutoStart = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (delayedAutoStart) - { - case "no": - delayedAutoStart = "0"; - break; - case "yes": - delayedAutoStart = "1"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - case "FailureActionsWhen": - failureActionsWhen = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (failureActionsWhen) - { - case "failedToStop": - failureActionsWhen = "0"; - break; - case "failedToStopOrReturnedError": - failureActionsWhen = "1"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - case "OnInstall": - install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == install) - //{ - // events |= MsiInterop.MsidbServiceConfigEventInstall; - //} - break; - case "OnReinstall": - reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == reinstall) - //{ - // events |= MsiInterop.MsidbServiceConfigEventReinstall; - //} - break; - case "OnUninstall": - uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == uninstall) - //{ - // events |= MsiInterop.MsidbServiceConfigEventUninstall; - //} - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - case "PreShutdownDelay": - preShutdownDelay = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "ServiceName": - if (!String.IsNullOrEmpty(serviceName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); - } - - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ServiceSid": - sid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (sid) - { - case "none": - sid = "0"; - break; - case "restricted": - sid = "3"; - break; - case "unrestricted": - sid = "1"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // Get the ServiceConfig required privilegs. - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "RequiredPrivilege": - requiredPrivileges = this.ParseRequiredPrivilege(child, requiredPrivileges); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); - } - else if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (!install && !reinstall && !uninstall) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); - } - - if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid)) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege")); - } - - if (!this.Core.EncounteredError) - { - if (!String.IsNullOrEmpty(delayedAutoStart)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".DS"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.DelayedAutoStart, - Argument = delayedAutoStart, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(failureActionsWhen)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".FA"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.FailureActionsFlag, - Argument = failureActionsWhen, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(sid)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".SS"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.ServiceSidInfo, - Argument = sid, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(requiredPrivileges)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".RP"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.RequiredPrivilegesInfo, - Argument = requiredPrivileges, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(preShutdownDelay)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".PD"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.PreshutdownInfo, - Argument = preShutdownDelay, - ComponentRef = componentId, - }); - } - } - } - - private string ParseRequiredPrivilege(XElement node, string requiredPrivileges) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string privilege = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - privilege = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (privilege) - { - case "assignPrimaryToken": - privilege = "SeAssignPrimaryTokenPrivilege"; - break; - case "audit": - privilege = "SeAuditPrivilege"; - break; - case "backup": - privilege = "SeBackupPrivilege"; - break; - case "changeNotify": - privilege = "SeChangeNotifyPrivilege"; - break; - case "createGlobal": - privilege = "SeCreateGlobalPrivilege"; - break; - case "createPagefile": - privilege = "SeCreatePagefilePrivilege"; - break; - case "createPermanent": - privilege = "SeCreatePermanentPrivilege"; - break; - case "createSymbolicLink": - privilege = "SeCreateSymbolicLinkPrivilege"; - break; - case "createToken": - privilege = "SeCreateTokenPrivilege"; - break; - case "debug": - privilege = "SeDebugPrivilege"; - break; - case "enableDelegation": - privilege = "SeEnableDelegationPrivilege"; - break; - case "impersonate": - privilege = "SeImpersonatePrivilege"; - break; - case "increaseBasePriority": - privilege = "SeIncreaseBasePriorityPrivilege"; - break; - case "increaseQuota": - privilege = "SeIncreaseQuotaPrivilege"; - break; - case "increaseWorkingSet": - privilege = "SeIncreaseWorkingSetPrivilege"; - break; - case "loadDriver": - privilege = "SeLoadDriverPrivilege"; - break; - case "lockMemory": - privilege = "SeLockMemoryPrivilege"; - break; - case "machineAccount": - privilege = "SeMachineAccountPrivilege"; - break; - case "manageVolume": - privilege = "SeManageVolumePrivilege"; - break; - case "profileSingleProcess": - privilege = "SeProfileSingleProcessPrivilege"; - break; - case "relabel": - privilege = "SeRelabelPrivilege"; - break; - case "remoteShutdown": - privilege = "SeRemoteShutdownPrivilege"; - break; - case "restore": - privilege = "SeRestorePrivilege"; - break; - case "security": - privilege = "SeSecurityPrivilege"; - break; - case "shutdown": - privilege = "SeShutdownPrivilege"; - break; - case "syncAgent": - privilege = "SeSyncAgentPrivilege"; - break; - case "systemEnvironment": - privilege = "SeSystemEnvironmentPrivilege"; - break; - case "systemProfile": - privilege = "SeSystemProfilePrivilege"; - break; - case "systemTime": - case "modifySystemTime": - privilege = "SeSystemtimePrivilege"; - break; - case "takeOwnership": - privilege = "SeTakeOwnershipPrivilege"; - break; - case "tcb": - case "trustedComputerBase": - privilege = "SeTcbPrivilege"; - break; - case "timeZone": - case "modifyTimeZone": - privilege = "SeTimeZonePrivilege"; - break; - case "trustedCredManAccess": - case "trustedCredentialManagerAccess": - privilege = "SeTrustedCredManAccessPrivilege"; - break; - case "undock": - privilege = "SeUndockPrivilege"; - break; - case "unsolicitedInput": - privilege = "SeUnsolicitedInputPrivilege"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - this.Core.VerifyNoInnerText(sourceLineNumbers, node); - - if (privilege == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - this.Core.ParseForExtensionElements(node); - - return (requiredPrivileges == null) ? privilege : String.Concat(requiredPrivileges, "[~]", privilege); - } - - /// - /// Parses a service config failure actions element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional element containing parent's service name. - private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var name = serviceName; - var install = false; - var reinstall = false; - var uninstall = false; - int? resetPeriod = null; - string rebootMessage = null; - string command = null; - string actions = null; - string actionsDelays = null; - - this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Command": - command = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "OnInstall": - install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "OnReinstall": - reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "OnUninstall": - uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RebootMessage": - rebootMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "ResetPeriod": - resetPeriod = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "ServiceName": - if (!String.IsNullOrEmpty(serviceName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); - } - - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // Get the ServiceConfigFailureActions actions. - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Failure": - string action = null; - string delay = null; - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - - foreach (var childAttrib in child.Attributes()) - { - if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace) - { - switch (childAttrib.Name.LocalName) - { - case "Action": - action = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (action) - { - case "none": - action = "0"; - break; - case "restartComputer": - action = "2"; - break; - case "restartService": - action = "1"; - break; - case "runCommand": - action = "3"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - case "Delay": - delay = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - break; - default: - this.Core.UnexpectedAttribute(child, childAttrib); - break; - } - } - } - - if (String.IsNullOrEmpty(action)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action")); - } - - if (String.IsNullOrEmpty(delay)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay")); - } - - if (!String.IsNullOrEmpty(actions)) - { - actions = String.Concat(actions, "[~]"); - } - actions = String.Concat(actions, action); - - if (!String.IsNullOrEmpty(actionsDelays)) - { - actionsDelays = String.Concat(actionsDelays, "[~]"); - } - actionsDelays = String.Concat(actionsDelays, delay); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); - } - else if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (!install && !reinstall && !uninstall) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiServiceConfigFailureActionsSymbol(sourceLineNumbers, id) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ResetPeriod = resetPeriod, - RebootMessage = rebootMessage, - Command = command, - Actions = actions, - DelayActions = actionsDelays, - ComponentRef = componentId, - }); - } - } - - /// - /// Parses a service control element. - /// - /// Element to parse. - /// Identifier of parent component. - private void ParseServiceControlElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string arguments = null; - Identifier id = null; - string name = null; - var installRemove = false; - var uninstallRemove = false; - var installStart = false; - var uninstallStart = false; - var installStop = false; - var uninstallStop = false; - bool? wait = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Remove": - var removeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (removeValue) - { - case "install": - installRemove = true; - break; - case "uninstall": - uninstallRemove = true; - break; - case "both": - installRemove = true; - uninstallRemove = true; - break; - case "": - break; - } - break; - case "Start": - var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (startValue) - { - case "install": - installStart = true; - break; - case "uninstall": - uninstallStart = true; - break; - case "both": - installStart = true; - uninstallStart = true; - break; - case "": - break; - } - break; - case "Stop": - var stopValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (stopValue) - { - case "install": - installStop = true; - break; - case "uninstall": - uninstallStop = true; - break; - case "both": - installStop = true; - uninstallStop = true; - break; - case "": - break; - } - break; - case "Wait": - wait = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - // get the ServiceControl arguments - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ServiceArgument": - arguments = this.ParseServiceArgument(child, arguments); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ServiceControlSymbol(sourceLineNumbers, id) - { - Name = name, - InstallRemove = installRemove, - UninstallRemove = uninstallRemove, - InstallStart = installStart, - UninstallStart = uninstallStart, - InstallStop = installStop, - UninstallStop = uninstallStop, - Arguments = arguments, - Wait = wait, - ComponentRef = componentId - }); - } - } - - private string ParseServiceArgument(XElement node, string arguments) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string argument = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Value": - argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - this.Core.VerifyNoInnerText(sourceLineNumbers, node); - - if (argument == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - return (arguments == null) ? argument : String.Concat(arguments, "[~]", argument); - } - - /// - /// Parses a service dependency element. - /// - /// Element to parse. - /// Parsed sevice dependency name. - private string ParseServiceDependencyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string dependency = null; - var group = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Group": - group = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == dependency) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return group ? String.Concat("+", dependency) : dependency; - } - - /// - /// Parses a service install element. - /// - /// Element to parse. - /// Identifier of parent component. - /// - private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string account = null; - string arguments = null; - string dependencies = null; - string description = null; - string displayName = null; - var eraseDescription = false; - string loadOrderGroup = null; - string name = null; - string password = null; - - var serviceType = ServiceType.OwnProcess; - var startType = ServiceStartType.Demand; - var errorControl = ServiceErrorControl.Normal; - var interactive = false; - var vital = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Account": - account = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Arguments": - arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "EraseDescription": - eraseDescription = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ErrorControl": - var errorControlValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (errorControlValue) - { - case "ignore": - errorControl = ServiceErrorControl.Ignore; - break; - case "normal": - errorControl = ServiceErrorControl.Normal; - break; - case "critical": - errorControl = ServiceErrorControl.Critical; - break; - case "": // error case handled by GetAttributeValue() - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical")); - break; - } - break; - case "Interactive": - interactive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "LoadOrderGroup": - loadOrderGroup = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Password": - password = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Start": - var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (startValue) - { - case "auto": - startType = ServiceStartType.Auto; - break; - case "demand": - startType = ServiceStartType.Demand; - break; - case "disabled": - startType = ServiceStartType.Disabled; - break; - case "boot": - case "system": - this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue)); - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled")); - break; - } - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "ownProcess": - serviceType = ServiceType.OwnProcess; - break; - case "shareProcess": - serviceType = ServiceType.ShareProcess; - break; - case "kernelDriver": - case "systemDriver": - this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue)); - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess")); - break; - } - break; - case "Vital": - vital = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (0 == startType) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start")); - } - - if (eraseDescription) - { - description = "[~]"; - } - - // get the ServiceInstall dependencies and config - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "PermissionEx": - this.ParsePermissionExElement(child, id.Id, "ServiceInstall"); - break; - case "ServiceConfig": - this.ParseServiceConfigElement(child, componentId, name); - break; - case "ServiceConfigFailureActions": - this.ParseServiceConfigFailureActionsElement(child, componentId, name); - break; - case "ServiceDependency": - dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "ServiceInstallId", id?.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (null != dependencies) - { - dependencies = String.Concat(dependencies, "[~]"); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ServiceInstallSymbol(sourceLineNumbers, id) - { - Name = name, - DisplayName = displayName, - ServiceType = serviceType, - StartType = startType, - ErrorControl = errorControl, - LoadOrderGroup = loadOrderGroup, - Dependencies = dependencies, - StartName = account, - Password = password, - Arguments = arguments, - ComponentRef = componentId, - Description = description, - Interactive = interactive, - Vital = vital - }); - } - } - - /// - /// Parses a SetDirectory element. - /// - /// Element to parse. - private void ParseSetDirectoryElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string actionName = null; - string id = null; - string condition = null; - var executionType = CustomActionExecutionType.Immediate; - var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); - break; - case "Sequence": - var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (sequenceValue) - { - case "execute": - sequences = new[] { SequenceTable.InstallExecuteSequence }; - break; - case "first": - executionType = CustomActionExecutionType.FirstSequence; - break; - case "ui": - sequences = new[] { SequenceTable.InstallUISequence }; - break; - case "both": - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); - break; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.VerifyNoInnerText(sourceLineNumbers, node); - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (String.IsNullOrEmpty(actionName)) - { - actionName = String.Concat("Set", id); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, actionName)) - { - ExecutionType = executionType, - SourceType = CustomActionSourceType.Directory, - TargetType = CustomActionTargetType.TextData, - Source = id, - Target = value - }); - - foreach (var sequence in sequences) - { - this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Public, sequence, actionName, condition, afterAction: "CostInitialize"); - } - } - } - - /// - /// Parses a SetProperty element. - /// - /// Element to parse. - private void ParseSetPropertyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string actionName = null; - string id = null; - string condition = null; - string afterAction = null; - string beforeAction = null; - var executionType = CustomActionExecutionType.Immediate; - var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "After": - afterAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Before": - beforeAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Sequence": - var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (sequenceValue) - { - case "execute": - sequences = new[] { SequenceTable.InstallExecuteSequence }; - break; - case "first": - executionType = CustomActionExecutionType.FirstSequence; - break; - case "ui": - sequences = new[] { SequenceTable.InstallUISequence }; - break; - case "both": - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); - break; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.VerifyNoInnerText(sourceLineNumbers, node); - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (String.IsNullOrEmpty(actionName)) - { - actionName = String.Concat("Set", id); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - if (null != beforeAction && null != afterAction) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before")); - } - else if (null == beforeAction && null == afterAction) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id")); - } - - this.Core.ParseForExtensionElements(node); - - // add the row and any references needed - if (!this.Core.EncounteredError) - { - // action that is scheduled to occur before/after itself - if (beforeAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction)); - } - else if (afterAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction)); - } - - this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, actionName)) - { - ExecutionType = executionType, - SourceType = CustomActionSourceType.Property, - TargetType = CustomActionTargetType.TextData, - Source = id, - Target = value, - }); - - foreach (var sequence in sequences) - { - this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Public, sequence, actionName, condition, beforeAction, afterAction); - } - } - } - - /// - /// Parses a SFP catalog element. - /// - /// Element to parse. - /// Parent SFPCatalog. - private void ParseSFPFileElement(XElement node, string parentSFPCatalog) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new FileSFPCatalogSymbol(sourceLineNumbers) - { - FileRef = id, - SFPCatalogRef = parentSFPCatalog - }); - } - } - - /// - /// Parses a SFP catalog element. - /// - /// Element to parse. - /// Parent SFPCatalog. - private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string parentName = null; - string dependency = null; - string name = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Dependency": - dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - parentSFPCatalog = name; - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "SFPCatalog": - this.ParseSFPCatalogElement(child, ref parentName); - if (null != dependency && parentName == dependency) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); - } - dependency = parentName; - break; - case "SFPFile": - this.ParseSFPFileElement(child, name); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null == dependency) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new SFPCatalogSymbol(sourceLineNumbers) - { - SFPCatalog = name, - Catalog = sourceFile, - Dependency = dependency - }); - } - } - - /// - /// Parses a shortcut element. - /// - /// Element to parse. - /// Identifer for parent component. - /// Local name of parent element. - /// Default identifier of parent (which is usually the target). - /// Flag to indicate whether the parent element is the keypath of a component or not (will only be true for file parent elements). - private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var advertise = false; - string arguments = null; - string description = null; - string descriptionResourceDll = null; - int? descriptionResourceId = null; - string directory = null; - string displayResourceDll = null; - int? displayResourceId = null; - int? hotkey = null; - string icon = null; - int? iconIndex = null; - string name = null; - string shortName = null; - ShortcutShowType? show = null; - string target = null; - string workingDirectory = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Advertise": - advertise = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Arguments": - arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DescriptionResourceDll": - descriptionResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DescriptionResourceId": - descriptionResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Directory": - directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); - break; - case "DisplayResourceDll": - displayResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayResourceId": - displayResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Hotkey": - hotkey = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Icon": - icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); - break; - case "IconIndex": - iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "Show": - var showValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (showValue) - { - case "normal": - show = ShortcutShowType.Normal; - break; - case "maximized": - show = ShortcutShowType.Maximized; - break; - case "minimized": - show = ShortcutShowType.Minimized; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); - break; - } - break; - case "Target": - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "WorkingDirectory": - workingDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (advertise && null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes")); - } - - if (null == directory) - { - if ("Component" == parentElementLocalName) - { - directory = defaultTarget; - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component")); - } - } - - if (null != descriptionResourceDll) - { - if (!descriptionResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId")); - } - } - else - { - if (descriptionResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll")); - } - } - - if (null != displayResourceDll) - { - if (!displayResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId")); - } - } - else - { - if (displayResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll")); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if ("Component" != parentElementLocalName && null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName)); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("sct", directory, LowercaseOrNull(name)); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Icon": - icon = this.ParseIconElement(child); - break; - case "ShortcutProperty": - this.ParseShortcutPropertyElement(child, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - if (advertise) - { - if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName) - { - this.Core.Write(WarningMessages.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget)); - } - - target = Guid.Empty.ToString("B"); - } - else if (null != target) - { - } - else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) - { - target = "[" + defaultTarget + "]"; - } - else if ("File" == parentElementLocalName) - { - target = "[#" + defaultTarget + "]"; - } - - this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) - { - DirectoryRef = directory, - Name = name, - ShortName = shortName, - ComponentRef = componentId, - Target = target, - Arguments = arguments, - Description = description, - Hotkey = hotkey, - IconRef = icon, - IconIndex = iconIndex, - Show = show, - WorkingDirectory = workingDirectory, - DisplayResourceDll = displayResourceDll, - DisplayResourceId = displayResourceId, - DescriptionResourceDll = descriptionResourceDll, - DescriptionResourceId = descriptionResourceId, - }); - } - } - - /// - /// Parses a shortcut property element. - /// - /// Element to parse. - /// - private void ParseShortcutPropertyElement(XElement node, string shortcutId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.VerifyNoInnerText(sourceLineNumbers, node); - - if (String.IsNullOrEmpty(key)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - else if (null == id) - { - id = this.Core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant()); - } - - if (String.IsNullOrEmpty(value)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiShortcutPropertySymbol(sourceLineNumbers, id) - { - ShortcutRef = shortcutId, - PropertyKey = key, - PropVariantValue = value - }); - } - } - - /// - /// Parses a typelib element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of file that acts as typelib server. - /// true if the component is 64-bit. - private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var advertise = YesNoType.NotSet; - var cost = CompilerConstants.IntegerNotSet; - string description = null; - var flags = 0; - string helpDirectory = null; - var language = CompilerConstants.IntegerNotSet; - var majorVersion = CompilerConstants.IntegerNotSet; - var minorVersion = CompilerConstants.IntegerNotSet; - var resourceId = CompilerConstants.LongNotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Advertise": - advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Control": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 2; - } - break; - case "Cost": - cost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "HasDiskImage": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 8; - } - break; - case "HelpDirectory": - helpDirectory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); - break; - case "Hidden": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 4; - } - break; - case "Language": - language = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "MajorVersion": - majorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, UInt16.MaxValue); - break; - case "MinorVersion": - minorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); - break; - case "ResourceId": - resourceId = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, Int32.MinValue, Int32.MaxValue); - break; - case "Restricted": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 1; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (CompilerConstants.IntegerNotSet == language) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); - language = CompilerConstants.IllegalInteger; - } - - // build up the typelib version string for the registry if the major or minor version was specified - string registryVersion = null; - if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) - { - if (CompilerConstants.IntegerNotSet != majorVersion) - { - registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat); - } - else - { - registryVersion = "0"; - } - - if (CompilerConstants.IntegerNotSet != minorVersion) - { - registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat)); - } - else - { - registryVersion = String.Concat(registryVersion, ".0"); - } - } - - // if the advertise state has not been set, default to non-advertised - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "AppId": - this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion); - break; - case "Class": - this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null); - break; - case "Interface": - this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (YesNoType.Yes == advertise) - { - if (CompilerConstants.LongNotSet != resourceId) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId")); - } - - if (0 != flags) - { - if (0x1 == (flags & 0x1)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes")); - } - - if (0x2 == (flags & 0x2)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes")); - } - - if (0x4 == (flags & 0x4)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes")); - } - - if (0x8 == (flags & 0x8)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes")); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new TypeLibSymbol(sourceLineNumbers) - { - LibId = id, - Language = language, - ComponentRef = componentId, - Description = description, - DirectoryRef = helpDirectory, - FeatureRef = Guid.Empty.ToString("B") - }); - - if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) - { - symbol.Version = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0); - } - - if (CompilerConstants.IntegerNotSet != cost) - { - symbol.Cost = cost; - } - } - } - else if (YesNoType.No == advertise) - { - if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no")); - } - - if (null == fileServer) - { - this.Core.Write(ErrorMessages.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File")); - } - - if (null == registryVersion) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no")); - } - - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description] - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId); - - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId] - var path = String.Concat("[#", fileServer, "]"); - if (CompilerConstants.LongNotSet != resourceId) - { - path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat)); - } - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId); - - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags] - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId); - - if (null != helpDirectory) - { - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory] - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectory, "]"), componentId); - } - } - } - - /// - /// Parses an upgrade element. - /// - /// Element to parse. - private void ParseUpgradeElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - // process the UpgradeVersion children here - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - - switch (child.Name.LocalName) - { - case "Property": - this.ParsePropertyElement(child); - this.Core.Write(WarningMessages.DeprecatedUpgradeProperty(childSourceLineNumbers)); - break; - case "UpgradeVersion": - this.ParseUpgradeVersionElement(child, id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - // No rows created here. All row creation is done in ParseUpgradeVersionElement. - } - - /// - /// Parse upgrade version element. - /// - /// Element to parse. - /// Upgrade code. - private void ParseUpgradeVersionElement(XElement node, string upgradeId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - string actionProperty = null; - string language = null; - string maximum = null; - string minimum = null; - var excludeLanguages = false; - var ignoreFailures = false; - var includeMax = false; - var includeMin = true; - var migrateFeatures = false; - var onlyDetect = false; - string removeFeatures = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ExcludeLanguages": - excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IgnoreRemoveFailure": - ignoreFailures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IncludeMaximum": - includeMax = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IncludeMinimum": // this is "yes" by default - includeMin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Language": - language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Minimum": - minimum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "Maximum": - maximum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "MigrateFeatures": - migrateFeatures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "OnlyDetect": - onlyDetect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Property": - actionProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "RemoveFeatures": - removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == actionProperty) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty) - { - this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty)); - } - - if (null == minimum && null == maximum) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeId, - VersionMin = minimum, - VersionMax = maximum, - Language = language, - ExcludeLanguages = excludeLanguages, - IgnoreRemoveFailures = ignoreFailures, - VersionMaxInclusive = includeMax, - VersionMinInclusive = includeMin, - MigrateFeatures = migrateFeatures, - OnlyDetect = onlyDetect, - Remove = removeFeatures, - ActionProperty = actionProperty - }); - - // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence - // if at least one row in Upgrade table lacks the OnlyDetect attribute. - if (!onlyDetect) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", "RemoveExistingProducts"); - } - } - } - - /// - /// Parses a verb element. - /// - /// Element to parse. - /// Extension verb is releated to. - /// Optional progId for extension. - /// Identifier for parent component. - /// Flag if verb is advertised. - private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string argument = null; - string command = null; - var sequence = CompilerConstants.IntegerNotSet; - string targetFile = null; - string targetProperty = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Argument": - argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Command": - command = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Sequence": - sequence = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "TargetFile": - targetFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, targetFile); - break; - case "TargetProperty": - targetProperty = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null != targetFile && null != targetProperty) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty")); - } - - this.Core.ParseForExtensionElements(node); - - if (YesNoType.Yes == advertise) - { - if (null != targetFile) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile")); - } - - if (null != targetProperty) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty")); - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new VerbSymbol(sourceLineNumbers) - { - ExtensionRef = extension, - Verb = id, - Command = command, - Argument = argument, - }); - - if (CompilerConstants.IntegerNotSet != sequence) - { - symbol.Sequence = sequence; - } - } - } - else if (YesNoType.No == advertise) - { - if (CompilerConstants.IntegerNotSet != sequence) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no")); - } - - if (null == targetFile && null == targetProperty) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no")); - } - - string target = null; - if (null != targetFile) - { - target = String.Concat("\"[#", targetFile, "]\""); - } - else if (null != targetProperty) - { - target = String.Concat("\"[", targetProperty, "]\""); - } - - if (null != argument) - { - target = String.Concat(target, " ", argument); - } - - var prefix = progId ?? String.Concat(".", extension); - - if (null != command) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId); - } - } - - /// - /// Parses a WixVariable element. - /// - /// Element to parse. - private void ParseWixVariableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var overridable = false; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Overridable": - overridable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixVariableSymbol(sourceLineNumbers, id) - { - Value = value, - Overridable = overridable - }); - } - } - - private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XElement node, XAttribute attribute) - { - var compressionLevel = this.Core.GetAttributeValue(sourceLineNumbers, attribute); - switch (compressionLevel) - { - case "high": - return CompressionLevel.High; - case "low": - return CompressionLevel.Low; - case "medium": - return CompressionLevel.Medium; - case "mszip": - return CompressionLevel.Mszip; - case "none": - return CompressionLevel.None; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalCompressionLevel(sourceLineNumbers, compressionLevel)); - break; - } - - return null; - } - } -} diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 1ee09166..b8c7b7b1 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -196,6 +196,9 @@ namespace WixToolset.Core case "ParentName": parentName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; + case "ProviderKey": + this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib); + break; case "SplashScreenSourceFile": splashScreenSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; @@ -340,6 +343,9 @@ namespace WixToolset.Core case "RelatedBundle": this.ParseRelatedBundleElement(child); break; + case "Requires": + this.ParseRequiresElement(child, null, false); + break; case "SetVariable": this.ParseSetVariableElement(child); break; @@ -2386,6 +2392,9 @@ namespace WixToolset.Core case "PayloadGroupRef": this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); break; + case "Provides": + this.ParseProvidesElement(child, packageType, id.Id, out _); + break; case "ExitCode": allowed = (packageType == WixBundlePackageType.Exe); if (allowed) diff --git a/src/WixToolset.Core/Compiler_Dependency.cs b/src/WixToolset.Core/Compiler_Dependency.cs new file mode 100644 index 00000000..74982fba --- /dev/null +++ b/src/WixToolset.Core/Compiler_Dependency.cs @@ -0,0 +1,385 @@ +// 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 +{ + using System; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + // The root registry key for the dependency extension. We write to Software\Classes explicitly + // based on the current security context instead of HKCR. See + // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. + private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; + + private static readonly char[] InvalidDependencyCharacters = new char[] { ' ', '\"', ';', '\\' }; + + /// + /// Processes the ProviderKey bundle attribute. + /// + /// Source line number for the parent element. + /// Parent element of attribute. + /// The XML attribute for the ProviderKey attribute. + private void ParseBundleProviderKeyAttribute(SourceLineNumber sourceLineNumbers, XElement parentElement, XAttribute attribute) + { + var providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attribute); + int illegalChar; + + // Make sure the key does not contain any illegal characters or values. + if (String.IsNullOrEmpty(providerKey)) + { + this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, attribute.Name.LocalName)); + } + else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) + { + this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); + } + else if ("ALL" == providerKey) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, parentElement.Name.LocalName, "ProviderKey", providerKey)); + } + + if (!this.Messaging.EncounteredError) + { + // Generate the primary key for the row. + var id = this.Core.CreateIdentifier("dep", attribute.Name.LocalName, providerKey); + + // Create the provider symbol for the bundle. The Component_ field is required + // in the table definition but unused for bundles, so just set it to the valid ID. + this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) + { + ComponentRef = id.Id, + ProviderKey = providerKey, + Attributes = WixDependencyProviderAttributes.ProvidesAttributesBundle, + }); + } + } + + /// + /// Processes the Provides element. + /// + /// The XML node for the Provides element. + /// The type of the package being chained into a bundle, or null if building an MSI package. + /// The identifier of the parent component or package. + /// Possible KeyPath identifier. + /// Yes if this is the keypath. + private YesNoType ParseProvidesElement(XElement node, WixBundlePackageType? packageType, string parentId, out string possibleKeyPath) + { + possibleKeyPath = null; + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string version = null; + string displayName = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Version": + version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Make sure the key is valid. The key will default to the ProductCode for MSI packages + // and the package code for MSP packages in the binder if not specified. + if (!String.IsNullOrEmpty(key)) + { + int illegalChar; + + // Make sure the key does not contain any illegal characters or values. + if (0 <= (illegalChar = key.IndexOfAny(InvalidDependencyCharacters))) + { + this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "Key", key[illegalChar], String.Join(" ", InvalidDependencyCharacters))); + } + else if ("ALL" == key) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Key", key)); + } + } + else if (!packageType.HasValue) + { + // Make sure the ProductCode is authored and set the key. + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "ProductCode"); + key = "!(bind.property.ProductCode)"; + } + else if (WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msu == packageType) + { + // Must specify the provider key when authored for a package. + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + // The Version attribute should not be authored in or for an MSI package. + if (!String.IsNullOrEmpty(version)) + { + switch (packageType) + { + case null: + this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers)); + break; + case WixBundlePackageType.Msi: + this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers, parentId)); + break; + } + } + else if (WixBundlePackageType.Msp == packageType || WixBundlePackageType.Msu == packageType) + { + // Must specify the Version when authored for packages that do not contain a version. + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + + // Need the element ID for child element processing, so generate now if not authored. + if (null == id) + { + id = this.Core.CreateIdentifier("dep", node.Name.LocalName, parentId, key); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Requires": + this.ParseRequiresElement(child, id.Id, requiresAction: !packageType.HasValue); + break; + case "RequiresRef": + this.ParseRequiresRefElement(child, id.Id, requiresAction: !packageType.HasValue); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Messaging.EncounteredError) + { + var symbol = this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) + { + ComponentRef = parentId, + ProviderKey = key, + }); + + if (!String.IsNullOrEmpty(version)) + { + symbol.Version = version; + } + + if (!String.IsNullOrEmpty(displayName)) + { + symbol.DisplayName = displayName; + } + + if (!packageType.HasValue) + { + // Generate registry rows for the provider using binder properties. + var keyProvides = String.Concat(DependencyRegistryRoot, key); + var root = RegistryRootType.MachineUser; + + var value = "[ProductCode]"; + this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, null, value, parentId); + + value = !String.IsNullOrEmpty(version) ? version : "[ProductVersion]"; + var versionRegistrySymbol = this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "Version", value, parentId); + + value = !String.IsNullOrEmpty(displayName) ? displayName : "[ProductName]"; + this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "DisplayName", value, parentId); + + // Use the Version registry value and use that as a potential key path. + possibleKeyPath = versionRegistrySymbol.Id; + } + } + + return YesNoType.NotSet; + } + + /// + /// Processes the Requires element. + /// + /// The XML node for the Requires element. + /// The parent provider identifier. + /// Whether the Requires custom action should be referenced. + private void ParseRequiresElement(XElement node, string providerId, bool requiresAction) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string providerKey = null; + string minVersion = null; + string maxVersion = null; + var attributes = WixDependencySymbolAttributes.None; + var illegalChar = -1; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ProviderKey": + providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Minimum": + minVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Maximum": + maxVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "IncludeMinimum": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixDependencySymbolAttributes.RequiresAttributesMinVersionInclusive; + } + break; + case "IncludeMaximum": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixDependencySymbolAttributes.RequiresAttributesMaxVersionInclusive; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (null == id) + { + // Generate an ID only if this element is authored under a Provides element; otherwise, a RequiresRef + // element will be necessary and the Id attribute will be required. + if (!String.IsNullOrEmpty(providerId)) + { + id = this.Core.CreateIdentifier("dep", node.Name.LocalName, providerKey); + } + else + { + this.Messaging.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Id", "Provides")); + id = Identifier.Invalid; + } + } + + if (String.IsNullOrEmpty(providerKey)) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProviderKey")); + } + // Make sure the key does not contain any illegal characters. + else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) + { + this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); + } + + if (!this.Messaging.EncounteredError) + { + var symbol = this.Core.AddSymbol(new WixDependencySymbol(sourceLineNumbers, id) + { + ProviderKey = providerKey, + MinVersion = minVersion, + MaxVersion = maxVersion, + Attributes = attributes + }); + + // Create the relationship between this WixDependency symbol and the WixDependencyProvider symbol. + if (!String.IsNullOrEmpty(providerId)) + { + this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) + { + WixDependencyProviderRef = providerId, + WixDependencyRef = id.Id, + }); + } + } + } + + /// + /// Processes the RequiresRef element. + /// + /// The XML node for the RequiresRef element. + /// The parent provider identifier. + /// Whether the Requires custom action should be referenced. + private void ParseRequiresRefElement(XElement node, string providerId, bool requiresAction) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(id)) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + // Create a link dependency on the row that contains information we'll need during bind. + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixDependency, id); + + // Create the relationship between the WixDependency row and the parent WixDependencyProvider row. + this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) + { + WixDependencyProviderRef = providerId, + WixDependencyRef = id, + }); + } + } + } +} diff --git a/src/WixToolset.Core/Compiler_Module.cs b/src/WixToolset.Core/Compiler_Module.cs index d730ae5d..6953467f 100644 --- a/src/WixToolset.Core/Compiler_Module.cs +++ b/src/WixToolset.Core/Compiler_Module.cs @@ -190,6 +190,9 @@ namespace WixToolset.Core case "PropertyRef": this.ParseSimpleRefElement(child, SymbolDefinitions.Property); break; + case "Requires": + this.ParseRequiresElement(child, null, false); + break; case "SetDirectory": this.ParseSetDirectoryElement(child); break; diff --git a/src/WixToolset.Core/Compiler_Package.cs b/src/WixToolset.Core/Compiler_Package.cs new file mode 100644 index 00000000..d2728e9c --- /dev/null +++ b/src/WixToolset.Core/Compiler_Package.cs @@ -0,0 +1,4975 @@ +// 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 +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a product element. + /// + /// Element to parse. + private void ParsePackageElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compressed = YesNoDefaultType.Default; + var sourceBits = 0; + var codepage = 65001; + var productCode = "*"; + var isPerMachine = true; + string installScope = null; + string upgradeCode = null; + string manufacturer = null; + string version = null; + string symbols = null; + var isCodepageSet = false; + var isPackageNameSet = false; + var isKeywordsSet = false; + var isPackageAuthorSet = false; + + this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); + + this.activeName = null; + this.activeLanguage = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Codepage": + codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); + break; + case "Compressed": + compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + break; + case "InstallerVersion": + msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Language": + this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Manufacturer": + manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); + if ("PUT-COMPANY-NAME-HERE" == manufacturer) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer)); + } + break; + case "Name": + this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); + if ("PUT-PRODUCT-NAME-HERE" == this.activeName) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); + } + break; + case "ProductCode": + productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); + break; + case "Scope": + installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (installScope) + { + case "perMachine": + // handled below after we create the section. + break; + case "perUser": + isPerMachine = false; + sourceBits |= 8; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); + break; + } + break; + case "ShortNames": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + sourceBits |= 1; + } + break; + case "UpgradeCode": + upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Version": // if the attribute is valid version, use the attribute value as is (so "1.0000.01.01" would *not* get translated to "1.0.1.1"). + var verifiedVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + if (!String.IsNullOrEmpty(verifiedVersion)) + { + version = attrib.Value; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == productCode) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == this.activeLanguage) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); + } + + if (null == manufacturer) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); + } + + if (null == this.activeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == upgradeCode) + { + this.Core.Write(WarningMessages.MissingUpgradeCode(sourceLineNumbers)); + } + + if (null == version) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + else if (!CompilerCore.IsValidProductVersion(version)) + { + this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); + } + + if (compressed != YesNoDefaultType.No) + { + sourceBits |= 2; + } + + if (this.Core.EncounteredError) + { + return; + } + + try + { + this.compilingProduct = true; + this.Core.CreateActiveSection(productCode, SectionType.Product, codepage, this.Context.CompilationId); + + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "Manufacturer"), manufacturer, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductCode"), productCode, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductLanguage"), this.activeLanguage, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductName"), this.activeName, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ProductVersion"), version, false, false, false, true); + if (null != upgradeCode) + { + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "UpgradeCode"), upgradeCode, false, false, false, true); + } + + if (isPerMachine) + { + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Public, "ALLUSERS"), "1", false, false, false, false); + } + + this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WordCount, + Value = sourceBits.ToString(CultureInfo.InvariantCulture) + }); + + var contextValues = new Dictionary + { + ["ProductLanguage"] = this.activeLanguage, + ["ProductVersion"] = version, + ["UpgradeCode"] = upgradeCode + }; + + var featureDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "_locDefinition": + break; + case "AdminExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); + break; + case "AdminUISequence": + this.ParseSequenceElement(child, SequenceTable.AdminUISequence); + break; + case "AdvertiseExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); + break; + case "InstallExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); + break; + case "InstallUISequence": + this.ParseSequenceElement(child, SequenceTable.InstallUISequence); + break; + case "AppId": + this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); + break; + case "Binary": + this.ParseBinaryElement(child); + break; + case "ComplianceCheck": + this.ParseComplianceCheckElement(child); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "ComponentGroup": + this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); + break; + case "CustomAction": + this.ParseCustomActionElement(child); + break; + case "CustomActionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); + break; + case "CustomTable": + this.ParseCustomTableElement(child); + break; + case "CustomTableRef": + this.ParseCustomTableRefElement(child); + break; + case "Directory": + this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); + break; + case "DirectoryRef": + this.ParseDirectoryRefElement(child); + break; + case "EmbeddedChainer": + this.ParseEmbeddedChainerElement(child); + break; + case "EmbeddedChainerRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); + break; + case "EnsureTable": + this.ParseEnsureTableElement(child); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode); + break; + case "FeatureGroupRef": + this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode); + break; + case "Icon": + this.ParseIconElement(child); + break; + case "InstanceTransforms": + this.ParseInstanceTransformsElement(child); + break; + case "Launch": + this.ParseLaunchElement(child); + break; + case "MajorUpgrade": + this.ParseMajorUpgradeElement(child, contextValues); + break; + case "Media": + this.ParseMediaElement(child, null); + break; + case "MediaTemplate": + this.ParseMediaTemplateElement(child, null); + break; + case "PackageCertificates": + case "PatchCertificates": + this.ParseCertificatesElement(child); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "PropertyRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Property); + break; + case "Requires": + this.ParseRequiresElement(child, null, false); + break; + case "SetDirectory": + this.ParseSetDirectoryElement(child); + break; + case "SetProperty": + this.ParseSetPropertyElement(child); + break; + case "SFPCatalog": + string parentName = null; + this.ParseSFPCatalogElement(child, ref parentName); + break; + case "SoftwareTag": + this.ParsePackageTagElement(child); + break; + case "SummaryInformation": + this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); + break; + case "SymbolPath": + if (null != symbols) + { + symbols += ";" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + case "UI": + this.ParseUIElement(child); + break; + case "UIRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); + break; + case "Upgrade": + this.ParseUpgradeElement(child); + break; + case "WixVariable": + this.ParseWixVariableElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (!isCodepageSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = "1252" + }); + } + + if (!isPackageNameSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = this.activeName + }); + } + + if (!isPackageAuthorSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = manufacturer + }); + } + + if (!isKeywordsSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = "Installer" + }); + } + + if (null != symbols) + { + this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers) + { + SymbolId = productCode, + SymbolType = SymbolPathType.Product, + SymbolPaths = symbols, + }); + } + } + } + finally + { + this.compilingProduct = false; + } + } + + private void GetDefaultPlatformAndInstallerVersion(out string platform, out int msiVersion) + { + // Let's default to a modern version of MSI. Users can override, + // of course, subject to platform-specific limitations. + msiVersion = 500; + + switch (this.CurrentPlatform) + { + case Platform.X86: + platform = "Intel"; + break; + case Platform.X64: + platform = "x64"; + break; + case Platform.ARM64: + platform = "Arm64"; + break; + default: + throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString()); + } + } + + private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform) + { + if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) + { + msiVersion = 200; + this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers)); + } + + if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion) + { + msiVersion = 500; + this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers)); + } + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Comments, + Value = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Title, + Value = "Installation Database" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.PlatformAndLanguage, + Value = String.Format(CultureInfo.InvariantCulture, "{0};{1}", platform, this.activeLanguage) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WindowsInstallerVersion, + Value = msiVersion.ToString(CultureInfo.InvariantCulture) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Security, + Value = "2" + }); + + } + + /// + /// Parses an odbc driver or translator element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Default identifer for driver/translator file. + /// Symbol type we're processing for. + private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, SymbolDefinitionType symbolDefinitionType) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var driver = fileId; + string name = null; + var setup = fileId; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "File": + driver = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, driver); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SetupFile": + setup = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, setup); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("odb", name, fileId, setup); + } + + // drivers have a few possible children + if (SymbolDefinitionType.ODBCDriver == symbolDefinitionType) + { + // process any data sources for the driver + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ODBCDataSource": + string ignoredKeyPath = null; + this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath); + break; + case "Property": + this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCAttribute); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + else + { + this.Core.ParseForExtensionElements(node); + } + + if (!this.Core.EncounteredError) + { + switch (symbolDefinitionType) + { + case SymbolDefinitionType.ODBCDriver: + this.Core.AddSymbol(new ODBCDriverSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + FileRef = driver, + SetupFileRef = setup, + }); + break; + case SymbolDefinitionType.ODBCTranslator: + this.Core.AddSymbol(new ODBCTranslatorSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + FileRef = driver, + SetupFileRef = setup, + }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); + } + } + } + + /// + /// Parses a Property element underneath an ODBC driver or translator. + /// + /// Element to parse. + /// Identifier of parent driver or translator. + /// Name of the table to create property in. + private void ParseODBCProperty(XElement node, string parentId, SymbolDefinitionType symbolDefinitionType) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string propertyValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + propertyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var identifier = new Identifier(AccessModifier.Private, parentId, id); + switch (symbolDefinitionType) + { + case SymbolDefinitionType.ODBCAttribute: + this.Core.AddSymbol(new ODBCAttributeSymbol(sourceLineNumbers, identifier) + { + DriverRef = parentId, + Attribute = id, + Value = propertyValue, + }); + break; + case SymbolDefinitionType.ODBCSourceAttribute: + this.Core.AddSymbol(new ODBCSourceAttributeSymbol(sourceLineNumbers, identifier) + { + DataSourceRef = parentId, + Attribute = id, + Value = propertyValue, + }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); + } + } + } + + /// + /// Parse an odbc data source element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Default name of driver. + /// Identifier of this element in case it is a keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var keyPath = YesNoType.NotSet; + string name = null; + var registration = CompilerConstants.IntegerNotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "DriverName": + driverName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "KeyPath": + keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Registration": + var registrationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (registrationValue) + { + case "machine": + registration = 0; + break; + case "user": + registration = 1; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (CompilerConstants.IntegerNotSet == registration) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration")); + registration = CompilerConstants.IllegalInteger; + } + + if (null == id) + { + id = this.Core.CreateIdentifier("odc", name, driverName, registration.ToString()); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Property": + this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCSourceAttribute); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ODBCDataSourceSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + DriverDescription = driverName, + Registration = registration + }); + } + + possibleKeyPath = id.Id; + return keyPath; + } + + /// + /// Parses a package element. + /// + /// Element to parse. + /// + /// + /// + /// + private void ParseSummaryInformationElement(XElement node, ref bool isCodepageSet, ref bool isPackageNameSet, ref bool isKeywordsSet, ref bool isPackageAuthorSet) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string codepage = null; + string packageName = null; + string keywords = null; + string packageAuthor = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Codepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); + break; + case "Description": + packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Keywords": + keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Manufacturer": + packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if ("PUT-COMPANY-NAME-HERE" == packageAuthor) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor)); + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + if (null != codepage) + { + isCodepageSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = codepage + }); + } + + if (null != packageName) + { + isPackageNameSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = packageName + }); + } + + if (null != packageAuthor) + { + isPackageAuthorSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = packageAuthor + }); + } + + if (null != keywords) + { + isKeywordsSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = keywords + }); + } + } + } + + /// + /// Parses a patch information element. + /// + /// Element to parse. + private void ParsePatchInformationElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var codepage = "1252"; + string comments = null; + var keywords = "Installer,Patching,PCP,Database"; + var msiVersion = 1; // Should always be 1 for patches + string packageAuthor = null; + var packageName = this.activeName; + var security = YesNoDefaultType.Default; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AdminImage": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Comments": + comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Compressed": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Description": + packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Keywords": + keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Languages": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Manufacturer": + packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Platforms": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "ReadOnly": + security = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + break; + case "ShortNames": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "SummaryCodepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = codepage + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Title, + Value = "Patch" + }); + + if (null != packageName) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = packageName + }); + } + + if (null != packageAuthor) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = packageAuthor + }); + } + + if (null != keywords) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = keywords + }); + } + + if (null != comments) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Comments, + Value = comments + }); + } + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WindowsInstallerVersion, + Value = msiVersion.ToString(CultureInfo.InvariantCulture) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WordCount, + Value = "0" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Security, + Value = YesNoDefaultType.No == security ? "0" : YesNoDefaultType.Yes == security ? "4" : "2" + }); + } + } + + /// + /// Parses a permission element. + /// + /// Element to parse. + /// Identifier of object to be secured. + /// Name of table that contains objectId. + private void ParsePermissionElement(XElement node, string objectId, string tableName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var bits = new BitArray(32); + string domain = null; + string[] specialPermissions = null; + string user = null; + + switch (tableName) + { + case "CreateFolder": + specialPermissions = Common.FolderPermissions; + break; + case "File": + specialPermissions = Common.FilePermissions; + break; + case "Registry": + specialPermissions = Common.RegistryPermissions; + break; + default: + this.Core.UnexpectedElement(node.Parent, node); + return; // stop processing this element since no valid permissions are available + } + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Domain": + domain = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "User": + user = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "FileAllRights": + // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127) + bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[16] = bits[17] = bits[18] = bits[19] = bits[20] = true; + break; + case "SpecificRightsAll": + // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111) + bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[9] = bits[10] = bits[11] = bits[12] = bits[13] = bits[14] = bits[15] = true; + break; + default: + var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + if (!this.Core.TrySetBitFromName(Common.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16)) + { + if (!this.Core.TrySetBitFromName(Common.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28)) + { + if (!this.Core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0)) + { + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == user) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User")); + } + + var permission = this.Core.CreateIntegerFromBitArray(bits); + + if (Int32.MinValue == permission) // just GENERIC_READ, which is MSI_NULL + { + this.Core.Write(ErrorMessages.GenericReadNotAllowed(sourceLineNumbers)); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new LockPermissionsSymbol(sourceLineNumbers) + { + LockObject = objectId, + Table = tableName, + Domain = domain, + User = user, + Permission = permission + }); + } + } + + /// + /// Parses an extended permission element. + /// + /// Element to parse. + /// Identifier of object to be secured. + /// Name of table that contains objectId. + private void ParsePermissionExElement(XElement node, string objectId, string tableName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string condition = null; + Identifier id = null; + string sddl = null; + + switch (tableName) + { + case "CreateFolder": + case "File": + case "Registry": + case "ServiceInstall": + break; + default: + this.Core.UnexpectedElement(node.Parent, node); + return; // stop processing this element since nothing will be valid. + } + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Sddl": + sddl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (null == sddl) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("pme", objectId, tableName, sddl); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiLockPermissionsExSymbol(sourceLineNumbers, id) + { + LockObject = objectId, + Table = tableName, + SDDLText = sddl, + Condition = condition + }); + } + } + + /// + /// Parses a progid element + /// + /// Element to parse. + /// Identifier of parent component. + /// Flag if progid is advertised. + /// CLSID related to ProgId. + /// Default description of ProgId + /// Optional parent ProgId + /// Set to true if an extension is found; used for error-checking. + /// Whether or not this ProgId is the first one found in the parent class. + /// This element's Id. + private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string icon = null; + var iconIndex = CompilerConstants.IntegerNotSet; + string noOpen = null; + string progId = null; + var progIdAdvertise = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + progId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Advertise": + progIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Icon": + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "IconIndex": + iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); + break; + case "NoOpen": + noOpen = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise)) + { + this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString())); + } + else if (YesNoType.NotSet != progIdAdvertise) + { + advertise = progIdAdvertise; + } + + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex)) + { + this.Core.Write(ErrorMessages.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers)); + } + + var firstProgIdForNestedClass = YesNoType.Yes; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Extension": + this.ParseExtensionElement(child, componentId, advertise, progId); + foundExtension = true; + break; + case "ProgId": + // Only allow one nested ProgId. If we have a child, we should not have a parent. + if (null == parent) + { + if (YesNoType.Yes == advertise) + { + this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass); + } + else if (YesNoType.No == advertise) + { + this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass); + } + + firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first. + } + else + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.ProgIdNestedTooDeep(childSourceLineNumbers)); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (YesNoType.Yes == advertise) + { + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ProgIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, progId)) + { + ProgId = progId, + ParentProgIdRef = parent, + ClassRef = classId, + Description = description, + }); + + if (null != icon) + { + symbol.IconRef = icon; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); + } + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + symbol.IconIndex = iconIndex; + } + + this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Class); + } + } + else if (YesNoType.No == advertise) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, String.Empty, description, componentId); + if (null != classId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId); + if (null != parent) // if this is a version independent ProgId + { + if (YesNoType.Yes == firstProgIdForClass) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId); + } + else + { + if (YesNoType.Yes == firstProgIdForClass) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId); + } + } + } + + if (null != icon) // ProgId's Default Icon + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); + + icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + icon = String.Concat(icon, ",", iconIndex); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId); + } + } + + if (null != noOpen) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name + } + + // raise an error for an orphaned ProgId + if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId) + { + this.Core.Write(WarningMessages.OrphanedProgId(sourceLineNumbers, progId)); + } + + return progId; + } + + /// + /// Parses a property element. + /// + /// Element to parse. + private void ParsePropertyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var admin = false; + var complianceCheck = false; + var hidden = false; + var secure = false; + var suppressModularization = YesNoType.NotSet; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Admin": + admin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ComplianceCheck": + complianceCheck = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Hidden": + hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Secure": + secure = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SuppressModularization": + suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if ("ProductID" == id.Id) + { + this.Core.Write(WarningMessages.ProductIdAuthored(sourceLineNumbers)); + } + else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id) + { + this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if ("ErrorDialog" == id.Id) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, value); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + { + switch (child.Name.LocalName) + { + case "ProductSearch": + this.ParseProductSearchElement(child, id.Id); + secure = true; + break; + default: + // let ParseSearchSignatures handle standard AppSearch children and unknown elements + break; + } + } + } + } + + // see if this property is used for appSearch + var signatures = this.ParseSearchSignatures(node); + + // If we're doing CCP then there must be a signature. + if (complianceCheck && 0 == signatures.Count) + { + this.Core.Write(ErrorMessages.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes")); + } + + foreach (var sig in signatures) + { + if (complianceCheck && !this.Core.EncounteredError) + { + this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Private, sig))); + } + + this.AddAppSearch(sourceLineNumbers, id, sig); + } + + // If we're doing AppSearch get that setup. + if (0 < signatures.Count) + { + this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); + } + else // just a normal old property. + { + // If the property value is empty and none of the flags are set, print out a warning that we're ignoring + // the element. + if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden) + { + this.Core.Write(WarningMessages.PropertyUseless(sourceLineNumbers, id.Id)); + } + else // there is a value and/or a flag set, do that. + { + this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); + } + } + + if (!this.Core.EncounteredError && YesNoType.Yes == suppressModularization) + { + this.Core.Write(WarningMessages.PropertyModularizationSuppressed(sourceLineNumbers)); + + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parses a RegistryKey element. + /// + /// Element to parse. + /// Identifier for parent component. + /// Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet. + /// Parent key for this Registry element when nested. + /// true if the component is 64-bit. + /// Identifier of this registry key since it could be the component's keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseRegistryKeyElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var key = parentKey; // default to parent key path + var forceCreateOnInstall = false; + var forceDeleteOnUninstall = false; + var keyPath = YesNoType.NotSet; + + possibleKeyPath = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + this.Core.Write(WarningMessages.DeprecatedRegistryKeyActionAttribute(sourceLineNumbers)); + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "create": + forceCreateOnInstall = true; + break; + case "createAndRemoveOnUninstall": + forceCreateOnInstall = true; + forceDeleteOnUninstall = true; + break; + case "none": + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "create", "createAndRemoveOnUninstall", "none")); + break; + } + break; + case "ForceCreateOnInstall": + forceCreateOnInstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ForceDeleteOnUninstall": + forceDeleteOnUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (null != parentKey) + { + key = Path.Combine(parentKey, key); + } + key = key?.TrimEnd('\\'); + break; + case "Root": + if (root.HasValue) + { + this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); + } + + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + var name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null); + + if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present + { + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + } + else // does not generate a Registry row, so no Id should be present + { + if (null != id) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true)); + } + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + key = String.Empty; // set the key to something to prevent null reference exceptions + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + string possibleChildKeyPath = null; + + switch (child.Name.LocalName) + { + case "RegistryKey": + if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) + { + if (YesNoType.Yes == keyPath) + { + this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); + } + + possibleKeyPath = possibleChildKeyPath; // the child is the key path + keyPath = YesNoType.Yes; + } + else if (null == possibleKeyPath && null != possibleChildKeyPath) + { + possibleKeyPath = possibleChildKeyPath; + } + break; + case "RegistryValue": + if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) + { + if (YesNoType.Yes == keyPath) + { + this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); + } + + possibleKeyPath = possibleChildKeyPath; // the child is the key path + keyPath = YesNoType.Yes; + } + else if (null == possibleKeyPath && null != possibleChildKeyPath) + { + possibleKeyPath = possibleChildKeyPath; + } + break; + case "Permission": + if (!forceCreateOnInstall) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); + } + this.ParsePermissionElement(child, id.Id, "Registry"); + break; + case "PermissionEx": + if (!forceCreateOnInstall) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); + } + this.ParsePermissionExElement(child, id.Id, "Registry"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (!this.Core.EncounteredError && null != name) + { + this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + ComponentRef = componentId, + }); + } + + return keyPath; + } + + /// + /// Parses a RegistryValue element. + /// + /// Element to parse. + /// Identifier for parent component. + /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. + /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. + /// true if the component is 64-bit. + /// Identifier of this registry key since it could be the component's keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseRegistryValueElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var key = parentKey; // default to parent key path + string name = null; + string value = null; + string action = null; + var valueType = RegistryValueType.String; + var actionType = RegistryValueActionType.Write; + var keyPath = YesNoType.NotSet; + + possibleKeyPath = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "append": + actionType = RegistryValueActionType.Append; + break; + case "prepend": + actionType = RegistryValueActionType.Prepend; + break; + case "write": + actionType = RegistryValueActionType.Write; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "append", "prepend", "write")); + break; + } + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (null != parentKey) + { + if (parentKey.EndsWith("\\", StringComparison.Ordinal)) + { + key = String.Concat(parentKey, key); + } + else + { + key = String.Concat(parentKey, "\\", key); + } + } + break; + case "KeyPath": + keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + if (root.HasValue) + { + this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); + } + + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "binary": + valueType = RegistryValueType.Binary; + break; + case "expandable": + valueType = RegistryValueType.Expandable; + break; + case "integer": + valueType = RegistryValueType.Integer; + break; + case "multiString": + valueType = RegistryValueType.MultiString; + break; + case "string": + valueType = RegistryValueType.String; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "binary", "expandable", "integer", "multiString", "string")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)(root ?? RegistryRootType.Unknown)).ToString(), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (RegistryValueType.MultiString != valueType && (RegistryValueActionType.Append == actionType || RegistryValueActionType.Prepend == actionType)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "MultiString": + case "MultiStringValue": + if (RegistryValueType.MultiString != valueType && null != value) + { + this.Core.Write(ErrorMessages.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type")); + } + else + { + value = this.ParseRegistryMultiStringElement(child, value); + } + break; + case "Permission": + this.ParsePermissionElement(child, id.Id, "Registry"); + break; + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "Registry"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + //switch (typeType) + //{ + //case Wix.RegistryValue.TypeType.binary: + // value = String.Concat("#x", value); + // break; + //case Wix.RegistryValue.TypeType.expandable: + // value = String.Concat("#%", value); + // break; + //case Wix.RegistryValue.TypeType.integer: + // value = String.Concat("#", value); + // break; + //case Wix.RegistryValue.TypeType.multiString: + // switch (actionType) + // { + // case Wix.RegistryValue.ActionType.append: + // value = String.Concat("[~]", value); + // break; + // case Wix.RegistryValue.ActionType.prepend: + // value = String.Concat(value, "[~]"); + // break; + // case Wix.RegistryValue.ActionType.write: + // default: + // if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) + // { + // value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); + // } + // break; + // } + // break; + //case Wix.RegistryValue.TypeType.@string: + // // escape the leading '#' character for string registry keys + // if (null != value && value.StartsWith("#", StringComparison.Ordinal)) + // { + // value = String.Concat("#", value); + // } + // break; + //} + + // value may be set by child MultiStringValue elements, so it must be checked here + if (null == value && valueType != RegistryValueType.Binary) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + else if (0 == value?.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values + { + this.Core.Write(ErrorMessages.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name)); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + Value = value, + ValueType = valueType, + ValueAction = actionType, + ComponentRef = componentId, + }); + } + + // If this was just a regular registry key (that could be the key path) + // and no child registry key set the possible key path, let's make this + // Registry/@Id a possible key path. + if (null == possibleKeyPath) + { + possibleKeyPath = id.Id; + } + + return keyPath; + } + + private string ParseRegistryMultiStringElement(XElement node, string value) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string multiStringValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + multiStringValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + this.Core.ParseForExtensionElements(node); + + return null == value ? multiStringValue ?? "[~]" : String.Concat(value, "[~]", multiStringValue); + } + + /// + /// Parses a RemoveRegistryKey element. + /// + /// The element to parse. + /// The component identifier of the parent element. + private void ParseRemoveRegistryKeyElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + RemoveRegistryActionType? actionType = null; + string key = null; + var name = "-"; + RegistryRootType? root = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "removeOnInstall": + actionType = RemoveRegistryActionType.RemoveOnInstall; + break; + case "removeOnUninstall": + actionType = RemoveRegistryActionType.RemoveOnUninstall; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "removeOnInstall", "removeOnUninstall")); + break; + } + //if (0 < action.Length) + //{ + // if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType)) + // { + // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall")); + // } + //} + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (!actionType.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + Action = actionType.Value, + ComponentRef = componentId, + }); + } + } + + /// + /// Parses a RemoveRegistryValue element. + /// + /// The element to parse. + /// The component identifier of the parent element. + private void ParseRemoveRegistryValueElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string name = null; + RegistryRootType? root = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + ComponentRef = componentId + }); + } + } + + /// + /// Parses a remove file element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of the parent component's directory. + private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string directory = null; + string name = null; + bool? onInstall = null; + bool? onUninstall = null; + string property = null; + string shortName = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, true); + break; + case "On": + var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (onValue) + { + case "install": + onInstall = true; + break; + case "uninstall": + onUninstall = true; + break; + case "both": + onInstall = true; + onUninstall = true; + break; + } + break; + case "Property": + property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (!onInstall.HasValue && !onUninstall.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); + } + + if (null != directory && null != property) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory)); + } + + if (null == id) + { + var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; + id = this.Core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + FileName = name, + ShortFileName = shortName, + DirPropertyRef = directory ?? property ?? parentDirectory, + OnInstall = onInstall, + OnUninstall = onUninstall, + }); + } + } + + /// + /// Parses a RemoveFolder element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of parent component's directory. + private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string directory = null; + bool? onInstall = null; + bool? onUninstall = null; + string property = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory); + break; + case "On": + var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (onValue) + { + case "install": + onInstall = true; + break; + case "uninstall": + onUninstall = true; + break; + case "both": + onInstall = true; + onUninstall = true; + break; + } + break; + case "Property": + property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (!onInstall.HasValue && !onUninstall.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); + } + + if (null != directory && null != property) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory)); + } + + if (null == id) + { + var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; + id = this.Core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, on.ToString()); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + DirPropertyRef = directory ?? property ?? parentDirectory, + OnInstall = onInstall, + OnUninstall = onUninstall + }); + } + } + + /// + /// Parses a reserve cost element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional and default identifier of referenced directory. + private void ParseReserveCostElement(XElement node, string componentId, string directoryId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var runFromSource = CompilerConstants.IntegerNotSet; + var runLocal = CompilerConstants.IntegerNotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, directoryId); + break; + case "RunFromSource": + runFromSource = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "RunLocal": + runLocal = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("rc", componentId, directoryId); + } + + if (CompilerConstants.IntegerNotSet == runFromSource) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource")); + } + + if (CompilerConstants.IntegerNotSet == runLocal) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ReserveCostSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + ReserveFolder = directoryId, + ReserveLocal = runLocal, + ReserveSource = runFromSource + }); + } + } + + /// + /// Parses a sequence element. + /// + /// Element to parse. + /// Name of sequence table. + private void ParseSequenceElement(XElement node, SequenceTable sequenceTable) + { + // Parse each action in the sequence. + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + var actionName = child.Name.LocalName; + string afterAction = null; + string beforeAction = null; + string condition = null; + var customAction = "Custom" == actionName; + var overridable = false; + var exitSequence = CompilerConstants.IntegerNotSet; + var sequence = CompilerConstants.IntegerNotSet; + var showDialog = "Show" == actionName; + var specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName; + var specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName; + var suppress = false; + + foreach (var attrib in child.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + if (customAction) + { + actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.CustomAction, actionName); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "After": + if (customAction || showDialog || specialAction || specialStandardAction) + { + afterAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), afterAction); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Before": + if (customAction || showDialog || specialAction || specialStandardAction) + { + beforeAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), beforeAction); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Condition": + condition = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Dialog": + if (showDialog) + { + actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.Dialog, actionName); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "OnExit": + if (customAction || showDialog || specialAction) + { + var exitValue = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + switch (exitValue) + { + case "success": + exitSequence = -1; + break; + case "cancel": + exitSequence = -2; + break; + case "error": + exitSequence = -3; + break; + case "suspend": + exitSequence = -4; + break; + } + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Overridable": + overridable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); + break; + case "Sequence": + sequence = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "Suppress": + suppress = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.VerifyNoInnerText(childSourceLineNumbers, node); + + if (customAction && "Custom" == actionName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); + } + else if (showDialog && "Show" == actionName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog")); + } + + if (CompilerConstants.IntegerNotSet != sequence) + { + if (CompilerConstants.IntegerNotSet != exitSequence) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit")); + } + else if (null != beforeAction || null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After")); + } + } + else // sequence not specified use OnExit (which may also be not set). + { + sequence = exitSequence; + } + + if (null != beforeAction && null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before")); + } + else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction) + { + this.Core.Write(ErrorMessages.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName)); + } + + // action that is scheduled to occur before/after itself + if (beforeAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction)); + } + else if (afterAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction)); + } + + // normal standard actions cannot be set overridable by the user (since they are overridable by default) + if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction) + { + this.Core.Write(ErrorMessages.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable")); + } + + // suppress cannot be specified at the same time as Before, After, or Sequence + if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable")); + } + + this.Core.ParseForExtensionElements(child); + + // add the row and any references needed + if (!this.Core.EncounteredError) + { + if (suppress) + { + this.Core.AddSymbol(new WixSuppressActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Public, sequenceTable, actionName)) + { + SequenceTable = sequenceTable, + Action = actionName + }); + } + else + { + var symbol = this.Core.AddSymbol(new WixActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Public, sequenceTable, actionName)) + { + SequenceTable = sequenceTable, + Action = actionName, + Condition = condition, + Before = beforeAction, + After = afterAction, + Overridable = overridable, + }); + + if (CompilerConstants.IntegerNotSet != sequence) + { + symbol.Sequence = sequence; + } + } + } + } + } + + + /// + /// Parses a service config element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional element containing parent's service name. + private void ParseServiceConfigElement(XElement node, string componentId, string serviceName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string delayedAutoStart = null; + string failureActionsWhen = null; + var name = serviceName; + var install = false; + var reinstall = false; + var uninstall = false; + string preShutdownDelay = null; + string requiredPrivileges = null; + string sid = null; + + this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "DelayedAutoStart": + delayedAutoStart = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (delayedAutoStart) + { + case "no": + delayedAutoStart = "0"; + break; + case "yes": + delayedAutoStart = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "FailureActionsWhen": + failureActionsWhen = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (failureActionsWhen) + { + case "failedToStop": + failureActionsWhen = "0"; + break; + case "failedToStopOrReturnedError": + failureActionsWhen = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "OnInstall": + install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == install) + //{ + // events |= MsiInterop.MsidbServiceConfigEventInstall; + //} + break; + case "OnReinstall": + reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == reinstall) + //{ + // events |= MsiInterop.MsidbServiceConfigEventReinstall; + //} + break; + case "OnUninstall": + uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == uninstall) + //{ + // events |= MsiInterop.MsidbServiceConfigEventUninstall; + //} + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + case "PreShutdownDelay": + preShutdownDelay = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "ServiceName": + if (!String.IsNullOrEmpty(serviceName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); + } + + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ServiceSid": + sid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sid) + { + case "none": + sid = "0"; + break; + case "restricted": + sid = "3"; + break; + case "unrestricted": + sid = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Get the ServiceConfig required privilegs. + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "RequiredPrivilege": + requiredPrivileges = this.ParseRequiredPrivilege(child, requiredPrivileges); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (!install && !reinstall && !uninstall) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); + } + + if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid)) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege")); + } + + if (!this.Core.EncounteredError) + { + if (!String.IsNullOrEmpty(delayedAutoStart)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".DS"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.DelayedAutoStart, + Argument = delayedAutoStart, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(failureActionsWhen)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".FA"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.FailureActionsFlag, + Argument = failureActionsWhen, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(sid)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".SS"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.ServiceSidInfo, + Argument = sid, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(requiredPrivileges)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".RP"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.RequiredPrivilegesInfo, + Argument = requiredPrivileges, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(preShutdownDelay)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".PD"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.PreshutdownInfo, + Argument = preShutdownDelay, + ComponentRef = componentId, + }); + } + } + } + + private string ParseRequiredPrivilege(XElement node, string requiredPrivileges) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string privilege = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + privilege = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (privilege) + { + case "assignPrimaryToken": + privilege = "SeAssignPrimaryTokenPrivilege"; + break; + case "audit": + privilege = "SeAuditPrivilege"; + break; + case "backup": + privilege = "SeBackupPrivilege"; + break; + case "changeNotify": + privilege = "SeChangeNotifyPrivilege"; + break; + case "createGlobal": + privilege = "SeCreateGlobalPrivilege"; + break; + case "createPagefile": + privilege = "SeCreatePagefilePrivilege"; + break; + case "createPermanent": + privilege = "SeCreatePermanentPrivilege"; + break; + case "createSymbolicLink": + privilege = "SeCreateSymbolicLinkPrivilege"; + break; + case "createToken": + privilege = "SeCreateTokenPrivilege"; + break; + case "debug": + privilege = "SeDebugPrivilege"; + break; + case "enableDelegation": + privilege = "SeEnableDelegationPrivilege"; + break; + case "impersonate": + privilege = "SeImpersonatePrivilege"; + break; + case "increaseBasePriority": + privilege = "SeIncreaseBasePriorityPrivilege"; + break; + case "increaseQuota": + privilege = "SeIncreaseQuotaPrivilege"; + break; + case "increaseWorkingSet": + privilege = "SeIncreaseWorkingSetPrivilege"; + break; + case "loadDriver": + privilege = "SeLoadDriverPrivilege"; + break; + case "lockMemory": + privilege = "SeLockMemoryPrivilege"; + break; + case "machineAccount": + privilege = "SeMachineAccountPrivilege"; + break; + case "manageVolume": + privilege = "SeManageVolumePrivilege"; + break; + case "profileSingleProcess": + privilege = "SeProfileSingleProcessPrivilege"; + break; + case "relabel": + privilege = "SeRelabelPrivilege"; + break; + case "remoteShutdown": + privilege = "SeRemoteShutdownPrivilege"; + break; + case "restore": + privilege = "SeRestorePrivilege"; + break; + case "security": + privilege = "SeSecurityPrivilege"; + break; + case "shutdown": + privilege = "SeShutdownPrivilege"; + break; + case "syncAgent": + privilege = "SeSyncAgentPrivilege"; + break; + case "systemEnvironment": + privilege = "SeSystemEnvironmentPrivilege"; + break; + case "systemProfile": + privilege = "SeSystemProfilePrivilege"; + break; + case "systemTime": + case "modifySystemTime": + privilege = "SeSystemtimePrivilege"; + break; + case "takeOwnership": + privilege = "SeTakeOwnershipPrivilege"; + break; + case "tcb": + case "trustedComputerBase": + privilege = "SeTcbPrivilege"; + break; + case "timeZone": + case "modifyTimeZone": + privilege = "SeTimeZonePrivilege"; + break; + case "trustedCredManAccess": + case "trustedCredentialManagerAccess": + privilege = "SeTrustedCredManAccessPrivilege"; + break; + case "undock": + privilege = "SeUndockPrivilege"; + break; + case "unsolicitedInput": + privilege = "SeUnsolicitedInputPrivilege"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (privilege == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + this.Core.ParseForExtensionElements(node); + + return (requiredPrivileges == null) ? privilege : String.Concat(requiredPrivileges, "[~]", privilege); + } + + /// + /// Parses a service config failure actions element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional element containing parent's service name. + private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var name = serviceName; + var install = false; + var reinstall = false; + var uninstall = false; + int? resetPeriod = null; + string rebootMessage = null; + string command = null; + string actions = null; + string actionsDelays = null; + + this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Command": + command = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "OnInstall": + install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnReinstall": + reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnUninstall": + uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RebootMessage": + rebootMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "ResetPeriod": + resetPeriod = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "ServiceName": + if (!String.IsNullOrEmpty(serviceName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); + } + + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Get the ServiceConfigFailureActions actions. + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Failure": + string action = null; + string delay = null; + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + + foreach (var childAttrib in child.Attributes()) + { + if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace) + { + switch (childAttrib.Name.LocalName) + { + case "Action": + action = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (action) + { + case "none": + action = "0"; + break; + case "restartComputer": + action = "2"; + break; + case "restartService": + action = "1"; + break; + case "runCommand": + action = "3"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "Delay": + delay = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + default: + this.Core.UnexpectedAttribute(child, childAttrib); + break; + } + } + } + + if (String.IsNullOrEmpty(action)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action")); + } + + if (String.IsNullOrEmpty(delay)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay")); + } + + if (!String.IsNullOrEmpty(actions)) + { + actions = String.Concat(actions, "[~]"); + } + actions = String.Concat(actions, action); + + if (!String.IsNullOrEmpty(actionsDelays)) + { + actionsDelays = String.Concat(actionsDelays, "[~]"); + } + actionsDelays = String.Concat(actionsDelays, delay); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (!install && !reinstall && !uninstall) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiServiceConfigFailureActionsSymbol(sourceLineNumbers, id) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ResetPeriod = resetPeriod, + RebootMessage = rebootMessage, + Command = command, + Actions = actions, + DelayActions = actionsDelays, + ComponentRef = componentId, + }); + } + } + + /// + /// Parses a service control element. + /// + /// Element to parse. + /// Identifier of parent component. + private void ParseServiceControlElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string arguments = null; + Identifier id = null; + string name = null; + var installRemove = false; + var uninstallRemove = false; + var installStart = false; + var uninstallStart = false; + var installStop = false; + var uninstallStop = false; + bool? wait = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Remove": + var removeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (removeValue) + { + case "install": + installRemove = true; + break; + case "uninstall": + uninstallRemove = true; + break; + case "both": + installRemove = true; + uninstallRemove = true; + break; + case "": + break; + } + break; + case "Start": + var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (startValue) + { + case "install": + installStart = true; + break; + case "uninstall": + uninstallStart = true; + break; + case "both": + installStart = true; + uninstallStart = true; + break; + case "": + break; + } + break; + case "Stop": + var stopValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (stopValue) + { + case "install": + installStop = true; + break; + case "uninstall": + uninstallStop = true; + break; + case "both": + installStop = true; + uninstallStop = true; + break; + case "": + break; + } + break; + case "Wait": + wait = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + // get the ServiceControl arguments + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ServiceArgument": + arguments = this.ParseServiceArgument(child, arguments); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ServiceControlSymbol(sourceLineNumbers, id) + { + Name = name, + InstallRemove = installRemove, + UninstallRemove = uninstallRemove, + InstallStart = installStart, + UninstallStart = uninstallStart, + InstallStop = installStop, + UninstallStop = uninstallStop, + Arguments = arguments, + Wait = wait, + ComponentRef = componentId + }); + } + } + + private string ParseServiceArgument(XElement node, string arguments) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string argument = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (argument == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + return (arguments == null) ? argument : String.Concat(arguments, "[~]", argument); + } + + /// + /// Parses a service dependency element. + /// + /// Element to parse. + /// Parsed sevice dependency name. + private string ParseServiceDependencyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string dependency = null; + var group = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Group": + group = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return group ? String.Concat("+", dependency) : dependency; + } + + /// + /// Parses a service install element. + /// + /// Element to parse. + /// Identifier of parent component. + /// + private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string account = null; + string arguments = null; + string dependencies = null; + string description = null; + string displayName = null; + var eraseDescription = false; + string loadOrderGroup = null; + string name = null; + string password = null; + + var serviceType = ServiceType.OwnProcess; + var startType = ServiceStartType.Demand; + var errorControl = ServiceErrorControl.Normal; + var interactive = false; + var vital = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Account": + account = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Arguments": + arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "EraseDescription": + eraseDescription = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ErrorControl": + var errorControlValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (errorControlValue) + { + case "ignore": + errorControl = ServiceErrorControl.Ignore; + break; + case "normal": + errorControl = ServiceErrorControl.Normal; + break; + case "critical": + errorControl = ServiceErrorControl.Critical; + break; + case "": // error case handled by GetAttributeValue() + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical")); + break; + } + break; + case "Interactive": + interactive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "LoadOrderGroup": + loadOrderGroup = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Password": + password = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Start": + var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (startValue) + { + case "auto": + startType = ServiceStartType.Auto; + break; + case "demand": + startType = ServiceStartType.Demand; + break; + case "disabled": + startType = ServiceStartType.Disabled; + break; + case "boot": + case "system": + this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue)); + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled")); + break; + } + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "ownProcess": + serviceType = ServiceType.OwnProcess; + break; + case "shareProcess": + serviceType = ServiceType.ShareProcess; + break; + case "kernelDriver": + case "systemDriver": + this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue)); + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess")); + break; + } + break; + case "Vital": + vital = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (0 == startType) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start")); + } + + if (eraseDescription) + { + description = "[~]"; + } + + // get the ServiceInstall dependencies and config + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "ServiceInstall"); + break; + case "ServiceConfig": + this.ParseServiceConfigElement(child, componentId, name); + break; + case "ServiceConfigFailureActions": + this.ParseServiceConfigFailureActionsElement(child, componentId, name); + break; + case "ServiceDependency": + dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "ServiceInstallId", id?.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (null != dependencies) + { + dependencies = String.Concat(dependencies, "[~]"); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ServiceInstallSymbol(sourceLineNumbers, id) + { + Name = name, + DisplayName = displayName, + ServiceType = serviceType, + StartType = startType, + ErrorControl = errorControl, + LoadOrderGroup = loadOrderGroup, + Dependencies = dependencies, + StartName = account, + Password = password, + Arguments = arguments, + ComponentRef = componentId, + Description = description, + Interactive = interactive, + Vital = vital + }); + } + } + + /// + /// Parses a SetDirectory element. + /// + /// Element to parse. + private void ParseSetDirectoryElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string actionName = null; + string id = null; + string condition = null; + var executionType = CustomActionExecutionType.Immediate; + var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); + break; + case "Sequence": + var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sequenceValue) + { + case "execute": + sequences = new[] { SequenceTable.InstallExecuteSequence }; + break; + case "first": + executionType = CustomActionExecutionType.FirstSequence; + break; + case "ui": + sequences = new[] { SequenceTable.InstallUISequence }; + break; + case "both": + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (String.IsNullOrEmpty(actionName)) + { + actionName = String.Concat("Set", id); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, actionName)) + { + ExecutionType = executionType, + SourceType = CustomActionSourceType.Directory, + TargetType = CustomActionTargetType.TextData, + Source = id, + Target = value + }); + + foreach (var sequence in sequences) + { + this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Public, sequence, actionName, condition, afterAction: "CostInitialize"); + } + } + } + + /// + /// Parses a SetProperty element. + /// + /// Element to parse. + private void ParseSetPropertyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string actionName = null; + string id = null; + string condition = null; + string afterAction = null; + string beforeAction = null; + var executionType = CustomActionExecutionType.Immediate; + var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + afterAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Before": + beforeAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Sequence": + var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sequenceValue) + { + case "execute": + sequences = new[] { SequenceTable.InstallExecuteSequence }; + break; + case "first": + executionType = CustomActionExecutionType.FirstSequence; + break; + case "ui": + sequences = new[] { SequenceTable.InstallUISequence }; + break; + case "both": + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (String.IsNullOrEmpty(actionName)) + { + actionName = String.Concat("Set", id); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + if (null != beforeAction && null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before")); + } + else if (null == beforeAction && null == afterAction) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id")); + } + + this.Core.ParseForExtensionElements(node); + + // add the row and any references needed + if (!this.Core.EncounteredError) + { + // action that is scheduled to occur before/after itself + if (beforeAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction)); + } + else if (afterAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction)); + } + + this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Public, actionName)) + { + ExecutionType = executionType, + SourceType = CustomActionSourceType.Property, + TargetType = CustomActionTargetType.TextData, + Source = id, + Target = value, + }); + + foreach (var sequence in sequences) + { + this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Public, sequence, actionName, condition, beforeAction, afterAction); + } + } + } + + /// + /// Parses a SFP catalog element. + /// + /// Element to parse. + /// Parent SFPCatalog. + private void ParseSFPFileElement(XElement node, string parentSFPCatalog) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new FileSFPCatalogSymbol(sourceLineNumbers) + { + FileRef = id, + SFPCatalogRef = parentSFPCatalog + }); + } + } + + /// + /// Parses a SFP catalog element. + /// + /// Element to parse. + /// Parent SFPCatalog. + private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string parentName = null; + string dependency = null; + string name = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Dependency": + dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + parentSFPCatalog = name; + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "SFPCatalog": + this.ParseSFPCatalogElement(child, ref parentName); + if (null != dependency && parentName == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); + } + dependency = parentName; + break; + case "SFPFile": + this.ParseSFPFileElement(child, name); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new SFPCatalogSymbol(sourceLineNumbers) + { + SFPCatalog = name, + Catalog = sourceFile, + Dependency = dependency + }); + } + } + + /// + /// Parses a shortcut element. + /// + /// Element to parse. + /// Identifer for parent component. + /// Local name of parent element. + /// Default identifier of parent (which is usually the target). + /// Flag to indicate whether the parent element is the keypath of a component or not (will only be true for file parent elements). + private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var advertise = false; + string arguments = null; + string description = null; + string descriptionResourceDll = null; + int? descriptionResourceId = null; + string directory = null; + string displayResourceDll = null; + int? displayResourceId = null; + int? hotkey = null; + string icon = null; + int? iconIndex = null; + string name = null; + string shortName = null; + ShortcutShowType? show = null; + string target = null; + string workingDirectory = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Advertise": + advertise = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Arguments": + arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DescriptionResourceDll": + descriptionResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DescriptionResourceId": + descriptionResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Directory": + directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); + break; + case "DisplayResourceDll": + displayResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayResourceId": + displayResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Hotkey": + hotkey = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Icon": + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); + break; + case "IconIndex": + iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "Show": + var showValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (showValue) + { + case "normal": + show = ShortcutShowType.Normal; + break; + case "maximized": + show = ShortcutShowType.Maximized; + break; + case "minimized": + show = ShortcutShowType.Minimized; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); + break; + } + break; + case "Target": + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "WorkingDirectory": + workingDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (advertise && null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes")); + } + + if (null == directory) + { + if ("Component" == parentElementLocalName) + { + directory = defaultTarget; + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component")); + } + } + + if (null != descriptionResourceDll) + { + if (!descriptionResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId")); + } + } + else + { + if (descriptionResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll")); + } + } + + if (null != displayResourceDll) + { + if (!displayResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId")); + } + } + else + { + if (displayResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll")); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if ("Component" != parentElementLocalName && null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName)); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("sct", directory, LowercaseOrNull(name)); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Icon": + icon = this.ParseIconElement(child); + break; + case "ShortcutProperty": + this.ParseShortcutPropertyElement(child, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (advertise) + { + if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName) + { + this.Core.Write(WarningMessages.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget)); + } + + target = Guid.Empty.ToString("B"); + } + else if (null != target) + { + } + else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) + { + target = "[" + defaultTarget + "]"; + } + else if ("File" == parentElementLocalName) + { + target = "[#" + defaultTarget + "]"; + } + + this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) + { + DirectoryRef = directory, + Name = name, + ShortName = shortName, + ComponentRef = componentId, + Target = target, + Arguments = arguments, + Description = description, + Hotkey = hotkey, + IconRef = icon, + IconIndex = iconIndex, + Show = show, + WorkingDirectory = workingDirectory, + DisplayResourceDll = displayResourceDll, + DisplayResourceId = displayResourceId, + DescriptionResourceDll = descriptionResourceDll, + DescriptionResourceId = descriptionResourceId, + }); + } + } + + /// + /// Parses a shortcut property element. + /// + /// Element to parse. + /// + private void ParseShortcutPropertyElement(XElement node, string shortcutId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.VerifyNoInnerText(sourceLineNumbers, node); + + if (String.IsNullOrEmpty(key)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + else if (null == id) + { + id = this.Core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant()); + } + + if (String.IsNullOrEmpty(value)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiShortcutPropertySymbol(sourceLineNumbers, id) + { + ShortcutRef = shortcutId, + PropertyKey = key, + PropVariantValue = value + }); + } + } + + /// + /// Parses a typelib element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of file that acts as typelib server. + /// true if the component is 64-bit. + private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var advertise = YesNoType.NotSet; + var cost = CompilerConstants.IntegerNotSet; + string description = null; + var flags = 0; + string helpDirectory = null; + var language = CompilerConstants.IntegerNotSet; + var majorVersion = CompilerConstants.IntegerNotSet; + var minorVersion = CompilerConstants.IntegerNotSet; + var resourceId = CompilerConstants.LongNotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Advertise": + advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Control": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 2; + } + break; + case "Cost": + cost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HasDiskImage": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 8; + } + break; + case "HelpDirectory": + helpDirectory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); + break; + case "Hidden": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 4; + } + break; + case "Language": + language = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "MajorVersion": + majorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, UInt16.MaxValue); + break; + case "MinorVersion": + minorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); + break; + case "ResourceId": + resourceId = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, Int32.MinValue, Int32.MaxValue); + break; + case "Restricted": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 1; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (CompilerConstants.IntegerNotSet == language) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); + language = CompilerConstants.IllegalInteger; + } + + // build up the typelib version string for the registry if the major or minor version was specified + string registryVersion = null; + if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) + { + if (CompilerConstants.IntegerNotSet != majorVersion) + { + registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat); + } + else + { + registryVersion = "0"; + } + + if (CompilerConstants.IntegerNotSet != minorVersion) + { + registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat)); + } + else + { + registryVersion = String.Concat(registryVersion, ".0"); + } + } + + // if the advertise state has not been set, default to non-advertised + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AppId": + this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion); + break; + case "Class": + this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null); + break; + case "Interface": + this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (YesNoType.Yes == advertise) + { + if (CompilerConstants.LongNotSet != resourceId) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId")); + } + + if (0 != flags) + { + if (0x1 == (flags & 0x1)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes")); + } + + if (0x2 == (flags & 0x2)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes")); + } + + if (0x4 == (flags & 0x4)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes")); + } + + if (0x8 == (flags & 0x8)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes")); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new TypeLibSymbol(sourceLineNumbers) + { + LibId = id, + Language = language, + ComponentRef = componentId, + Description = description, + DirectoryRef = helpDirectory, + FeatureRef = Guid.Empty.ToString("B") + }); + + if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) + { + symbol.Version = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0); + } + + if (CompilerConstants.IntegerNotSet != cost) + { + symbol.Cost = cost; + } + } + } + else if (YesNoType.No == advertise) + { + if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no")); + } + + if (null == fileServer) + { + this.Core.Write(ErrorMessages.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File")); + } + + if (null == registryVersion) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no")); + } + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId); + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId] + var path = String.Concat("[#", fileServer, "]"); + if (CompilerConstants.LongNotSet != resourceId) + { + path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat)); + } + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId); + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId); + + if (null != helpDirectory) + { + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectory, "]"), componentId); + } + } + } + + /// + /// Parses an upgrade element. + /// + /// Element to parse. + private void ParseUpgradeElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + // process the UpgradeVersion children here + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + + switch (child.Name.LocalName) + { + case "Property": + this.ParsePropertyElement(child); + this.Core.Write(WarningMessages.DeprecatedUpgradeProperty(childSourceLineNumbers)); + break; + case "UpgradeVersion": + this.ParseUpgradeVersionElement(child, id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + // No rows created here. All row creation is done in ParseUpgradeVersionElement. + } + + /// + /// Parse upgrade version element. + /// + /// Element to parse. + /// Upgrade code. + private void ParseUpgradeVersionElement(XElement node, string upgradeId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + string actionProperty = null; + string language = null; + string maximum = null; + string minimum = null; + var excludeLanguages = false; + var ignoreFailures = false; + var includeMax = false; + var includeMin = true; + var migrateFeatures = false; + var onlyDetect = false; + string removeFeatures = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ExcludeLanguages": + excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IgnoreRemoveFailure": + ignoreFailures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMaximum": + includeMax = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMinimum": // this is "yes" by default + includeMin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Language": + language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Minimum": + minimum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Maximum": + maximum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "MigrateFeatures": + migrateFeatures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnlyDetect": + onlyDetect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Property": + actionProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "RemoveFeatures": + removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == actionProperty) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty) + { + this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty)); + } + + if (null == minimum && null == maximum) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeId, + VersionMin = minimum, + VersionMax = maximum, + Language = language, + ExcludeLanguages = excludeLanguages, + IgnoreRemoveFailures = ignoreFailures, + VersionMaxInclusive = includeMax, + VersionMinInclusive = includeMin, + MigrateFeatures = migrateFeatures, + OnlyDetect = onlyDetect, + Remove = removeFeatures, + ActionProperty = actionProperty + }); + + // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence + // if at least one row in Upgrade table lacks the OnlyDetect attribute. + if (!onlyDetect) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", "RemoveExistingProducts"); + } + } + } + + /// + /// Parses a verb element. + /// + /// Element to parse. + /// Extension verb is releated to. + /// Optional progId for extension. + /// Identifier for parent component. + /// Flag if verb is advertised. + private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string argument = null; + string command = null; + var sequence = CompilerConstants.IntegerNotSet; + string targetFile = null; + string targetProperty = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Argument": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Command": + command = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Sequence": + sequence = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "TargetFile": + targetFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, targetFile); + break; + case "TargetProperty": + targetProperty = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null != targetFile && null != targetProperty) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty")); + } + + this.Core.ParseForExtensionElements(node); + + if (YesNoType.Yes == advertise) + { + if (null != targetFile) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile")); + } + + if (null != targetProperty) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty")); + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new VerbSymbol(sourceLineNumbers) + { + ExtensionRef = extension, + Verb = id, + Command = command, + Argument = argument, + }); + + if (CompilerConstants.IntegerNotSet != sequence) + { + symbol.Sequence = sequence; + } + } + } + else if (YesNoType.No == advertise) + { + if (CompilerConstants.IntegerNotSet != sequence) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no")); + } + + if (null == targetFile && null == targetProperty) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no")); + } + + string target = null; + if (null != targetFile) + { + target = String.Concat("\"[#", targetFile, "]\""); + } + else if (null != targetProperty) + { + target = String.Concat("\"[", targetProperty, "]\""); + } + + if (null != argument) + { + target = String.Concat(target, " ", argument); + } + + var prefix = progId ?? String.Concat(".", extension); + + if (null != command) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId); + } + } + + /// + /// Parses a WixVariable element. + /// + /// Element to parse. + private void ParseWixVariableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var overridable = false; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Overridable": + overridable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixVariableSymbol(sourceLineNumbers, id) + { + Value = value, + Overridable = overridable + }); + } + } + + private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XElement node, XAttribute attribute) + { + var compressionLevel = this.Core.GetAttributeValue(sourceLineNumbers, attribute); + switch (compressionLevel) + { + case "high": + return CompressionLevel.High; + case "low": + return CompressionLevel.Low; + case "medium": + return CompressionLevel.Medium; + case "mszip": + return CompressionLevel.Mszip; + case "none": + return CompressionLevel.None; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalCompressionLevel(sourceLineNumbers, compressionLevel)); + break; + } + + return null; + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs new file mode 100644 index 00000000..14eb8ff7 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs @@ -0,0 +1,30 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class DependencyExtensionFixture + { + [Fact] + public void CanBuildUsingProvides() + { + var folder = TestData.Get(@"TestData\UsingProvides"); + var build = new Builder(folder, null, new[] { folder }); + + var results = build.BuildAndQuery(Build, "WixDependencyProvider"); + Assert.Equal(new[] + { + "WixDependencyProvider:dep74OfIcniaqxA7EprRGBw4Oyy3r8\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tUsingProvides\t\t\t", + }, results); + } + + private static void Build(string[] args) + { + var result = WixRunner.Execute(args) + .AssertSuccess(); + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs new file mode 100644 index 00000000..07da1215 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs new file mode 100644 index 00000000..7e459e9a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file -- cgit v1.2.3-55-g6feb From 697f2cdbdcd8198d06ebf14fc2b65f0ce3fa5a94 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 1 Mar 2021 10:12:40 -0800 Subject: TARGETDIR is the default directory root, so allow it --- src/WixToolset.Core/Compiler.cs | 5 ----- .../TestData/SimpleModule/Module.wxs | 12 +++++------- 2 files changed, 5 insertions(+), 12 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index ac99a8a1..384f8795 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -4226,11 +4226,6 @@ namespace WixToolset.Core { this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); } - - if (null == parentId) - { - this.Core.Write(ErrorMessages.DirectoryRootWithoutName(sourceLineNumbers, node.Name.LocalName, "Name")); - } } else if (!String.IsNullOrEmpty(name)) { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs index 737ac8df..9a523162 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs @@ -1,15 +1,13 @@ - + - - + - - - + + + - -- cgit v1.2.3-55-g6feb From 3eb3c26c796984b64365fda077f173af8bf31559 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 1 Mar 2021 10:14:38 -0800 Subject: Fix handling of suppress modularization Partially fixes wixtoolset/issues#5944 --- .../Bind/ModularizeCommand.cs | 2 +- src/WixToolset.Core/Compiler.cs | 12 ++- src/WixToolset.Core/Compiler_Package.cs | 5 +- .../ModuleFixture.cs | 108 +++++++++++++++++++++ .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 50 ---------- .../SuppressModularization/Module.en-us.wxl | 10 ++ .../TestData/SuppressModularization/Module.wxs | 10 ++ .../TestData/SuppressModularization/data/test.txt | 1 + 8 files changed, 143 insertions(+), 55 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs index 66ca502d..49ef1adf 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs @@ -21,7 +21,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.ModularizationSuffix = modularizationSuffix; // Gather all the unique suppress modularization identifiers. - this.SuppressModularizationIdentifiers = new HashSet(suppressSymbols.Select(s => s.Id.Id)); + this.SuppressModularizationIdentifiers = new HashSet(suppressSymbols.Select(s => s.SuppressIdentifier)); } private WindowsInstallerData Output { get; } diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 384f8795..7113c3b5 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -728,14 +728,17 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { - var symbol = this.Core.AddSymbol(new BinarySymbol(sourceLineNumbers, id) + this.Core.AddSymbol(new BinarySymbol(sourceLineNumbers, id) { Data = new IntermediateFieldPathValue { Path = sourceFile } }); if (YesNoType.Yes == suppressModularization) { - this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers, id)); + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) + { + SuppressIdentifier = id.Id + }); } } @@ -3502,7 +3505,10 @@ namespace WixToolset.Core if (YesNoType.Yes == suppressModularization) { - this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers, id)); + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) + { + SuppressIdentifier = id.Id + }); } } } diff --git a/src/WixToolset.Core/Compiler_Package.cs b/src/WixToolset.Core/Compiler_Package.cs index d2728e9c..7a842ef0 100644 --- a/src/WixToolset.Core/Compiler_Package.cs +++ b/src/WixToolset.Core/Compiler_Package.cs @@ -1515,7 +1515,10 @@ namespace WixToolset.Core { this.Core.Write(WarningMessages.PropertyModularizationSuppressed(sourceLineNumbers)); - this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers, id)); + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) + { + SuppressIdentifier = id.Id + }); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs new file mode 100644 index 00000000..349bad2c --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs @@ -0,0 +1,108 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class ModuleFixture + { + [Fact] + public void CanBuildSimpleModule() + { + var folder = TestData.Get(@"TestData\SimpleModule"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Module.wxs"), + "-loc", Path.Combine(folder, "Module.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msm") + }); + + result.AssertSuccess(); + + var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); + Assert.True(File.Exists(msmPath)); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().OrderBy(d => d.Id.Id).ToList(); + WixAssert.CompareLineByLine(new[] + { + "MergeRedirectFolder\tTARGETDIR\t.", + "TARGETDIR\t\tSourceDir" + }, dirSymbols.Select(d => String.Join("\t", d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal("filyIq8rqcxxf903Hsn5K9L0SWV73g", fileSymbol.Id.Id); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + + var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var fileRows = data.Tables["File"].Rows; + Assert.Equal(new[] + { + "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" + }, fileRows.Select(r => r.FieldAsString(0)).ToArray()); + + var cabPath = Path.Combine(intermediateFolder, "msm-test.cab"); + Query.ExtractStream(msmPath, "MergeModule.CABinet", cabPath); + var files = Query.GetCabinetFiles(cabPath); + Assert.Equal(new[] + { + "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" + }, files.Select(f => Path.Combine(f.Path, f.Name)).ToArray()); + } + } + + [Fact] + public void CanSuppressModularization() + { + var folder = TestData.Get(@"TestData\SuppressModularization"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Module.wxs"), + "-loc", Path.Combine(folder, "Module.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-sw1079", + "-sw1086", + "-o", Path.Combine(intermediateFolder, @"bin\test.msm") + }); + + result.AssertSuccess(); + + var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); + + var rows = Query.QueryDatabase(msmPath, new[] { "CustomAction", "Property" }); + WixAssert.CompareLineByLine(new[] + { + "CustomAction:Test\t11265\tFakeCA.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tTestEntry\t", + "Property:MsiHiddenProperties\tTest" + }, rows); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index e26e197f..a8ea0a18 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -326,56 +326,6 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact] - public void CanBuildSimpleModule() - { - var folder = TestData.Get(@"TestData\SimpleModule"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Module.wxs"), - "-loc", Path.Combine(folder, "Module.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msm") - }); - - result.AssertSuccess(); - - var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); - Assert.True(File.Exists(msmPath)); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal("filyIq8rqcxxf903Hsn5K9L0SWV73g", fileSymbol.Id.Id); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - - var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var fileRows = data.Tables["File"].Rows; - Assert.Equal(new[] - { - "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" - }, fileRows.Select(r => r.FieldAsString(0)).ToArray()); - - var cabPath = Path.Combine(intermediateFolder, "msm-test.cab"); - Query.ExtractStream(msmPath, "MergeModule.CABinet", cabPath); - var files = Query.GetCabinetFiles(cabPath); - Assert.Equal(new[] - { - "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" - }, files.Select(f => Path.Combine(f.Path, f.Name)).ToArray()); - } - } - [Fact] public void CanBuildManualUpgrade() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl new file mode 100644 index 00000000..c74e86a7 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl @@ -0,0 +1,10 @@ + + + + + + Example Company + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs new file mode 100644 index 00000000..f4ce9c48 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file -- cgit v1.2.3-55-g6feb From 7eab295351796e2b41c1805d027957e1a7d9ddc6 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 1 Mar 2021 23:34:57 -0800 Subject: Correctly set the parent SourceLineNumber for include files --- src/WixToolset.Core/Preprocessor.cs | 4 +-- .../PreprocessorFixture.cs | 40 ++++++++++++++++++++++ .../TestData/IncludePath/Package.wxs | 2 +- .../TestData/IncludePath/PackageComponents.wxs | 4 +-- .../TestData/IncludePath/data/DontDoThis.wxi | 6 ++++ 5 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs index b111b291..81b17578 100644 --- a/src/WixToolset.Core/Preprocessor.cs +++ b/src/WixToolset.Core/Preprocessor.cs @@ -1351,7 +1351,7 @@ namespace WixToolset.Core if (state.Context.CurrentSourceLineNumber.LineNumber != newLine) { - state.Context.CurrentSourceLineNumber = new SourceLineNumber(state.Context.CurrentSourceLineNumber.FileName, newLine); + state.Context.CurrentSourceLineNumber = new SourceLineNumber(state.Context.CurrentSourceLineNumber.FileName, state.Context.CurrentSourceLineNumber.Parent, newLine); } } } @@ -1372,7 +1372,7 @@ namespace WixToolset.Core state.CurrentFileStack.Push(path); state.SourceStack.Push(state.Context.CurrentSourceLineNumber); - state.Context.CurrentSourceLineNumber = new SourceLineNumber(path); + state.Context.CurrentSourceLineNumber = new SourceLineNumber(path, state.Context.CurrentSourceLineNumber); state.IncludeNextStack.Push(true); } diff --git a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs index aad3ed73..89057991 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs @@ -8,6 +8,7 @@ namespace WixToolsetTest.CoreIntegration using WixToolset.Core; using WixToolset.Core.TestPackage; using WixToolset.Data; + using WixToolset.Data.Symbols; using WixToolset.Extensibility.Data; using Xunit; @@ -39,6 +40,45 @@ namespace WixToolsetTest.CoreIntegration Assert.Null(includedFile.SourceLineNumbers.Parent); } + [Fact] + public void IncludeSourceLineNumbersPreserved() + { + var folder = TestData.Get(@"TestData\IncludePath"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(warningsAsErrors: false, new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-includepath", Path.Combine(folder, "data"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + using (var output = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) + { + var intermediate = Intermediate.Load(output); + var component = intermediate.Sections.Single().Symbols.OfType().Single(); + Assert.Equal(3, component.SourceLineNumbers.LineNumber); + Assert.Equal(5, component.SourceLineNumbers.Parent.LineNumber); + + var encoded = component.SourceLineNumbers.GetEncoded(); + var decoded = SourceLineNumber.CreateFromEncoded(encoded); + Assert.Equal(3, decoded.LineNumber); + Assert.Equal(5, decoded.Parent.LineNumber); + } + } + } + [Fact] /// /// This test will fail on 32-bit operating systems because it depends on "CommonProgramFiles(x86)" diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs index 6269fe9d..48a38e85 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs @@ -1,4 +1,4 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs index e26c4509..7a0485ed 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs @@ -2,9 +2,7 @@ - - - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi new file mode 100644 index 00000000..03885e3e --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi @@ -0,0 +1,6 @@ + + + + + + -- cgit v1.2.3-55-g6feb From ed20ef6dc8caa5d585c1a715ff4ba577687bf291 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 27 Feb 2021 16:28:42 -0600 Subject: Add failing tests for package description and packages sharing payloads In Core projects, treat warnings as errors. --- src/Custom.Build.props | 6 + ...CreateBootstrapperApplicationManifestCommand.cs | 9 +- .../Bind/SequenceActionsCommand.cs | 1 + .../BundleFixture.cs | 6 + .../BundleManifestFixture.cs | 129 +++++++++++++++++++++ .../CustomPackageDescription.wxs | 12 ++ .../SharedPayloadsBetweenPackages.wxs | 18 +++ 7 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 src/Custom.Build.props create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/Custom.Build.props b/src/Custom.Build.props new file mode 100644 index 00000000..889fb62e --- /dev/null +++ b/src/Custom.Build.props @@ -0,0 +1,6 @@ + + + + true + + diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs index f85a5b62..a24137f3 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -211,7 +211,9 @@ namespace WixToolset.Core.Burn.Bundles private void WritePayloadInfo(XmlTextWriter writer) { - var payloadSymbols = this.Section.Symbols.OfType(); + // TODO: check v3 - should this be only include package payloads or include all non-UX container payloads? + var payloadSymbols = this.Section.Symbols.OfType() + .Where(p => !String.IsNullOrEmpty(p.PackageRef)); foreach (var payloadSymbol in payloadSymbols) { @@ -219,10 +221,7 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteAttributeString("Payload", payloadSymbol.Id.Id); - if (!String.IsNullOrEmpty(payloadSymbol.PackageRef)) - { - writer.WriteAttributeString("Package", payloadSymbol.PackageRef); - } + writer.WriteAttributeString("Package", payloadSymbol.PackageRef); if (!String.IsNullOrEmpty(payloadSymbol.ContainerRef)) { diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs index 056b129b..7d75d74c 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs @@ -569,6 +569,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// /// The action symbol to be sequenced. /// Collection of actions which must be included. + /// A dictionary used for detecting cyclic references among action symbols. private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) { var after = false; diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index 0660dd7b..2cc9a39e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -3,6 +3,7 @@ namespace WixToolsetTest.CoreIntegration { using System; + using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -115,6 +116,11 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal($"" + "" + "", registrationElement.GetTestXml()); + + var msiPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='test.msi']"); + var msiPayload = (XmlNode)Assert.Single(msiPayloads); + Assert.Equal("", + msiPayload.GetTestXml(new Dictionary>() { { "Payload", new List { "FileSize", "Hash" } } })); } var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs index ae83150a..3829cdf0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -51,6 +51,93 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesBAManifestWithPackageInformation() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "CustomPackageDescription", "CustomPackageDescription.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var packageElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPackageProperties"); + var ignoreAttributesByElementName = new Dictionary> + { + { "WixPackageProperties", new List { "DownloadSize", "PackageSize", "InstalledSize", "Version" } }, + }; + Assert.Equal(3, packageElements.Count); + Assert.Equal("", packageElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", packageElements[1].GetTestXml()); + Assert.Equal("", packageElements[2].GetTestXml(ignoreAttributesByElementName)); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesBAManifestWithPayloadInformation() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var payloadElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties"); + var ignoreAttributesByElementName = new Dictionary> + { + { "WixPayloadProperties", new List { "Size" } }, + }; + Assert.Equal(4, payloadElements.Count); + Assert.Equal("", payloadElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[1].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[2].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[3].GetTestXml(ignoreAttributesByElementName)); + } + } + [Fact] public void PopulatesBEManifestWithBundleExtensionBundleCustomData() { @@ -191,6 +278,48 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesManifestWithExePackages() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); + var ignoreAttributesByElementName = new Dictionary> + { + { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, + }; + Assert.Equal(2, exePackageElements.Count); + Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", exePackageElements[1].GetTestXml(ignoreAttributesByElementName)); + } + } + [Fact] public void PopulatesManifestWithSetVariables() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs new file mode 100644 index 00000000..db8b05f2 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs new file mode 100644 index 00000000..2588ffc1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From b54ac261d0a03b75cf05ef370351445774b82155 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 27 Feb 2021 16:29:58 -0600 Subject: Test big payloads by RemotePayload since real files are slow. The test will be moved to a Burn e2e test. #4008 --- src/WixToolset.Core/Compiler_Bundle.cs | 4 +- .../BundleFixture.cs | 63 ++++----------------- .../TestData/LargePayload/Bundle.en-us.wxl | 10 ---- .../TestData/LargePayload/Bundle.wxs | 13 ----- .../LargePayload/data/MsiPackage/Shared.dll | 1 - .../TestData/LargePayload/data/MsiPackage/test.txt | 1 - .../TestData/LargePayload/data/fakeba.dll | 1 - .../TestData/LargePayload/data/large_file.dat | 2 - .../TestData/LargePayload/data/test.msi | Bin 32768 -> 0 bytes .../SingleExeBundle/SingleExeRemotePayload.wxs | 2 +- 10 files changed, 14 insertions(+), 83 deletions(-) delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/Shared.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/fakeba.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/large_file.dat delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/test.msi (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index c790d721..189ac9b5 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -1447,7 +1447,7 @@ namespace WixToolset.Core remotePayload.ProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Size": - remotePayload.Size = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + remotePayload.Size = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, 0, Int64.MaxValue); break; case "Version": remotePayload.Version = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -3260,7 +3260,7 @@ namespace WixToolset.Core public string ProductName { get; set; } - public int Size { get; set; } + public long Size { get; set; } public string Version { get; set; } } diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index 2cc9a39e..38554b70 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -252,6 +252,7 @@ namespace WixToolsetTest.CoreIntegration var baseFolder = fs.GetFolder(); var intermediateFolder = Path.Combine(baseFolder, "obj"); var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); var result = WixRunner.Execute(new[] { @@ -266,6 +267,16 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); Assert.True(File.Exists(exePath)); + Assert.True(File.Exists(pdbPath)); + + using (var wixOutput = WixOutput.Read(pdbPath)) + { + var intermediate = Intermediate.Load(wixOutput); + var section = intermediate.Sections.Single(); + + var payloadSymbol = section.Symbols.OfType().Where(x => x.Id.Id == "NetFx462Web").Single(); + Assert.Equal(Int64.MaxValue, payloadSymbol.FileSize); + } } } @@ -372,57 +383,5 @@ namespace WixToolsetTest.CoreIntegration Assert.InRange(result.ExitCode, 2, Int32.MaxValue); } } - - [Fact] - public void CanBuildBundleWithLargePayload() - { - var folder = TestData.Get(@"TestData\LargePayload"); - - // Overwrite the payload with a 2.5 GiB file. We do this dynamically to avoid committing such - // a large file to source control. - var largeFile = Path.Combine(folder, "data", "large_file.dat"); - const long TwoAndAHalfGigabytes = 2_684_354_560; - using (var stream = File.Create(largeFile)) - { - stream.Seek(TwoAndAHalfGigabytes - 1, SeekOrigin.Begin); - stream.WriteByte(1); - } - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Bundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - Assert.Empty(result.Messages.Where(m => m.Level == MessageLevel.Warning)); - - Assert.True(File.Exists(exePath)); - Assert.True(File.Exists(pdbPath)); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\large_file.dat"))); - - using (var wixOutput = WixOutput.Read(pdbPath)) - { - var intermediate = Intermediate.Load(wixOutput); - var section = intermediate.Sections.Single(); - - var payloadSymbol = section.Symbols.OfType().Where(x => x.Name == "large_file.dat").Single(); - Assert.Equal(TwoAndAHalfGigabytes, payloadSymbol.FileSize); - } - } - - File.Delete(largeFile); - } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.en-us.wxl deleted file mode 100644 index bc1dee83..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.en-us.wxl +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - ~TestBundle - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.wxs deleted file mode 100644 index 5c7735b5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/Bundle.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/Shared.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/Shared.dll deleted file mode 100644 index 0e461ba8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/Shared.dll +++ /dev/null @@ -1 +0,0 @@ -This is Shared.dll. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/test.txt deleted file mode 100644 index 8b986220..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/MsiPackage/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/fakeba.dll deleted file mode 100644 index 970efdf0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/fakeba.dll +++ /dev/null @@ -1 +0,0 @@ -This is a fakeba.dll \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/large_file.dat b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/large_file.dat deleted file mode 100644 index 8115cc60..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/large_file.dat +++ /dev/null @@ -1,2 +0,0 @@ -When running the tests, this file will be overwritten with 2.5GB of data to test how Wix handles large files. We've avoided -committing such a large file to Git as it would bloat the repo. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/test.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/test.msi deleted file mode 100644 index 0722d60e..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/LargePayload/data/test.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs index fcb9dd8d..79ba52d2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs @@ -21,7 +21,7 @@ Description="Microsoft .NET Framework 4.6.2 Setup" Hash="C42E6ED280290648BBD59F664008852F4CFE4548" ProductName="Microsoft .NET Framework 4.6.2" - Size="1429344" + Size="9223372036854775807" Version="4.6.1590.0" /> -- cgit v1.2.3-55-g6feb From 8b3488c8c77959f425d0e5f70d27c5b2b1c86125 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 28 Feb 2021 21:05:49 -0600 Subject: Use new PackagePayload symbols for the package's payload. #4183 --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 7 +- .../Bind/GenerateManifestDataFromIRCommand.cs | 4 + .../Bundles/CreateBurnManifestCommand.cs | 5 +- .../Bundles/GetPackageFacadesCommand.cs | 111 ++++++++- src/WixToolset.Core/Compile/CompilerPayload.cs | 93 +++++-- src/WixToolset.Core/Compiler_Bundle.cs | 268 ++++++++++++++------- src/WixToolset.Core/Linker.cs | 2 +- .../PackagePayloadFixture.cs | 207 ++++++++++++++++ .../PackagePayload/MissingSourceFileAndHash.wxs | 10 + .../PackagePayload/MissingSourceFileAndName.wxs | 10 + .../PackagePayloadInPayloadGroup.wxs | 15 ++ .../TestData/PackagePayload/SpecifiedHash.wxs | 10 + .../SpecifiedHashAndMissingDownloadUrl.wxs | 10 + .../PackagePayload/SpecifiedSourceFileAndHash.wxs | 10 + .../WrongPackagePayloadInPayloadGroup.wxs | 15 ++ .../SingleExeBundle/SingleExeRemotePayload.wxs | 9 +- 16 files changed, 670 insertions(+), 116 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 724dd7ff..12a530ae 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -180,12 +180,17 @@ namespace WixToolset.Core.Burn IDictionary facades; { - var command = new GetPackageFacadesCommand(chainPackageSymbols, section); + var command = new GetPackageFacadesCommand(this.Messaging, chainPackageSymbols, section); command.Execute(); facades = command.PackageFacades; } + if (this.Messaging.EncounteredError) + { + return; + } + // Process each package facade. Note this is likely to add payloads and other symbols so // note that any indexes created above may be out of date now. foreach (var facade in facades.Values) diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs index 36ced6cf..d4a69513 100644 --- a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs @@ -66,12 +66,16 @@ namespace WixToolset.Core.Burn.Bind case SymbolDefinitionType.WixBundleContainer: case SymbolDefinitionType.WixBundleCustomDataAttribute: case SymbolDefinitionType.WixBundleExePackage: + case SymbolDefinitionType.WixBundleExePackagePayload: case SymbolDefinitionType.WixBundleExtension: case SymbolDefinitionType.WixBundleMsiFeature: case SymbolDefinitionType.WixBundleMsiPackage: + case SymbolDefinitionType.WixBundleMsiPackagePayload: case SymbolDefinitionType.WixBundleMsiProperty: case SymbolDefinitionType.WixBundleMspPackage: + case SymbolDefinitionType.WixBundleMspPackagePayload: case SymbolDefinitionType.WixBundleMsuPackage: + case SymbolDefinitionType.WixBundleMsuPackagePayload: case SymbolDefinitionType.WixBundlePackage: case SymbolDefinitionType.WixBundlePackageCommandLine: case SymbolDefinitionType.WixBundlePackageExitCode: diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 3bc6bf1b..1559a646 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -564,15 +564,16 @@ namespace WixToolset.Core.Burn.Bundles } // Write any contained Payloads with the PackagePayload being first + var packagePayloadId = package.PackageSymbol.PayloadRef; writer.WriteStartElement("PayloadRef"); - writer.WriteAttributeString("Id", package.PackageSymbol.PayloadRef); + writer.WriteAttributeString("Id", packagePayloadId); writer.WriteEndElement(); var packagePayloads = payloadsByPackage[package.PackageId]; foreach (var payload in packagePayloads) { - if (payload.Id.Id != package.PackageSymbol.PayloadRef) + if (payload.Id.Id != packagePayloadId) { writer.WriteStartElement("PayloadRef"); writer.WriteAttributeString("Id", payload.Id.Id); diff --git a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs index 24d1e8d8..dacff364 100644 --- a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs @@ -6,17 +6,21 @@ namespace WixToolset.Core.Burn.Bundles using System.Linq; using WixToolset.Data; using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; internal class GetPackageFacadesCommand { - public GetPackageFacadesCommand(IEnumerable chainPackageSymbols, IntermediateSection section) + public GetPackageFacadesCommand(IMessaging messaging, IEnumerable chainPackageSymbols, IntermediateSection section) { + this.Messaging = messaging; this.ChainPackageSymbols = chainPackageSymbols; this.Section = section; } private IEnumerable ChainPackageSymbols { get; } + private IMessaging Messaging { get; } + private IntermediateSection Section { get; } public IDictionary PackageFacades { get; private set; } @@ -27,12 +31,101 @@ namespace WixToolset.Core.Burn.Bundles var msiPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); var mspPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); var msuPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var exePackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var msiPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var mspPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var msuPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); var facades = new Dictionary(); foreach (var package in this.ChainPackageSymbols) { var id = package.Id.Id; + + IntermediateSymbol packagePayload = null; + foreach (var wixGroup in this.Section.Symbols.OfType().Where(g => g.ParentType == ComplexReferenceParentType.Package && g.ParentId == id)) + { + if (wixGroup.ChildType == ComplexReferenceChildType.PackagePayload) + { + IntermediateSymbol tempPackagePayload = null; + if (exePackagePayloads.TryGetValue(wixGroup.ChildId, out var exePackagePayload)) + { + if (package.Type == WixBundlePackageType.Exe) + { + tempPackagePayload = exePackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(exePackagePayload.SourceLineNumbers, "Exe")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else if (msiPackagePayloads.TryGetValue(wixGroup.ChildId, out var msiPackagePayload)) + { + if (package.Type == WixBundlePackageType.Msi) + { + tempPackagePayload = msiPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msiPackagePayload.SourceLineNumbers, "Msi")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else if (mspPackagePayloads.TryGetValue(wixGroup.ChildId, out var mspPackagePayload)) + { + if (package.Type == WixBundlePackageType.Msp) + { + tempPackagePayload = mspPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(mspPackagePayload.SourceLineNumbers, "Msp")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else if (msuPackagePayloads.TryGetValue(wixGroup.ChildId, out var msuPackagePayload)) + { + if (package.Type == WixBundlePackageType.Msu) + { + tempPackagePayload = msuPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msuPackagePayload.SourceLineNumbers, "Msu")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound(package.Type + "PackagePayload", wixGroup.ChildId)); + } + + if (tempPackagePayload != null) + { + if (packagePayload == null) + { + packagePayload = tempPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.MultiplePackagePayloads(tempPackagePayload.SourceLineNumbers, id, packagePayload.Id.Id, tempPackagePayload.Id.Id)); + this.Messaging.Write(ErrorMessages.MultiplePackagePayloads2(packagePayload.SourceLineNumbers)); + this.Messaging.Write(ErrorMessages.MultiplePackagePayloads3(package.SourceLineNumbers)); + } + } + } + } + + if (packagePayload == null) + { + this.Messaging.Write(ErrorMessages.MissingPackagePayload(package.SourceLineNumbers, id, package.Type.ToString())); + } + else + { + package.PayloadRef = packagePayload.Id.Id; + } + switch (package.Type) { case WixBundlePackageType.Exe: @@ -40,6 +133,10 @@ namespace WixToolset.Core.Burn.Bundles { facades.Add(id, new PackageFacade(package, exePackage)); } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleExePackage", id)); + } break; case WixBundlePackageType.Msi: @@ -47,6 +144,10 @@ namespace WixToolset.Core.Burn.Bundles { facades.Add(id, new PackageFacade(package, msiPackage)); } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsiPackage", id)); + } break; case WixBundlePackageType.Msp: @@ -54,6 +155,10 @@ namespace WixToolset.Core.Burn.Bundles { facades.Add(id, new PackageFacade(package, mspPackage)); } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMspPackage", id)); + } break; case WixBundlePackageType.Msu: @@ -61,6 +166,10 @@ namespace WixToolset.Core.Burn.Bundles { facades.Add(id, new PackageFacade(package, msuPackage)); } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsuPackage", id)); + } break; } } diff --git a/src/WixToolset.Core/Compile/CompilerPayload.cs b/src/WixToolset.Core/Compile/CompilerPayload.cs index 4eda56f8..7a5fd1b2 100644 --- a/src/WixToolset.Core/Compile/CompilerPayload.cs +++ b/src/WixToolset.Core/Compile/CompilerPayload.cs @@ -23,6 +23,8 @@ namespace WixToolset.Core public Identifier Id { get; set; } + public bool IsRemoteAllowed { get; set; } + public bool IsRequired { get; set; } = true; public string Name { get; set; } @@ -48,26 +50,17 @@ namespace WixToolset.Core private SourceLineNumber SourceLineNumbers { get; } - private void CalculateAndVerifyFields(CompilerPayload remotePayload = null) + private void CalculateAndVerifyFields() { + var isRemote = this.IsRemoteAllowed && !String.IsNullOrEmpty(this.Hash); + if (String.IsNullOrEmpty(this.SourceFile)) { - if (String.IsNullOrEmpty(this.Name)) - { - if (this.IsRequired) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile")); - } - } - else if (remotePayload == null) + if (!String.IsNullOrEmpty(this.Name) && !isRemote) { this.SourceFile = Path.Combine("SourceDir", this.Name); } } - else if (remotePayload != null) - { - this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "RemotePayload", "SourceFile")); - } else if (this.SourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { if (String.IsNullOrEmpty(this.Name)) @@ -80,24 +73,67 @@ namespace WixToolset.Core } } - if (remotePayload != null) + if (String.IsNullOrEmpty(this.SourceFile) && !isRemote) { - if (this.DownloadUrl == null) + if (this.IsRequired) { - this.Core.Write(ErrorMessages.ExpectedAttributeWithElement(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "RemotePayload")); + if (!this.IsRemoteAllowed) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile")); + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "Hash")); + } } + } + else if (this.IsRemoteAllowed) + { + var isLocal = !String.IsNullOrEmpty(this.SourceFile); - if (YesNoDefaultType.No != this.Compressed) + if (isLocal) + { + if (isRemote) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile")); + } + } + else { + if (String.IsNullOrEmpty(this.DownloadUrl)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "Hash")); + } + + if (String.IsNullOrEmpty(this.Name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "Hash")); + } + + if (YesNoDefaultType.Yes == this.Compressed) + { + this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName)); + } + this.Compressed = YesNoDefaultType.No; - this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName)); } - this.Description = remotePayload.Description; - this.DisplayName = remotePayload.DisplayName; - this.Hash = remotePayload.Hash; - this.Size = remotePayload.Size; - this.Version = remotePayload.Version; + VerifyValidValue("Description", !String.IsNullOrEmpty(this.Description)); + VerifyValidValue("ProductName", !String.IsNullOrEmpty(this.ProductName)); + VerifyValidValue("Size", this.Size.HasValue); + VerifyValidValue("Version", !String.IsNullOrEmpty(this.Version)); + + void VerifyValidValue(string attributeName, bool isSpecified) + { + if (isLocal && isSpecified) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, attributeName, "SourceFile")); + } + else if (!isLocal && !isSpecified) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, attributeName, "Hash")); + } + } } } @@ -143,9 +179,9 @@ namespace WixToolset.Core return symbol; } - public void FinishCompilingPackage(CompilerPayload remotePayload) + public void FinishCompilingPackage() { - this.CalculateAndVerifyFields(remotePayload); + this.CalculateAndVerifyFields(); this.GenerateIdFromFilename(); if (this.Id == null) @@ -155,6 +191,13 @@ namespace WixToolset.Core } } + public void FinishCompilingPackagePayload() + { + this.CalculateAndVerifyFields(); + this.GenerateIdFromFilename(); + this.GenerateIdFromPrefix("ppy"); + } + public void FinishCompilingPayload() { this.CalculateAndVerifyFields(); diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 64fe2acc..60db75d6 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -1451,71 +1451,6 @@ namespace WixToolset.Core return compilerPayload.Id; } - private CompilerPayload ParseRemotePayloadElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var remotePayload = new CompilerPayload(this.Core, sourceLineNumbers, node); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Description": - remotePayload.ParseDescription(attrib); - break; - case "Hash": - remotePayload.ParseHash(attrib); - break; - case "ProductName": - remotePayload.ParseProductName(attrib); - break; - case "Size": - remotePayload.ParseSize(attrib); - break; - case "Version": - remotePayload.ParseVersion(attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(remotePayload.ProductName)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductName")); - } - - if (String.IsNullOrEmpty(remotePayload.Description)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); - } - - if (String.IsNullOrEmpty(remotePayload.Hash)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Hash")); - } - - if (0 == remotePayload.Size) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Size")); - } - - if (String.IsNullOrEmpty(remotePayload.Version)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - - return remotePayload; - } - /// /// Parse PayloadGroup element. /// @@ -1561,8 +1496,21 @@ namespace WixToolset.Core { if (CompilerCore.WixNamespace == child.Name.Namespace) { + WixBundlePackageType? packageType = null; switch (child.Name.LocalName) { + case "ExePackagePayload": + packageType = WixBundlePackageType.Exe; + break; + case "MsiPackagePayload": + packageType = WixBundlePackageType.Msi; + break; + case "MspPackagePayload": + packageType = WixBundlePackageType.Msp; + break; + case "MsuPackagePayload": + packageType = WixBundlePackageType.Msu; + break; case "Payload": previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); previousType = ComplexReferenceChildType.Payload; @@ -1575,6 +1523,19 @@ namespace WixToolset.Core this.Core.UnexpectedElement(node, child); break; } + + if (packageType.HasValue) + { + var compilerPayload = this.ParsePackagePayloadElement(null, child, packageType.Value, null); + var payloadSymbol = compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.PayloadGroup, id?.Id, previousType, previousId?.Id); + if (payloadSymbol != null) + { + previousId = payloadSymbol.Id; + previousType = ComplexReferenceChildType.Payload; + + this.CreatePackagePayloadSymbol(payloadSymbol.SourceLineNumbers, packageType.Value, payloadSymbol.Id, ComplexReferenceParentType.PayloadGroup, id); + } + } } else { @@ -1984,7 +1945,7 @@ namespace WixToolset.Core Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);; + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) { IsRequired = false, @@ -2008,8 +1969,9 @@ namespace WixToolset.Core string msuKB = null; var enableFeatureSelection = YesNoType.NotSet; var forcePerMachine = YesNoType.NotSet; - CompilerPayload remotePayload = null; + CompilerPayload childPackageCompilerPayload = null; var slipstream = YesNoType.NotSet; + var hasPayloadInfo = false; var expectedNetFx4Args = new string[] { "/q", "/norestart", "/chainingpackage" }; @@ -2029,12 +1991,15 @@ namespace WixToolset.Core break; case "Name": compilerPayload.ParseName(attrib); + hasPayloadInfo = true; break; case "SourceFile": compilerPayload.ParseSourceFile(attrib); + hasPayloadInfo = true; break; case "DownloadUrl": compilerPayload.ParseDownloadUrl(attrib); + hasPayloadInfo = true; break; case "After": after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -2128,6 +2093,7 @@ namespace WixToolset.Core break; case "Compressed": compilerPayload.ParseCompressed(attrib); + hasPayloadInfo = true; break; case "Slipstream": slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); @@ -2150,26 +2116,30 @@ namespace WixToolset.Core } } - // We need to handle RemotePayload up front because it effects value of sourceFile which is used in Id generation. Id is needed by other child elements. - foreach (var child in node.Elements(CompilerCore.WixNamespace + "RemotePayload")) + // We need to handle the package payload up front because it affects Id generation. Id is needed by other child elements. + var packagePayloadElementName = packageType + "PackagePayload"; + foreach (var child in node.Elements(CompilerCore.WixNamespace + packagePayloadElementName)) { var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - if (CompilerCore.WixNamespace == node.Name.Namespace && node.Name.LocalName != "ExePackage" && node.Name.LocalName != "MsuPackage") + if (childPackageCompilerPayload != null) { - this.Core.Write(ErrorMessages.RemotePayloadUnsupported(childSourceLineNumbers)); - continue; + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); } - - if (null != remotePayload) + else if (hasPayloadInfo) { - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); + this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "SourceFile", "Name", "DownloadUrl", "Compressed")); } - remotePayload = this.ParseRemotePayloadElement(child); + childPackageCompilerPayload = this.ParsePackagePayloadElement(childSourceLineNumbers, child, packageType, compilerPayload.Id); + } + + if (compilerPayload.Id == null && childPackageCompilerPayload != null) + { + compilerPayload.Id = childPackageCompilerPayload.Id; } - compilerPayload.FinishCompilingPackage(remotePayload); + compilerPayload.FinishCompilingPackage(); var id = compilerPayload.Id; if (null == logPathVariable) @@ -2279,7 +2249,11 @@ namespace WixToolset.Core this.ParseCommandLineElement(child, id.Id); } break; - case "RemotePayload": + case "ExePackagePayload": + case "MsiPackagePayload": + case "MspPackagePayload": + case "MsuPackagePayload": + allowed = packagePayloadElementName == child.Name.LocalName; // Handled previously break; default: @@ -2301,8 +2275,13 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { - // We create the package contents as a payload with this package as the parent - compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id); + var packageCompilerPayload = childPackageCompilerPayload ?? (hasPayloadInfo ? compilerPayload : null); + if (packageCompilerPayload != null) + { + var payload = packageCompilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id); + + this.CreatePackagePayloadSymbol(sourceLineNumbers, packageType, payload.Id, ComplexReferenceParentType.Package, id); + } this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); @@ -2313,7 +2292,6 @@ namespace WixToolset.Core var chainPackageSymbol = this.Core.AddSymbol(new WixBundlePackageSymbol(sourceLineNumbers, id) { Type = packageType, - PayloadRef = id.Id, Attributes = attributes, InstallCondition = installCondition, CacheId = cacheId, @@ -2391,6 +2369,134 @@ namespace WixToolset.Core return id.Id; } + private void CreatePackagePayloadSymbol(SourceLineNumber sourceLineNumbers, WixBundlePackageType packageType, Identifier payloadId, ComplexReferenceParentType parentType, Identifier parentId) + { + switch (packageType) + { + case WixBundlePackageType.Exe: + this.Core.AddSymbol(new WixBundleExePackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + + case WixBundlePackageType.Msi: + this.Core.AddSymbol(new WixBundleMsiPackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + + case WixBundlePackageType.Msp: + this.Core.AddSymbol(new WixBundleMspPackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + + case WixBundlePackageType.Msu: + this.Core.AddSymbol(new WixBundleMsuPackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + } + + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PackagePayload, payloadId?.Id, ComplexReferenceChildType.Unknown, null); + } + + private CompilerPayload ParsePackagePayloadElement(SourceLineNumber sourceLineNumbers, XElement node, WixBundlePackageType packageType, Identifier defaultId) + { + sourceLineNumbers = sourceLineNumbers ?? Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) + { + Id = defaultId, + IsRemoteAllowed = packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu, + }; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + var allowed = true; + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Compressed": + compilerPayload.ParseCompressed(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + case "DownloadUrl": + compilerPayload.ParseDownloadUrl(attrib); + break; + case "Description": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseDescription(attrib); + } + break; + case "Hash": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseHash(attrib); + } + break; + case "ProductName": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseProductName(attrib); + } + break; + case "Size": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseSize(attrib); + } + break; + case "Version": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseVersion(attrib); + } + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedAttribute(node, attrib); + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + compilerPayload.FinishCompilingPackagePayload(); + + // Now that the PayloadId is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); + } + + this.Core.ParseForExtensionElements(node); + + return compilerPayload; + } + /// /// Parse CommandLine element. /// diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index e0af89ba..41e0db7d 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -1242,7 +1242,7 @@ namespace WixToolset.Core var groups = new WixGroupingOrdering(entrySection, this.Messaging); // Create UX payloads and Package payloads - groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, new[] { ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload }); + groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, new[] { ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload }); groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Package, false); groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Container, false); groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Layout, false); diff --git a/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs new file mode 100644 index 00000000..77a21f61 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs @@ -0,0 +1,207 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class PackagePayloadFixture + { + [Fact] + public void CanSpecifyPackagePayloadInPayloadGroup() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackagePayload", "PackagePayloadInPayloadGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); + var ignoreAttributesByElementName = new Dictionary> + { + { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, + }; + Assert.Equal(1, exePackageElements.Count); + Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); + } + } + + [Fact] + public void ErrorWhenMissingSourceFileAndHash() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "MissingSourceFileAndHash.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(44, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MsuPackagePayload element's SourceFile or Hash attribute was not found; one of these is required.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenMissingSourceFileAndName() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "MissingSourceFileAndName.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(44, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MsiPackagePayload element's Name or SourceFile attribute was not found; one of these is required.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenSpecifiedHash() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SpecifiedHash.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(4, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MspPackagePayload element contains an unexpected attribute 'Hash'.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenSpecifiedHashAndMissingDownloadUrl() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SpecifiedHashAndMissingDownloadUrl.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(10, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MsuPackagePayload/@DownloadUrl attribute was not found; it is required when attribute Hash is specified.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenSpecifiedSourceFileAndHash() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SpecifiedSourceFileAndHash.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(35, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The ExePackagePayload/@Hash attribute cannot be specified when attribute SourceFile is present.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenWrongPackagePayloadInPayloadGroup() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackagePayload", "WrongPackagePayloadInPayloadGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + Assert.Equal(407, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The ExePackagePayload element can only be used for ExePackages.", + "The location of the package related to previous error.", + "There is no payload defined for package 'WrongPackagePayloadInPayloadGroup'. This is specified on the MsiPackage element or a child MsiPackagePayload element.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs new file mode 100644 index 00000000..5e1b99ff --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs new file mode 100644 index 00000000..f220d81a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs new file mode 100644 index 00000000..149870a4 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs new file mode 100644 index 00000000..3c361c49 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs new file mode 100644 index 00000000..8e62f660 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs new file mode 100644 index 00000000..f79da874 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs new file mode 100644 index 00000000..dda306cf --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs index 79ba52d2..56f08ba9 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs @@ -13,11 +13,10 @@ Vital="yes" Permanent="yes" Protocol="netfx4" - DownloadUrl="C" - LogPathVariable="NetFx462FullLog" - Compressed="no" - Name="NDP462-KB3151802-Web.exe"> - + Date: Tue, 2 Mar 2021 15:22:42 -0600 Subject: Don't recommend "/ChainingPackage" for netfx4 protocol ExePackages. #5762 --- src/WixToolset.Core/Compiler_Bundle.cs | 2 +- .../TestData/SingleExeBundle/SingleExeRemotePayload.wxs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 60db75d6..46b79484 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -1973,7 +1973,7 @@ namespace WixToolset.Core var slipstream = YesNoType.NotSet; var hasPayloadInfo = false; - var expectedNetFx4Args = new string[] { "/q", "/norestart", "/chainingpackage" }; + var expectedNetFx4Args = new string[] { "/q", "/norestart" }; // This list lets us evaluate extension attributes *after* all core attributes // have been parsed and dealt with, regardless of authoring order. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs index 56f08ba9..0d459f02 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs @@ -3,9 +3,8 @@ Date: Tue, 2 Mar 2021 11:26:55 -0800 Subject: Add test to verify patches without files work Resolves wixtoolset/issues#4912 --- .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 32 ++++++++++++++++++++++ .../TestData/PatchNoFileChanges/.data/A.txt | 1 + .../TestData/PatchNoFileChanges/Package.wxs | 25 +++++++++++++++++ .../TestData/PatchNoFileChanges/Patch.wxs | 16 +++++++++++ 4 files changed, 74 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs index dda4ca28..788cc01f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs @@ -52,6 +52,38 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanBuildSimplePatchWithNoFileChanges() + { + var folder = TestData.Get(@"TestData\PatchNoFileChanges"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1", hasNoFiles: true); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + Assert.True(File.Exists(baselinePdb)); + Assert.True(File.Exists(update1Pdb)); + + var doc = GetExtractPatchXml(patchPath); + Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + + var names = Query.GetSubStorageNames(patchPath); + Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); + + var cab = Path.Combine(tempFolder, "foo.cab"); + Query.ExtractStream(patchPath, "foo.cab", cab); + Assert.True(File.Exists(cab)); + + var files = Query.GetCabinetFiles(cab); + Assert.Empty(files); + } + } + [Fact] public void CanBuildBundleWithNonSpecificPatches() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt @@ -0,0 +1 @@ +This is A v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs new file mode 100644 index 00000000..9d530376 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs new file mode 100644 index 00000000..889b1220 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 816bfd180f132a9b07aaa573f5ac0f5948195764 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 3 Mar 2021 10:37:34 -0800 Subject: Complete MOVE_TO_BACKEND code migration Fixes wixtoolset/issues#6212 --- .../Bind/BindDatabaseCommand.cs | 3 +- .../CreateWindowsInstallerDataFromIRCommand.cs | 296 +++++++++++++- .../Bind/OptimizeFileFacadesOrderCommand.cs | 12 +- .../Bind/ProcessDependencyReferencesCommand.cs | 2 +- src/WixToolset.Core/Compiler.cs | 4 +- src/WixToolset.Core/Compiler_Package.cs | 2 +- src/WixToolset.Core/Linker.cs | 435 ++------------------- .../MsiQueryFixture.cs | 2 +- .../TestData/CustomTable/CustomTable.wxs | 4 +- 9 files changed, 338 insertions(+), 422 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 7a64b777..292f1572 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -192,6 +192,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind command.Execute(); } + if (section.Type == SectionType.Product || section.Type == SectionType.Module) { var command = new AddRequiredStandardDirectories(section, platform); command.Execute(); @@ -329,7 +330,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (dependencyRefs.Any()) { - var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs); + var command = new ProcessDependencyReferencesCommand(section, dependencyRefs); command.Execute(); } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs index dc60a9ac..9ec26964 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs @@ -25,6 +25,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.TableDefinitions = tableDefinitions; this.BackendExtensions = backendExtensions; this.BackendHelper = backendHelper; + this.GeneratedShortNames = new Dictionary>(); } private IEnumerable BackendExtensions { get; } @@ -37,6 +38,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IntermediateSection Section { get; } + private Dictionary> GeneratedShortNames { get; } + public WindowsInstallerData Data { get; private set; } public WindowsInstallerData Execute() @@ -136,6 +139,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind case SymbolDefinitionType.ModuleConfiguration: this.AddModuleConfigurationSymbol((ModuleConfigurationSymbol)symbol); + this.EnsureModuleIgnoredTable(symbol, "ModuleConfiguration"); + break; + + case SymbolDefinitionType.ModuleSubstitution: + this.EnsureModuleIgnoredTable(symbol, "ModuleSubstitution"); break; case SymbolDefinitionType.MsiEmbeddedUI: @@ -262,6 +270,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind } this.AddIndexedCellSymbols(cellsByTableAndRowId); + this.EnsureRequiredTables(); + this.ReportGeneratedShortFileNameConflicts(); + this.ReportIllegalTables(); + this.ReportMismatchedModularizations(); + this.ReportWindowsInstallerDataInconsistencies(); } private void AddAssemblySymbol(AssemblySymbol symbol) @@ -426,6 +439,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[2] = symbol.Source; row[3] = symbol.Target; row[4] = symbol.PatchUninstall ? (int?)WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall : null; + + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); + } } private void AddDialogSymbol(DialogSymbol symbol) @@ -483,6 +505,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[0] = symbol.Id.Id; row[1] = symbol.ParentDirectoryRef; row[2] = defaultDir; + + if (OutputType.Module == this.Data.Type) + { + var directoryId = symbol.Id.Id; + + if (WindowsInstallerStandard.IsStandardDirectory(directoryId)) + { + // If the directory table contains references to standard windows folders + // mergemod.dll will add customactions to set the MSM directory to + // the same directory as the standard windows folder and will add references to + // custom action to all the standard sequence tables. A problem will occur + // if the MSI does not have these tables as mergemod.dll does not add these + // tables to the MSI if absent. This code adds the tables in case mergemod.dll + // needs them. + this.Data.EnsureTable(this.TableDefinitions["CustomAction"]); + this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); + } + else + { + foreach (var standardDirectory in WindowsInstallerStandard.StandardDirectories()) + { + if (directoryId.StartsWith(standardDirectory.Id.Id, StringComparison.Ordinal)) + { + this.Messaging.Write(WarningMessages.StandardDirectoryConflictInMergeModule(symbol.SourceLineNumbers, directoryId, standardDirectory.Id.Id)); + } + } + } + } } private void AddDuplicateFileSymbol(DuplicateFileSymbol symbol) @@ -570,6 +624,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (null == symbol.ShortName && null != name && !Common.IsValidShortFilename(name, false)) { symbol.ShortName = CreateShortName(name, true, false, "File", symbol.DirectoryRef); + + if (!this.GeneratedShortNames.TryGetValue(symbol.ShortName, out var potentialConflicts)) + { + potentialConflicts = new List(); + this.GeneratedShortNames.Add(symbol.ShortName, potentialConflicts); + } + + potentialConflicts.Add(symbol); } var row = (FileRow)this.CreateRow(symbol, "File"); @@ -612,7 +674,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var tableName = (IniFileActionType.AddLine == symbol.Action || IniFileActionType.AddTag == symbol.Action || IniFileActionType.CreateLine == symbol.Action) ? "IniFile" : "RemoveIniFile"; var name = symbol.FileName; - if (null == symbol.ShortFileName && null != name && !Common.IsValidShortFilename(name, false)) + if (null == symbol.ShortFileName && null != name && !Common.IsValidShortFilename(name, false)) { symbol.ShortFileName = CreateShortName(name, true, false, "IniFile", symbol.ComponentRef); } @@ -1168,6 +1230,238 @@ namespace WixToolset.Core.WindowsInstaller.Bind private bool AddSymbolDefaultly(IntermediateSymbol symbol) => this.BackendHelper.TryAddSymbolToMatchingTableDefinitions(this.Section, symbol, this.Data, this.TableDefinitions); + private void EnsureModuleIgnoredTable(IntermediateSymbol symbol, string ignoredTable) + { + var tableDefinition = this.TableDefinitions["ModuleIgnoreTable"]; + var table = this.Data.EnsureTable(tableDefinition); + if (!table.Rows.Any(r => r.FieldAsString(0) == ignoredTable)) + { + var row = this.CreateRow(symbol, tableDefinition); + row[0] = ignoredTable; + } + } + + private void EnsureRequiredTables() + { + // check for missing table and add them or display an error as appropriate + switch (this.Data.Type) + { + case OutputType.Module: + this.Data.EnsureTable(this.TableDefinitions["Component"]); + this.Data.EnsureTable(this.TableDefinitions["Directory"]); + this.Data.EnsureTable(this.TableDefinitions["FeatureComponents"]); + this.Data.EnsureTable(this.TableDefinitions["File"]); + this.Data.EnsureTable(this.TableDefinitions["ModuleComponents"]); + this.Data.EnsureTable(this.TableDefinitions["ModuleSignature"]); + break; + + case OutputType.PatchCreation: + var imageFamiliesCount = this.Data.Tables["ImageFamilies"]?.Rows.Count ?? 0; + var targetImagesCount = this.Data.Tables["TargetImages"]?.Rows.Count ?? 0; + var upgradedImagesCount = this.Data.Tables["UpgradedImages"]?.Rows.Count ?? 0; + + if (imageFamiliesCount < 1) + { + this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("ImageFamilies")); + } + + if (targetImagesCount < 1) + { + this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("TargetImages")); + } + + if (upgradedImagesCount < 1) + { + this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("UpgradedImages")); + } + + this.Data.EnsureTable(this.TableDefinitions["Properties"]); + break; + + case OutputType.Product: + this.Data.EnsureTable(this.TableDefinitions["File"]); + this.Data.EnsureTable(this.TableDefinitions["Media"]); + break; + } + } + + private void ReportGeneratedShortFileNameConflicts() + { + foreach (var conflicts in this.GeneratedShortNames.Values.Where(l => l.Count > 1)) + { + this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict(conflicts[0].SourceLineNumbers, conflicts[0].ShortName)); + for (var i = 1; i < conflicts.Count; ++i) + { + this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict2(conflicts[i].SourceLineNumbers)); + } + } + } + + private void ReportIllegalTables() + { + foreach (var table in this.Data.Tables) + { + switch (this.Data.Type) + { + case OutputType.Module: + if ("BBControl" == table.Name || + "Billboard" == table.Name || + "CCPSearch" == table.Name || + "Feature" == table.Name || + "LaunchCondition" == table.Name || + "Media" == table.Name || + "Patch" == table.Name || + "Upgrade" == table.Name || + "WixMerge" == table.Name) + { + foreach (Row row in table.Rows) + { + this.Messaging.Write(ErrorMessages.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name)); + } + } + else if ("Error" == table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(WarningMessages.DangerousTableInMergeModule(row.SourceLineNumbers, table.Name)); + } + } + break; + + case OutputType.PatchCreation: + if (!table.Definition.Unreal && + "_SummaryInformation" != table.Name && + "ExternalFiles" != table.Name && + "FamilyFileRanges" != table.Name && + "ImageFamilies" != table.Name && + "PatchMetadata" != table.Name && + "PatchSequence" != table.Name && + "Properties" != table.Name && + "TargetFiles_OptionalData" != table.Name && + "TargetImages" != table.Name && + "UpgradedFiles_OptionalData" != table.Name && + "UpgradedFilesToIgnore" != table.Name && + "UpgradedImages" != table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(ErrorMessages.UnexpectedTableInPatchCreationPackage(row.SourceLineNumbers, table.Name)); + } + } + break; + + case OutputType.Patch: + if (!table.Definition.Unreal && + "_SummaryInformation" != table.Name && + "Media" != table.Name && + "MsiFileHash" != table.Name && + "MsiPatchMetadata" != table.Name && + "MsiPatchSequence" != table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(ErrorMessages.UnexpectedTableInPatch(row.SourceLineNumbers, table.Name)); + } + } + break; + + case OutputType.Product: + if ("ModuleAdminExecuteSequence" == table.Name || + "ModuleAdminUISequence" == table.Name || + "ModuleAdvtExecuteSequence" == table.Name || + "ModuleAdvtUISequence" == table.Name || + "ModuleComponents" == table.Name || + "ModuleConfiguration" == table.Name || + "ModuleDependency" == table.Name || + "ModuleExclusion" == table.Name || + "ModuleIgnoreTable" == table.Name || + "ModuleInstallExecuteSequence" == table.Name || + "ModuleInstallUISequence" == table.Name || + "ModuleSignature" == table.Name || + "ModuleSubstitution" == table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(WarningMessages.UnexpectedTableInProduct(row.SourceLineNumbers, table.Name)); + } + } + break; + } + } + } + + private void ReportMismatchedModularizations() + { + // verify that modularization types match for foreign key relationships + foreach (var tableDefinition in this.TableDefinitions) + { + foreach (var columnDefinition in tableDefinition.Columns) + { + if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.KeyColumn.HasValue) + { + if (this.TableDefinitions.TryGet(columnDefinition.KeyTable, out var keyTableDefinition)) + { + var keyColumnIndex = columnDefinition.KeyColumn ?? -1; + + if (keyColumnIndex <= 0 || keyColumnIndex > keyTableDefinition.Columns.Length) + { + this.Messaging.Write(ErrorMessages.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex)); + } + else if (keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType) + { + this.Messaging.Write(ErrorMessages.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType.ToString())); + } + } + // else - ignore missing table definitions as that error is caught in other places + } + } + } + } + + private void ReportWindowsInstallerDataInconsistencies() + { + // Get the output's minimum installer version + var outputInstallerVersion = Int32.MaxValue; + + if (this.Data.Tables.TryGetTable("_SummaryInformation", out var summaryInformationTable)) + { + outputInstallerVersion = summaryInformationTable.Rows.FirstOrDefault(r => 14 == r.FieldAsInteger(0))?.FieldAsInteger(1) ?? Int32.MaxValue; + } + + // Ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40). + if (outputInstallerVersion <= 100 && OutputType.Product == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["Error"]); + } + + // Check for the presence of tables/rows/columns that require MSI 1.1 or later. + if (outputInstallerVersion < 110) + { + if (this.Data.Tables.TryGetTable("IsolatedComponent", out var isolatedComponentTable)) + { + foreach (var row in isolatedComponentTable.Rows) + { + this.Messaging.Write(WarningMessages.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion)); + } + } + } + + // Check for the presence of tables/rows/columns that require MSI 4.0 or later + if (outputInstallerVersion < 400) + { + if (this.Data.Tables.TryGetTable("Shortcut", out var shortcutTable)) + { + foreach (var row in shortcutTable.Rows) + { + if (null != row[12] || null != row[13] || null != row[14] || null != row[15]) + { + this.Messaging.Write(WarningMessages.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion)); + } + } + } + } + } + private static OutputType SectionTypeToOutputType(SectionType type) { switch (type) diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs index e96dfd91..67515154 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs @@ -36,7 +36,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var canonicalComponentTargetPaths = this.ComponentTargetPaths(); - this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths)); + this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths, this.Section.Type == SectionType.Module)); return this.FileFacades; } @@ -71,17 +71,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind private class FileFacadeOptimizer : IComparer { - public FileFacadeOptimizer(Dictionary componentTargetPaths) + public FileFacadeOptimizer(Dictionary componentTargetPaths, bool optimizingMergeModule) { this.ComponentTargetPaths = componentTargetPaths; + this.OptimizingMergeModule = optimizingMergeModule; } private Dictionary ComponentTargetPaths { get; } + private bool OptimizingMergeModule { get; } + public int Compare(FileFacade x, FileFacade y) { - // First group files by DiskId. - var compare = x.DiskId.CompareTo(y.DiskId); + // First group files by DiskId but ignore if processing a Merge Module + // because Merge Modules don't have separate disks. + var compare = this.OptimizingMergeModule ? 0 : x.DiskId.CompareTo(y.DiskId); if (compare != 0) { diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs index 0ae7ca73..7a7c2649 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs @@ -17,7 +17,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; private const string RegistryDependents = "Dependents"; - public ProcessDependencyReferencesCommand(IWindowsInstallerBackendHelper backendHelper, IntermediateSection section, IEnumerable dependencyRefSymbols) + public ProcessDependencyReferencesCommand(IntermediateSection section, IEnumerable dependencyRefSymbols) { this.Section = section; this.DependencyRefSymbols = dependencyRefSymbols; diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 8aa82d47..f311a5f6 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -7178,7 +7178,7 @@ namespace WixToolset.Core cabinet = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "CompressionLevel": - compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, node, attrib); + compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); break; case "DiskPrompt": diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -7381,7 +7381,7 @@ namespace WixToolset.Core } break; case "CompressionLevel": - compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, node, attrib); + compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); break; case "DiskPrompt": diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); diff --git a/src/WixToolset.Core/Compiler_Package.cs b/src/WixToolset.Core/Compiler_Package.cs index 2cc77a60..03ba1c40 100644 --- a/src/WixToolset.Core/Compiler_Package.cs +++ b/src/WixToolset.Core/Compiler_Package.cs @@ -4950,7 +4950,7 @@ namespace WixToolset.Core } } - private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XElement node, XAttribute attribute) + private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XAttribute attribute) { var compressionLevel = this.Core.GetAttributeValue(sourceLineNumbers, attribute); switch (compressionLevel) diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index 41e0db7d..3281cbd0 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -93,52 +93,12 @@ namespace WixToolset.Core } } -#if MOVE_TO_BACKEND - bool containsModuleSubstitution = false; - bool containsModuleConfiguration = false; -#endif - //this.activeOutput = null; -#if MOVE_TO_BACKEND - StringCollection generatedShortFileNameIdentifiers = new StringCollection(); - Hashtable generatedShortFileNames = new Hashtable(); -#endif - var multipleFeatureComponents = new Hashtable(); var wixVariables = new Dictionary(); -#if MOVE_TO_BACKEND - // verify that modularization types match for foreign key relationships - foreach (TableDefinition tableDefinition in this.tableDefinitions) - { - foreach (ColumnDefinition columnDefinition in tableDefinition.Columns) - { - if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.IsKeyColumnSet) - { - try - { - TableDefinition keyTableDefinition = this.tableDefinitions[columnDefinition.KeyTable]; - - if (0 >= columnDefinition.KeyColumn || keyTableDefinition.Columns.Count < columnDefinition.KeyColumn) - { - this.Messaging.Write(WixErrors.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, columnDefinition.KeyColumn)); - } - else if (keyTableDefinition.Columns[columnDefinition.KeyColumn - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType) - { - this.Messaging.Write(WixErrors.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, columnDefinition.KeyColumn, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[columnDefinition.KeyColumn - 1].ModularizeType.ToString())); - } - } - catch (WixMissingTableDefinitionException) - { - // ignore missing table definitions - this error is caught in other places - } - } - } - } -#endif - // First find the entry section and while processing all sections load all the symbols from all of the sections. var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, sections, this.Context.ExpectedOutputType); find.Execute(); @@ -211,7 +171,8 @@ namespace WixToolset.Core // resolve the feature to feature connects this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.SymbolsByName); - // Create the section to hold the linked content. + // Create a new section to hold the linked content. Start with the entry section's + // metadata. var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type, find.EntrySection.Codepage); var sectionCount = 0; @@ -245,54 +206,6 @@ namespace WixToolset.Core } break; -#if MOVE_TO_BACKEND - case "CustomAction": - if (OutputType.Module == this.activeOutput.Type) - { - this.activeOutput.EnsureTable(this.tableDefinitions["AdminExecuteSequence"]); - this.activeOutput.EnsureTable(this.tableDefinitions["AdminUISequence"]); - this.activeOutput.EnsureTable(this.tableDefinitions["AdvtExecuteSequence"]); - this.activeOutput.EnsureTable(this.tableDefinitions["InstallExecuteSequence"]); - this.activeOutput.EnsureTable(this.tableDefinitions["InstallUISequence"]); - } - break; - - case "Directory": - foreach (Row row in table.Rows) - { - if (OutputType.Module == this.activeOutput.Type) - { - string directory = row[0].ToString(); - if (WindowsInstallerStandard.IsStandardDirectory(directory)) - { - // if the directory table contains references to standard windows folders - // mergemod.dll will add customactions to set the MSM directory to - // the same directory as the standard windows folder and will add references to - // custom action to all the standard sequence tables. A problem will occur - // if the MSI does not have these tables as mergemod.dll does not add these - // tables to the MSI if absent. This code adds the tables in case mergemod.dll - // needs them. - this.activeOutput.EnsureTable(this.tableDefinitions["CustomAction"]); - this.activeOutput.EnsureTable(this.tableDefinitions["AdminExecuteSequence"]); - this.activeOutput.EnsureTable(this.tableDefinitions["AdminUISequence"]); - this.activeOutput.EnsureTable(this.tableDefinitions["AdvtExecuteSequence"]); - this.activeOutput.EnsureTable(this.tableDefinitions["InstallExecuteSequence"]); - this.activeOutput.EnsureTable(this.tableDefinitions["InstallUISequence"]); - } - else - { - foreach (string standardDirectory in WindowsInstallerStandard.GetStandardDirectories()) - { - if (directory.StartsWith(standardDirectory, StringComparison.Ordinal)) - { - this.Messaging.Write(WixWarnings.StandardDirectoryConflictInMergeModule(row.SourceLineNumbers, directory, standardDirectory)); - } - } - } - } - } - break; -#endif case SymbolDefinitionType.Extension: if (SectionType.Product == resolvedSection.Type) { @@ -300,16 +213,6 @@ namespace WixToolset.Core } break; -#if MOVE_TO_BACKEND - case SymbolDefinitionType.ModuleSubstitution: - containsModuleSubstitution = true; - break; - - case SymbolDefinitionType.ModuleConfiguration: - containsModuleConfiguration = true; - break; -#endif - case SymbolDefinitionType.Assembly: if (SectionType.Product == resolvedSection.Type) { @@ -338,26 +241,6 @@ namespace WixToolset.Core } break; -#if MOVE_TO_BACKEND - case "WixFile": - foreach (Row row in table.Rows) - { - // DiskId is not valid when creating a module, so set it to - // 0 for all files to ensure proper sorting in the binder - if (SectionType.Module == entrySection.Type) - { - row[5] = 0; - } - - // if the short file name was generated, check for collisions - if (0x1 == (int)row[9]) - { - generatedShortFileNameIdentifiers.Add((string)row[0]); - } - } - break; -#endif - case SymbolDefinitionType.WixMerge: if (SectionType.Product == resolvedSection.Type) { @@ -374,30 +257,9 @@ namespace WixToolset.Core break; case SymbolDefinitionType.WixVariable: - // check for colliding values and collect the wix variable rows - { - var wixVariableSymbol = (WixVariableSymbol)symbol; - var id = wixVariableSymbol.Id.Id; - - if (wixVariables.TryGetValue(id, out var collidingSymbol)) - { - if (collidingSymbol.Overridable && !wixVariableSymbol.Overridable) - { - wixVariables[id] = wixVariableSymbol; - } - else if (!wixVariableSymbol.Overridable || (collidingSymbol.Overridable && wixVariableSymbol.Overridable)) - { - this.Messaging.Write(ErrorMessages.WixVariableCollision(wixVariableSymbol.SourceLineNumbers, id)); - } - } - else - { - wixVariables.Add(id, wixVariableSymbol); - } - } - - copySymbol = false; - break; + this.AddWixVariable(wixVariables, (WixVariableSymbol)symbol); + copySymbol = false; // Do not copy the symbol, it will be added later after all overriding has been handled. + break; } if (copySymbol) @@ -407,7 +269,7 @@ namespace WixToolset.Core } } - // copy the module to feature connections into the output + // Copy the module to feature connections into the output. foreach (ConnectToFeature connectToFeature in modulesToFeatures) { foreach (var feature in connectToFeature.ConnectFeatures) @@ -420,136 +282,23 @@ namespace WixToolset.Core } } -#if MOVE_TO_BACKEND - // check for missing table and add them or display an error as appropriate - switch (this.activeOutput.Type) - { - case OutputType.Module: - this.activeOutput.EnsureTable(this.tableDefinitions["Component"]); - this.activeOutput.EnsureTable(this.tableDefinitions["Directory"]); - this.activeOutput.EnsureTable(this.tableDefinitions["FeatureComponents"]); - this.activeOutput.EnsureTable(this.tableDefinitions["File"]); - this.activeOutput.EnsureTable(this.tableDefinitions["ModuleComponents"]); - this.activeOutput.EnsureTable(this.tableDefinitions["ModuleSignature"]); - break; - case OutputType.PatchCreation: - Table imageFamiliesTable = this.activeOutput.Tables["ImageFamilies"]; - Table targetImagesTable = this.activeOutput.Tables["TargetImages"]; - Table upgradedImagesTable = this.activeOutput.Tables["UpgradedImages"]; - - if (null == imageFamiliesTable || 1 > imageFamiliesTable.Rows.Count) - { - this.Messaging.Write(WixErrors.ExpectedRowInPatchCreationPackage("ImageFamilies")); - } - - if (null == targetImagesTable || 1 > targetImagesTable.Rows.Count) - { - this.Messaging.Write(WixErrors.ExpectedRowInPatchCreationPackage("TargetImages")); - } - - if (null == upgradedImagesTable || 1 > upgradedImagesTable.Rows.Count) - { - this.Messaging.Write(WixErrors.ExpectedRowInPatchCreationPackage("UpgradedImages")); - } - - this.activeOutput.EnsureTable(this.tableDefinitions["Properties"]); - break; - case OutputType.Product: - this.activeOutput.EnsureTable(this.tableDefinitions["File"]); - this.activeOutput.EnsureTable(this.tableDefinitions["Media"]); - break; - } - - this.CheckForIllegalTables(this.activeOutput); -#endif - - //correct the section Id in FeatureComponents table + // Correct the section Id in FeatureComponents table. if (this.sectionIdOnRows) { - //var componentSectionIds = new Dictionary(); - - //foreach (var componentSymbol in entrySection.Symbols.OfType()) - //{ - // componentSectionIds.Add(componentSymbol.Id.Id, componentSymbol.SectionId); - //} - - //foreach (var featureComponentSymbol in entrySection.Symbols.OfType()) - //{ - // if (componentSectionIds.TryGetValue(featureComponentSymbol.Component_, out var componentSectionId)) - // { - // featureComponentSymbol.SectionId = componentSectionId; - // } - //} - } - -#if MOVE_TO_BACKEND - // add the ModuleSubstitution table to the ModuleIgnoreTable - if (containsModuleSubstitution) - { - Table moduleIgnoreTableTable = this.activeOutput.EnsureTable(this.tableDefinitions["ModuleIgnoreTable"]); - - Row moduleIgnoreTableRow = moduleIgnoreTableTable.CreateRow(null); - moduleIgnoreTableRow[0] = "ModuleSubstitution"; - } - - // add the ModuleConfiguration table to the ModuleIgnoreTable - if (containsModuleConfiguration) - { - Table moduleIgnoreTableTable = this.activeOutput.EnsureTable(this.tableDefinitions["ModuleIgnoreTable"]); - - Row moduleIgnoreTableRow = moduleIgnoreTableTable.CreateRow(null); - moduleIgnoreTableRow[0] = "ModuleConfiguration"; - } -#endif - -#if MOVE_TO_BACKEND - // index all the file rows - Table fileTable = this.activeOutput.Tables["File"]; - RowDictionary indexedFileRows = (null == fileTable) ? new RowDictionary() : new RowDictionary(fileTable); - - // flag all the generated short file name collisions - foreach (string fileId in generatedShortFileNameIdentifiers) - { - FileRow fileRow = indexedFileRows[fileId]; - - string[] names = fileRow.FileName.Split('|'); - string shortFileName = names[0]; +#if TODO_DO_SYMBOLS_NEED_SECTIONIDS + var componentSectionIds = resolvedSection.Symbols.OfType().ToDictionary(c => c.Id.Id, c => c.SectionId); - // create lists of conflicting generated short file names - if (!generatedShortFileNames.Contains(shortFileName)) + foreach (var featureComponentSymbol in resolvedSection.Symbols.OfType()) { - generatedShortFileNames.Add(shortFileName, new ArrayList()); - } - ((ArrayList)generatedShortFileNames[shortFileName]).Add(fileRow); - } - - // check for generated short file name collisions - foreach (DictionaryEntry entry in generatedShortFileNames) - { - string shortFileName = (string)entry.Key; - ArrayList fileRows = (ArrayList)entry.Value; - - if (1 < fileRows.Count) - { - // sort the rows by DiskId - fileRows.Sort(); - - this.Messaging.Write(WixWarnings.GeneratedShortFileNameConflict(((FileRow)fileRows[0]).SourceLineNumbers, shortFileName)); - - for (int i = 1; i < fileRows.Count; i++) + if (componentSectionIds.TryGetValue(featureComponentSymbol.ComponentRef, out var componentSectionId)) { - FileRow fileRow = (FileRow)fileRows[i]; - - if (null != fileRow.SourceLineNumbers) - { - this.Messaging.Write(WixWarnings.GeneratedShortFileNameConflict2(fileRow.SourceLineNumbers)); - } + featureComponentSymbol.SectionId = componentSectionId; } } - } #endif + } - // copy the wix variable rows to the output after all overriding has been accounted for. + // Copy the wix variable rows to the output now that all overriding has been accounted for. foreach (var symbol in wixVariables.Values) { resolvedSection.AddSymbol(symbol); @@ -567,10 +316,6 @@ namespace WixToolset.Core var localizationsByCulture = collate.Execute(); intermediate = new Intermediate(resolvedSection.Id, Data.IntermediateLevels.Linked, new[] { resolvedSection }, localizationsByCulture); - -#if MOVE_TO_BACKEND - this.CheckOutputConsistency(output); -#endif } finally { @@ -583,159 +328,31 @@ namespace WixToolset.Core return this.Messaging.EncounteredError ? null : intermediate; } -#if MOVE_TO_BACKEND /// - /// Checks for any tables in the output which are not allowed in the output type. + /// Check for colliding values and collect the wix variable rows. /// - /// The output to check. - private void CheckForIllegalTables(Output output) + /// Collection of WixVariableSymbols by id. + /// WixVariableSymbol to add, if not overridden. + private void AddWixVariable(Dictionary wixVariables, WixVariableSymbol symbol) { - foreach (Table table in output.Tables) - { - switch (output.Type) - { - case OutputType.Module: - if ("BBControl" == table.Name || - "Billboard" == table.Name || - "CCPSearch" == table.Name || - "Feature" == table.Name || - "LaunchCondition" == table.Name || - "Media" == table.Name || - "Patch" == table.Name || - "Upgrade" == table.Name || - "WixMerge" == table.Name) - { - foreach (Row row in table.Rows) - { - this.Messaging.Write(WixErrors.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name)); - } - } - else if ("Error" == table.Name) - { - foreach (Row row in table.Rows) - { - this.Messaging.Write(WixWarnings.DangerousTableInMergeModule(row.SourceLineNumbers, table.Name)); - } - } - break; - case OutputType.PatchCreation: - if (!table.Definition.Unreal && - "_SummaryInformation" != table.Name && - "ExternalFiles" != table.Name && - "FamilyFileRanges" != table.Name && - "ImageFamilies" != table.Name && - "PatchMetadata" != table.Name && - "PatchSequence" != table.Name && - "Properties" != table.Name && - "TargetFiles_OptionalData" != table.Name && - "TargetImages" != table.Name && - "UpgradedFiles_OptionalData" != table.Name && - "UpgradedFilesToIgnore" != table.Name && - "UpgradedImages" != table.Name) - { - foreach (Row row in table.Rows) - { - this.Messaging.Write(WixErrors.UnexpectedTableInPatchCreationPackage(row.SourceLineNumbers, table.Name)); - } - } - break; - case OutputType.Patch: - if (!table.Definition.Unreal && - "_SummaryInformation" != table.Name && - "Media" != table.Name && - "MsiPatchMetadata" != table.Name && - "MsiPatchSequence" != table.Name) - { - foreach (Row row in table.Rows) - { - this.Messaging.Write(WixErrors.UnexpectedTableInPatch(row.SourceLineNumbers, table.Name)); - } - } - break; - case OutputType.Product: - if ("ModuleAdminExecuteSequence" == table.Name || - "ModuleAdminUISequence" == table.Name || - "ModuleAdvtExecuteSequence" == table.Name || - "ModuleAdvtUISequence" == table.Name || - "ModuleComponents" == table.Name || - "ModuleConfiguration" == table.Name || - "ModuleDependency" == table.Name || - "ModuleExclusion" == table.Name || - "ModuleIgnoreTable" == table.Name || - "ModuleInstallExecuteSequence" == table.Name || - "ModuleInstallUISequence" == table.Name || - "ModuleSignature" == table.Name || - "ModuleSubstitution" == table.Name) - { - foreach (Row row in table.Rows) - { - this.Messaging.Write(WixWarnings.UnexpectedTableInProduct(row.SourceLineNumbers, table.Name)); - } - } - break; - } - } - } -#endif + var id = symbol.Id.Id; -#if MOVE_TO_BACKEND - /// - /// Performs various consistency checks on the output. - /// - /// Output containing instance transform definitions. - private void CheckOutputConsistency(Output output) - { - // Get the output's minimum installer version - int outputInstallerVersion = int.MinValue; - Table summaryInformationTable = output.Tables["_SummaryInformation"]; - if (null != summaryInformationTable) + if (wixVariables.TryGetValue(id, out var collidingSymbol)) { - foreach (Row row in summaryInformationTable.Rows) + if (collidingSymbol.Overridable && !symbol.Overridable) { - if (14 == (int)row[0]) - { - outputInstallerVersion = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); - break; - } + wixVariables[id] = symbol; } - } - - // ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40) - if (100 >= outputInstallerVersion && OutputType.Product == output.Type) - { - output.EnsureTable(this.tableDefinitions["Error"]); - } - - // check for the presence of tables/rows/columns that require MSI 1.1 or later - if (110 > outputInstallerVersion) - { - Table isolatedComponentTable = output.Tables["IsolatedComponent"]; - if (null != isolatedComponentTable) + else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable)) { - foreach (Row row in isolatedComponentTable.Rows) - { - this.Messaging.Write(WixWarnings.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion)); - } + this.Messaging.Write(ErrorMessages.WixVariableCollision(symbol.SourceLineNumbers, id)); } } - - // check for the presence of tables/rows/columns that require MSI 4.0 or later - if (400 > outputInstallerVersion) + else { - Table shortcutTable = output.Tables["Shortcut"]; - if (null != shortcutTable) - { - foreach (Row row in shortcutTable.Rows) - { - if (null != row[12] || null != row[13] || null != row[14] || null != row[15]) - { - this.Messaging.Write(WixWarnings.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion)); - } - } - } + wixVariables.Add(id, symbol); } } -#endif /// /// Load the standard action and directory symbols. diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 8c3487f0..2af47034 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -1022,7 +1022,7 @@ namespace WixToolsetTest.CoreIntegration Assert.Empty(section.Symbols.OfType()); var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - Assert.Null(data.Tables["File"]); + Assert.Empty(data.Tables["File"].Rows); var results = Query.QueryDatabase(msiPath, new[] { "File" }); WixAssert.CompareLineByLine(new[] diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs index 7f4a43e5..d32e808c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs @@ -7,7 +7,7 @@ - + @@ -20,7 +20,7 @@ - + -- cgit v1.2.3-55-g6feb From 722ffb41bb83879d3e086b6faffd45ee38c5a5b6 Mon Sep 17 00:00:00 2001 From: Andrij Abyzov Date: Thu, 4 Mar 2021 00:43:32 +0100 Subject: Create unit test for WixVariable resolution issue #6376 --- .../TestData/WixVariableOverride/Package.en-us.wxl | 11 +++++ .../TestData/WixVariableOverride/Package.wxs | 21 ++++++++ .../WixVariableOverride/PackageComponents.wxs | 14 ++++++ .../TestData/WixVariableOverride/data/test.txt | 1 + .../TestData/WixVariableOverride/data/test2.txt | 1 + .../WixlibFixture.cs | 57 ++++++++++++++++++++++ 6 files changed, 105 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs new file mode 100644 index 00000000..e1cf7394 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs new file mode 100644 index 00000000..df867923 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt new file mode 100644 index 00000000..eab3a9b5 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt @@ -0,0 +1 @@ +This is test2.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs index 6ae2c0b8..52460843 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs @@ -144,6 +144,63 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6376")] + public void CanOverridePathWixVariable() + { + var folder = TestData.Get(@"TestData\WixVariableOverride"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-bf", + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var wixlib = Intermediate.Load(wixlibPath); + + Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); + Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); + Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); + Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + + Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); + Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); + + var section = intermediate.Sections.Single(); + + var wixFile = section.Symbols.OfType().First(); + Assert.Equal(Path.Combine(folder, @"data\test2.txt"), wixFile.Data.Path); + } + } + [Fact] public void CanBuildWithExtensionUsingWixlib() { -- cgit v1.2.3-55-g6feb From 1f4652516a385aeeeea1558738efa0863f63c9fc Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 5 Mar 2021 17:38:14 -0600 Subject: Fix harvesting providers from MSI packages. --- .../Bundles/ProcessMsiPackageCommand.cs | 14 ++--- .../DependencyExtensionFixture.cs | 60 +++++++++++++++++++++- .../TestData/Dependency/UsingProvidesBundle.wxs | 8 +++ .../TestData/UsingProvides/Package.wxs | 2 +- 4 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs index b598af96..5ba1ad07 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs @@ -512,7 +512,7 @@ namespace WixToolset.Core.Burn.Bundles { if (db.Tables.Contains("WixDependencyProvider")) { - var query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"; + var query = "SELECT `WixDependencyProvider`, `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"; using (var view = db.OpenView(query)) { @@ -526,14 +526,16 @@ namespace WixToolset.Core.Burn.Bundles break; } + var id = new Identifier(AccessModifier.Section, Common.GenerateIdentifier("dep", msiPackage.Id.Id, record.GetString(1))); + // Import the provider key and attributes. - this.Section.AddSymbol(new ProvidesDependencySymbol(msiPackage.SourceLineNumbers) + this.Section.AddSymbol(new ProvidesDependencySymbol(msiPackage.SourceLineNumbers, id) { PackageRef = msiPackage.Id.Id, - Key = record.GetString(1), - Version = record.GetString(2) ?? msiPackage.ProductVersion, - DisplayName = record.GetString(3) ?? this.Facade.PackageSymbol.DisplayName, - Attributes = record.GetInteger(4), + Key = record.GetString(2), + Version = record.GetString(3) ?? msiPackage.ProductVersion, + DisplayName = record.GetString(4) ?? this.Facade.PackageSymbol.DisplayName, + Attributes = record.GetInteger(5), Imported = true }); } diff --git a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs index 14eb8ff7..de038bde 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs @@ -2,6 +2,10 @@ namespace WixToolsetTest.CoreIntegration { + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using Xunit; @@ -9,7 +13,61 @@ namespace WixToolsetTest.CoreIntegration public class DependencyExtensionFixture { [Fact] - public void CanBuildUsingProvides() + public void CanBuildBundleUsingMsiWithProvides() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "UsingProvides", "Package.wxs"), + Path.Combine(folder, "UsingProvides", "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "UsingProvides", "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "UsingProvides"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "UsingProvides.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Dependency", "UsingProvidesBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage/burn:Provides"); + WixAssert.CompareLineByLine(new string[] + { + "", + "", + }, provides.Cast().Select(e => e.GetTestXml()).ToArray()); + } + } + + [Fact] + public void CanBuildPackageUsingProvides() { var folder = TestData.Get(@"TestData\UsingProvides"); var build = new Builder(folder, null, new[] { folder }); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs new file mode 100644 index 00000000..9c3a9690 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs index 07da1215..9ddcdc90 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs @@ -1,5 +1,5 @@ - + -- cgit v1.2.3-55-g6feb From 7fa2390b06b643ec775d00d7938c1624f5c0fdfe Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 5 Mar 2021 18:28:01 -0600 Subject: Fix parsing ProviderKey for Bundle. --- src/WixToolset.Core/Compiler_Bundle.cs | 16 +++++-- .../DependencyExtensionFixture.cs | 53 +++++++++++++++++++++- .../Dependency/CustomProviderKeyBundle.wxs | 10 ++++ 3 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 46b79484..89ca94ba 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -197,7 +197,7 @@ namespace WixToolset.Core parentName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "ProviderKey": - this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib); + // This can't be processed until we create the section. break; case "SplashScreenSourceFile": splashScreenSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -261,10 +261,20 @@ namespace WixToolset.Core this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; this.Core.CreateActiveSection(this.activeName, SectionType.Bundle, 0, this.Context.CompilationId); - // Now that the active section is initialized, process only extension attributes. + // Now that the active section is initialized, process only extension attributes and the special ProviderKey attribute. foreach (var attrib in node.Attributes()) { - if (!String.IsNullOrEmpty(attrib.Name.NamespaceName) && CompilerCore.WixNamespace != attrib.Name.Namespace) + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ProviderKey": + this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib); + break; + // Unknown attributes were reported earlier. + } + } + else { this.Core.ParseExtensionAttribute(node, attrib); } diff --git a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs index de038bde..48270ce4 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs @@ -57,12 +57,61 @@ namespace WixToolsetTest.CoreIntegration var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); extractResult.AssertSuccess(); - var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage/burn:Provides"); + var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage/burn:Provides") + .Cast() + .Select(e => e.GetTestXml()) + .ToArray(); WixAssert.CompareLineByLine(new string[] { "", "", - }, provides.Cast().Select(e => e.GetTestXml()).ToArray()); + }, provides); + } + } + + [Fact] + public void CanBuildBundleWithCustomProviderKey() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Dependency", "CustomProviderKeyBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributesByElementName = new Dictionary> + { + { "Registration", new List { "Id" } }, + }; + var registration = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, registration); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs new file mode 100644 index 00000000..6df8a7c0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 6bcf25fe37f0b4dc5f2f43720777e7ab0d26b030 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 7 Mar 2021 14:48:08 -0600 Subject: Add failing test for patch from MSI that included a file from a wixext. --- .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 25 +++++++++++++++++- .../TestData/PatchFromWixlib/Package.wxs | 30 ++++++++++++++++++++++ .../TestData/PatchFromWixlib/Patch.wxs | 16 ++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs index 788cc01f..de6d00a2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs @@ -2,6 +2,7 @@ namespace WixToolsetTest.CoreIntegration { + using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; @@ -10,6 +11,7 @@ namespace WixToolsetTest.CoreIntegration using System.Text; using System.Xml; using System.Xml.Linq; + using Example.Extension; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using WixToolset.Data; @@ -84,6 +86,25 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact(Skip = "Test demonstrates failure")] + public void CanBuildPatchFromProductWithFilesFromWixlib() + { + var folder = TestData.Get(@"TestData\PatchFromWixlib"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1", hasNoFiles: true); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + Assert.True(File.Exists(baselinePdb)); + Assert.True(File.Exists(update1Pdb)); + } + } + [Fact] public void CanBuildBundleWithNonSpecificPatches() { @@ -164,6 +185,7 @@ namespace WixToolsetTest.CoreIntegration private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB) { + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); var result = WixRunner.Execute(new[] @@ -175,7 +197,8 @@ namespace WixToolsetTest.CoreIntegration "-d", "B=" + defineB, "-bindpath", Path.Combine(sourceFolder, ".data"), "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", outputPath + "-o", outputPath, + "-ext", extensionPath, }); result.AssertSuccess(); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs new file mode 100644 index 00000000..5cb8ede8 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs new file mode 100644 index 00000000..52e87f64 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 8e51e27adbcfe1892463463b4e7dc251019f427c Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 8 Mar 2021 14:20:23 -0600 Subject: Add failing test for authoring Provides in chain packages. --- .../DependencyExtensionFixture.cs | 43 ++++++++++++++++++++++ .../Dependency/ExePackageProvidesBundle.wxs | 10 +++++ 2 files changed, 53 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs index 48270ce4..6437b731 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs @@ -12,6 +12,49 @@ namespace WixToolsetTest.CoreIntegration public class DependencyExtensionFixture { + [Fact(Skip = "Test demonstrates failure")] + public void CanBuildBundleUsingExePackageWithProvides() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Dependency", "ExePackageProvidesBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage/burn:Provides") + .Cast() + .Select(e => e.GetTestXml()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, provides); + } + } + [Fact] public void CanBuildBundleUsingMsiWithProvides() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs new file mode 100644 index 00000000..4d188d3a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 1fa3b2f3c3e1380a5ae5ed76cdf028f221473da3 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 19 Mar 2021 11:15:12 -0700 Subject: Update tests to validate Shortcut Name/ShortName handling Resolves wixtoolset/issues#4227 --- .../MsiQueryFixture.cs | 34 --------- .../ShortcutFixture.cs | 78 +++++++++++++++++++++ .../TestData/Shortcut/DecompiledShortcuts.wxs | 2 +- .../Shortcut/ShortcutSameNameShortName.wxs | 12 ++++ .../TestData/Shortcut/shortcuts.msi | Bin 32768 -> 32768 bytes 5 files changed, 91 insertions(+), 35 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs index 2af47034..71edddc6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -715,40 +715,6 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact] - public void PopulatesMsiShortcutPropertyTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Shortcut", "ShortcutProperty.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty", "Shortcut" }); - WixAssert.CompareLineByLine(new[] - { - "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue", - "Shortcut:TheShortcut\tINSTALLFOLDER\td\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", - }, results); - } - } - [Fact] public void PopulatesReserveCostTable() { diff --git a/src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs new file mode 100644 index 00000000..3b6c50c0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs @@ -0,0 +1,78 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ShortcutFixture + { + [Fact] + public void CanBuildShortcutNameWithShortname() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Shortcut", "ShortcutSameNameShortName.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var results = Query.QueryDatabase(msiPath, new[] { "Shortcut" }); + WixAssert.CompareLineByLine(new[] + { + "Shortcut:sctzJpBYlrhdx4Mm9Xh41X0KPWYiX0\tINSTALLFOLDER\tDaName\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", + }, results); + } + } + + [Fact] + public void PopulatesMsiShortcutPropertyTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Shortcut", "ShortcutProperty.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty", "Shortcut" }); + WixAssert.CompareLineByLine(new[] + { + "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue", + "Shortcut:TheShortcut\tINSTALLFOLDER\td\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", + }, results); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs index 13412b50..ab57b0cd 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs @@ -6,7 +6,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs new file mode 100644 index 00000000..d704bbf1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi index 3a24d1a8..8737f3c2 100644 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi and b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi differ -- cgit v1.2.3-55-g6feb From 977b748b499e02f7e5226416b1cf5cfcf3842129 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 23 Mar 2021 01:21:51 -0700 Subject: Allow payloads to be shared across packages in a Bundle Fixes wixtoolset/issues#6370 --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 81 ++++++++++++++-------- ...CreateBootstrapperApplicationManifestCommand.cs | 54 +++++++++------ .../CreateBundleExtensionManifestCommand.cs | 8 +++ .../Bundles/CreateBurnManifestCommand.cs | 10 +-- .../Bundles/GetPackageFacadesCommand.cs | 3 +- .../Bundles/ProcessMsiPackageCommand.cs | 32 ++++++--- .../BundleManifestFixture.cs | 10 +-- .../SharedPayloadsBetweenPackages.wxs | 2 +- 8 files changed, 129 insertions(+), 71 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 7b5a3174..d154fef9 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -63,7 +63,7 @@ namespace WixToolset.Core.Burn private IEnumerable BackendExtensions { get; } - private Intermediate Output { get; } + private Intermediate Output { get; } private string OutputPath { get; } @@ -95,7 +95,7 @@ namespace WixToolset.Core.Burn var wixGroupSymbols = this.GetRequiredSymbols(); - // Ensure there is one and only one row in the WixBundle table. + // Ensure there is one and only one WixBundleSymbol. // The compiler and linker behavior should have colluded to get // this behavior. var bundleSymbol = this.GetSingleSymbol(); @@ -104,12 +104,12 @@ namespace WixToolset.Core.Burn bundleSymbol.Attributes |= WixBundleAttributes.PerMachine; // default to per-machine but the first-per user package wil flip the bundle per-user. - // Ensure there is one and only one row in the WixBootstrapperApplicationDll table. + // Ensure there is one and only one WixBootstrapperApplicationDllSymbol. // The compiler and linker behavior should have colluded to get // this behavior. var bundleApplicationDllSymbol = this.GetSingleSymbol(); - // Ensure there is one and only one row in the WixChain table. + // Ensure there is one and only one WixChainSymbol. // The compiler and linker behavior should have colluded to get // this behavior. var chainSymbol = this.GetSingleSymbol(); @@ -122,10 +122,15 @@ namespace WixToolset.Core.Burn // If there are any fields to resolve later, create the cache to populate during bind. var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; - var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section); - orderSearchesCommand.Execute(); - var orderedSearches = orderSearchesCommand.OrderedSearchFacades; - var extensionSearchSymbolsById = orderSearchesCommand.ExtensionSearchSymbolsByExtensionId; + IEnumerable orderedSearches; + IDictionary> extensionSearchSymbolsById; + { + var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section); + orderSearchesCommand.Execute(); + + orderedSearches = orderSearchesCommand.OrderedSearchFacades; + extensionSearchSymbolsById = orderSearchesCommand.ExtensionSearchSymbolsByExtensionId; + } // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). { @@ -136,8 +141,9 @@ namespace WixToolset.Core.Burn // Get the explicit payloads. var payloadSymbols = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); - // Update explicitly authored payloads with their parent package and container (as appropriate) + // Update explicitly authored payloads with their parent container // to make it easier to gather the payloads later. foreach (var groupSymbol in wixGroupSymbols) { @@ -145,14 +151,10 @@ namespace WixToolset.Core.Burn { var payloadSymbol = payloadSymbols[groupSymbol.ChildId]; - if (ComplexReferenceParentType.Package == groupSymbol.ParentType) + if (ComplexReferenceParentType.Container == groupSymbol.ParentType) { - Debug.Assert(String.IsNullOrEmpty(payloadSymbol.PackageRef)); - payloadSymbol.PackageRef = groupSymbol.ParentId; - } - else if (ComplexReferenceParentType.Container == groupSymbol.ParentType) - { - Debug.Assert(String.IsNullOrEmpty(payloadSymbol.ContainerRef)); + // TODO: v3 didn't warn if we overwrote the payload's container. + // Should we warn now? payloadSymbol.ContainerRef = groupSymbol.ParentId; } else if (ComplexReferenceParentType.Layout == groupSymbol.ParentType) @@ -167,7 +169,7 @@ namespace WixToolset.Core.Burn // Process the explicitly authored payloads. ISet processedPayloads; { - var command = new ProcessPayloadsCommand(this.ServiceProvider, this.BackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory); + var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory); command.Execute(); fileTransfers.AddRange(command.FileTransfers); @@ -204,7 +206,7 @@ namespace WixToolset.Core.Burn case WixBundlePackageType.Msi: { - var command = new ProcessMsiPackageCommand(this.ServiceProvider, this.BackendExtensions, section, facade, payloadSymbols); + var command = new ProcessMsiPackageCommand(this.ServiceProvider, this.BackendExtensions, section, facade, packagesPayloads[facade.PackageId]); command.Execute(); if (null != variableCache) @@ -249,12 +251,13 @@ namespace WixToolset.Core.Burn // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) // are present. payloadSymbols = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); // Process the payloads that were added by processing the packages. { var toProcess = payloadSymbols.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList(); - var command = new ProcessPayloadsCommand(this.ServiceProvider, this.BackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory); + var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory); command.Execute(); fileTransfers.AddRange(command.FileTransfers); @@ -265,15 +268,13 @@ namespace WixToolset.Core.Burn // Set the package metadata from the payloads now that we have the complete payload information. { - var payloadsByPackageId = payloadSymbols.Values.ToLookup(p => p.PackageRef); - foreach (var facade in facades.Values) { facade.PackageSymbol.Size = 0; - var packagePayloads = payloadsByPackageId[facade.PackageId]; + var packagePayloads = packagesPayloads[facade.PackageId]; - foreach (var payload in packagePayloads) + foreach (var payload in packagePayloads.Values) { facade.PackageSymbol.Size += payload.FileSize.Value; } @@ -415,7 +416,7 @@ namespace WixToolset.Core.Burn // Generate the core-defined BA manifest tables... string baManifestPath; { - var command = new CreateBootstrapperApplicationManifestCommand(section, bundleSymbol, orderedFacades, uxPayloadIndex, payloadSymbols, this.IntermediateFolder, this.InternalBurnBackendHelper); + var command = new CreateBootstrapperApplicationManifestCommand(section, bundleSymbol, orderedFacades, uxPayloadIndex, packagesPayloads, this.IntermediateFolder, this.InternalBurnBackendHelper); command.Execute(); var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; @@ -462,7 +463,7 @@ namespace WixToolset.Core.Burn { var executableName = Path.GetFileName(this.OutputPath); - var command = new CreateBurnManifestCommand(this.Messaging, this.BackendExtensions, executableName, section, bundleSymbol, containers, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, orderedSearches, this.IntermediateFolder); + var command = new CreateBurnManifestCommand(this.Messaging, this.BackendExtensions, executableName, section, bundleSymbol, containers, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, packagesPayloads, orderedSearches, this.IntermediateFolder); command.Execute(); manifestPath = command.OutputPath; @@ -580,7 +581,7 @@ namespace WixToolset.Core.Burn if (0 == symbols.Count) { - throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); + this.Messaging.Write(ErrorMessages.MissingBundleInformation(nameof(T))); } return symbols; @@ -592,10 +593,36 @@ namespace WixToolset.Core.Burn if (1 != symbols.Count) { - throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); + this.Messaging.Write(ErrorMessages.MissingBundleInformation(nameof(T))); } return symbols[0]; } + + private static Dictionary> RecalculatePackagesPayloads(Dictionary payloadSymbols, IEnumerable wixGroupSymbols) + { + var packagesPayloads = new Dictionary>(); + + foreach (var groupSymbol in wixGroupSymbols) + { + if (ComplexReferenceChildType.Payload == groupSymbol.ChildType) + { + var payloadSymbol = payloadSymbols[groupSymbol.ChildId]; + + if (ComplexReferenceParentType.Package == groupSymbol.ParentType) + { + if (!packagesPayloads.TryGetValue(groupSymbol.ParentId, out var packagePayloadsById)) + { + packagePayloadsById = new Dictionary(); + packagesPayloads.Add(groupSymbol.ParentId, packagePayloadsById); + } + + packagePayloadsById.Add(payloadSymbol.Id.Id, payloadSymbol); + } + } + } + + return packagesPayloads; + } } } diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs index 63a168a0..1fdd76da 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -15,13 +15,13 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateBootstrapperApplicationManifestCommand { - public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable chainPackages, int lastUXPayloadIndex, Dictionary payloadSymbols, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) + public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable chainPackages, int lastUXPayloadIndex, Dictionary> packagesPayloads, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) { this.Section = section; this.BundleSymbol = bundleSymbol; this.ChainPackages = chainPackages; this.LastUXPayloadIndex = lastUXPayloadIndex; - this.Payloads = payloadSymbols; + this.PackagesPayloads = packagesPayloads; this.IntermediateFolder = intermediateFolder; this.InternalBurnBackendHelper = internalBurnBackendHelper; } @@ -36,7 +36,7 @@ namespace WixToolset.Core.Burn.Bundles private int LastUXPayloadIndex { get; } - private Dictionary Payloads { get; } + private Dictionary> PackagesPayloads { get; } private string IntermediateFolder { get; } @@ -98,7 +98,12 @@ namespace WixToolset.Core.Burn.Bundles { foreach (var package in this.ChainPackages) { - var packagePayload = this.Payloads[package.PackageSymbol.PayloadRef]; + if (!this.PackagesPayloads.TryGetValue(package.PackageId, out var payloads)) + { + continue; + } + + var packagePayload = payloads[package.PackageSymbol.PayloadRef]; var size = package.PackageSymbol.Size.ToString(CultureInfo.InvariantCulture); @@ -212,33 +217,36 @@ namespace WixToolset.Core.Burn.Bundles private void WritePayloadInfo(XmlTextWriter writer) { // TODO: check v3 - should this be only include package payloads or include all non-UX container payloads? - var payloadSymbols = this.Section.Symbols.OfType() - .Where(p => !String.IsNullOrEmpty(p.PackageRef)); - - foreach (var payloadSymbol in payloadSymbols) + foreach (var kvp in this.PackagesPayloads.OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) { - writer.WriteStartElement("WixPayloadProperties"); + var packageId = kvp.Key; + var payloadsById = kvp.Value; - writer.WriteAttributeString("Payload", payloadSymbol.Id.Id); + foreach (var payloadSymbol in payloadsById.Values.OrderBy(p => p.Id.Id, StringComparer.Ordinal)) + { + writer.WriteStartElement("WixPayloadProperties"); - writer.WriteAttributeString("Package", payloadSymbol.PackageRef); + writer.WriteAttributeString("Package", packageId); - if (!String.IsNullOrEmpty(payloadSymbol.ContainerRef)) - { - writer.WriteAttributeString("Container", payloadSymbol.ContainerRef); - } + writer.WriteAttributeString("Payload", payloadSymbol.Id.Id); + + if (!String.IsNullOrEmpty(payloadSymbol.ContainerRef)) + { + writer.WriteAttributeString("Container", payloadSymbol.ContainerRef); + } - writer.WriteAttributeString("Name", payloadSymbol.Name); - writer.WriteAttributeString("Size", payloadSymbol.FileSize.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Name", payloadSymbol.Name); + writer.WriteAttributeString("Size", payloadSymbol.FileSize.Value.ToString(CultureInfo.InvariantCulture)); - if (!String.IsNullOrEmpty(payloadSymbol.DownloadUrl)) - { - writer.WriteAttributeString("DownloadUrl", payloadSymbol.DownloadUrl); - } + if (!String.IsNullOrEmpty(payloadSymbol.DownloadUrl)) + { + writer.WriteAttributeString("DownloadUrl", payloadSymbol.DownloadUrl); + } - writer.WriteAttributeString("LayoutOnly", payloadSymbol.LayoutOnly ? "yes" : "no"); + writer.WriteAttributeString("LayoutOnly", payloadSymbol.LayoutOnly ? "yes" : "no"); - writer.WriteEndElement(); + writer.WriteEndElement(); + } } } diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs index 7b5b9656..e587413e 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs @@ -68,6 +68,14 @@ namespace WixToolset.Core.Burn.Bundles { var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName); + this.Section.AddSymbol(new WixGroupSymbol(this.BundleSymbol.SourceLineNumbers) + { + ParentType = ComplexReferenceParentType.Container, + ParentId = BurnConstants.BurnUXContainerName, + ChildType = ComplexReferenceChildType.Payload, + ChildId = generatedId + }); + var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) { Name = BurnCommon.BundleExtensionDataFileName, diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 1559a646..0c16ea26 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -18,7 +18,7 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateBurnManifestCommand { - public CreateBurnManifestCommand(IMessaging messaging, IEnumerable backendExtensions, string executableName, IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable containers, WixChainSymbol chainSymbol, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, IEnumerable orderedSearches, string intermediateFolder) + public CreateBurnManifestCommand(IMessaging messaging, IEnumerable backendExtensions, string executableName, IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable containers, WixChainSymbol chainSymbol, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, Dictionary> packagesPayloads, IEnumerable orderedSearches, string intermediateFolder) { this.Messaging = messaging; this.BackendExtensions = backendExtensions; @@ -31,6 +31,7 @@ namespace WixToolset.Core.Burn.Bundles this.RollbackBoundaries = boundaries; this.UXContainerPayloads = uxPayloads; this.Payloads = allPayloadsById; + this.PackagesPayloads = packagesPayloads; this.OrderedSearches = orderedSearches; this.IntermediateFolder = intermediateFolder; } @@ -57,6 +58,8 @@ namespace WixToolset.Core.Burn.Bundles private Dictionary Payloads { get; } + private Dictionary> PackagesPayloads { get; } + private IEnumerable Containers { get; } private IEnumerable UXContainerPayloads { get; } @@ -328,7 +331,6 @@ namespace WixToolset.Core.Burn.Bundles var targetCodesByPatch = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); var msiFeaturesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); var msiPropertiesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); - var payloadsByPackage = this.Payloads.Values.ToLookup(p => p.PackageRef); var relatedPackagesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); var slipstreamMspsByPackage = this.Section.Symbols.OfType().ToLookup(r => r.TargetPackageRef); var exitCodesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.ChainPackageId); @@ -569,9 +571,9 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteAttributeString("Id", packagePayloadId); writer.WriteEndElement(); - var packagePayloads = payloadsByPackage[package.PackageId]; + var packagePayloads = this.PackagesPayloads[package.PackageId]; - foreach (var payload in packagePayloads) + foreach (var payload in packagePayloads.Values) { if (payload.Id.Id != packagePayloadId) { diff --git a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs index dacff364..b8b256fd 100644 --- a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs @@ -27,6 +27,7 @@ namespace WixToolset.Core.Burn.Bundles public void Execute() { + var wixGroupPackagesGroupedById = this.Section.Symbols.OfType().Where(g => g.ParentType == ComplexReferenceParentType.Package).ToLookup(g => g.ParentId); var exePackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); var msiPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); var mspPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); @@ -43,7 +44,7 @@ namespace WixToolset.Core.Burn.Bundles var id = package.Id.Id; IntermediateSymbol packagePayload = null; - foreach (var wixGroup in this.Section.Symbols.OfType().Where(g => g.ParentType == ComplexReferenceParentType.Package && g.ParentId == id)) + foreach (var wixGroup in wixGroupPackagesGroupedById[id]) { if (wixGroup.ChildType == ComplexReferenceChildType.PackagePayload) { diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs index c8867eb7..4bc40011 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs @@ -23,7 +23,7 @@ namespace WixToolset.Core.Burn.Bundles { private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'"; - public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary payloadSymbols) + public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary packagePayloads) { this.Messaging = serviceProvider.GetService(); this.BackendHelper = serviceProvider.GetService(); @@ -31,7 +31,7 @@ namespace WixToolset.Core.Burn.Bundles this.BackendExtensions = backendExtensions; - this.AuthoredPayloads = payloadSymbols; + this.PackagePayloads = packagePayloads; this.Section = section; this.Facade = facade; } @@ -44,7 +44,7 @@ namespace WixToolset.Core.Burn.Bundles private IEnumerable BackendExtensions { get; } - private Dictionary AuthoredPayloads { get; } + private Dictionary PackagePayloads { get; } private PackageFacade Facade { get; } @@ -55,7 +55,7 @@ namespace WixToolset.Core.Burn.Bundles /// public void Execute() { - var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; + var packagePayload = this.PackagePayloads[this.Facade.PackageSymbol.PayloadRef]; var msiPackage = (WixBundleMsiPackageSymbol)this.Facade.SpecificPackageSymbol; @@ -182,9 +182,7 @@ namespace WixToolset.Core.Burn.Bundles private ISet GetPayloadTargetNames(string packageId) { - var payloadNames = this.Section.Symbols.OfType() - .Where(p => p.PackageRef == packageId) - .Select(p => p.Name); + var payloadNames = this.PackagePayloads.Values.Select(p => p.Name); return new HashSet(payloadNames, StringComparer.OrdinalIgnoreCase); } @@ -397,13 +395,20 @@ namespace WixToolset.Core.Burn.Bundles var generatedId = this.BackendHelper.GenerateIdentifier("cab", packagePayload.Id.Id, cabinet); var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.PackageSymbol.SourceLineNumbers); + this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) + { + ParentType = ComplexReferenceParentType.Package, + ParentId = this.Facade.PackageId, + ChildType = ComplexReferenceChildType.Payload, + ChildId = generatedId + }); + this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) { Name = cabinetName, SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, Compressed = packagePayload.Compressed, UnresolvedSourceFile = cabinetName, - PackageRef = packagePayload.PackageRef, ContainerRef = packagePayload.ContainerRef, ContentFile = true, Packaging = packagePayload.Packaging, @@ -466,7 +471,7 @@ namespace WixToolset.Core.Burn.Bundles if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) || (!compressed && 0 == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesCompressed))) { - string fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); + var fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); var name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); if (!payloadNames.Contains(name)) @@ -474,13 +479,20 @@ namespace WixToolset.Core.Burn.Bundles var generatedId = this.BackendHelper.GenerateIdentifier("f", packagePayload.Id.Id, record.GetString(2)); var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.PackageSymbol.SourceLineNumbers); + this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) + { + ParentType = ComplexReferenceParentType.Package, + ParentId = this.Facade.PackageId, + ChildType = ComplexReferenceChildType.Payload, + ChildId = generatedId + }); + this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) { Name = name, SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, Compressed = packagePayload.Compressed, UnresolvedSourceFile = name, - PackageRef = packagePayload.PackageRef, ContainerRef = packagePayload.ContainerRef, ContentFile = true, Packaging = packagePayload.Packaging, diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs index 20044235..7ec3104c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -94,7 +94,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6370")] + [Fact] public void PopulatesBAManifestWithPayloadInformation() { var folder = TestData.Get(@"TestData"); @@ -131,10 +131,10 @@ namespace WixToolsetTest.CoreIntegration { "WixPayloadProperties", new List { "Size" } }, }; Assert.Equal(4, payloadElements.Count); - Assert.Equal("", payloadElements[0].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", payloadElements[1].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", payloadElements[2].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", payloadElements[3].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[1].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[2].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[3].GetTestXml(ignoreAttributesByElementName)); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs index 2588ffc1..3457a3b7 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs @@ -12,7 +12,7 @@ - + -- cgit v1.2.3-55-g6feb From 8a3ce82d689e16424620e3b52161f19771d19d1d Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 23 Mar 2021 02:44:02 -0700 Subject: Correctly set Compressed, Description, DisplayName in BootstrapperApplicationData Fixes wixtoolset/issues#6371 Fixes wixtoolset/issues#6372 --- .../Bundles/CreateBootstrapperApplicationManifestCommand.cs | 2 +- src/WixToolset.Core/Compiler_Bundle.cs | 8 ++++++-- src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs | 8 ++++---- .../CustomPackageDescription/CustomPackageDescription.wxs | 8 ++++---- 4 files changed, 15 insertions(+), 11 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs index 1fdd76da..f61dce46 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -129,7 +129,7 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); - writer.WriteAttributeString("Compressed", packagePayload.Compressed == true ? "yes" : "no"); + writer.WriteAttributeString("Compressed", packagePayload.Packaging == PackagingType.Embedded ? "yes" : "no"); if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) { diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index cc4550a8..1c79a11b 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -1961,6 +1961,8 @@ namespace WixToolset.Core string installCondition = null; var cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space. string cacheId = null; + string description = null; + string displayName = null; var logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; var rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; var permanent = YesNoType.NotSet; @@ -2038,10 +2040,10 @@ namespace WixToolset.Core cacheId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Description": - compilerPayload.ParseDescription(attrib); + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "DisplayName": - compilerPayload.ParseDisplayName(attrib); + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "EnableFeatureSelection": enableFeatureSelection = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); @@ -2302,6 +2304,8 @@ namespace WixToolset.Core Attributes = attributes, InstallCondition = installCondition, CacheId = cacheId, + Description = description, + DisplayName = displayName, LogPathVariable = logPathVariable, RollbackLogPathVariable = rollbackPathVariable, }); diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs index 7ec3104c..43386789 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -51,7 +51,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6372")] + [Fact] public void PopulatesBAManifestWithPackageInformation() { var folder = TestData.Get(@"TestData"); @@ -278,7 +278,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6370")] + [Fact] public void PopulatesManifestWithExePackages() { var folder = TestData.Get(@"TestData"); @@ -315,8 +315,8 @@ namespace WixToolsetTest.CoreIntegration { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, }; Assert.Equal(2, exePackageElements.Count); - Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", exePackageElements[1].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", exePackageElements[1].GetTestXml(ignoreAttributesByElementName)); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs index db8b05f2..10c4f91f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs @@ -2,11 +2,11 @@ - - - + + + - + -- cgit v1.2.3-55-g6feb From 87ffd980dc518a7ab40901eeae27b75259ea32b0 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Tue, 30 Mar 2021 21:51:33 -0500 Subject: Add failing test for DownloadUrl placeholder replacement. --- .../PayloadFixture.cs | 61 ++++++++++++++++++++++ .../Payload/DownloadUrlPlaceholdersBundle.wxs | 30 +++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs index 5b6bbeb5..9bd33eac 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs @@ -6,6 +6,7 @@ namespace WixToolsetTest.CoreIntegration using System.Collections.Generic; using System.IO; using System.Linq; + using System.Xml; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using WixToolset.Data; @@ -141,5 +142,65 @@ namespace WixToolsetTest.CoreIntegration Assert.InRange(result.ExitCode, 2, int.MaxValue); } } + + [Fact(Skip = "Test demonstrates failure")] + public void ReplacesDownloadUrlPlaceholders() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Payload", "DownloadUrlPlaceholdersBundle.wxs"), + Path.Combine(folder, "SimpleBundle", "MultiFileBootstrapperApplication.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributesByElementName = new Dictionary> + { + { "Container", new List { "FileSize", "Hash" } }, + { "Payload", new List { "FileSize", "Hash" } }, + }; + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + "", + "", + @"", + @"", + }, payloads); + + var containers = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Container") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, containers); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs new file mode 100644 index 00000000..87bb79f9 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 4449fcc5b8d104817c67135229682c66c3d892ca Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Fri, 2 Apr 2021 14:41:49 -0700 Subject: Enable codepages and languages to be set via .wxl files Fixes wixtoolset/issues#5801 --- .../WixToolset.Core.Burn.csproj | 1 - .../Bind/AttachPatchTransformsCommand.cs | 26 ++-- .../Bind/BindDatabaseCommand.cs | 127 +++++++++-------- .../Bind/BindSummaryInfoCommand.cs | 61 ++++++-- .../Bind/BindTransformCommand.cs | 2 +- .../Bind/CreateDeltaPatchesCommand.cs | 4 +- .../Bind/CreateIdtFileCommand.cs | 42 +++--- .../CreateWindowsInstallerDataFromIRCommand.cs | 32 ++++- .../Bind/GenerateDatabaseCommand.cs | 11 +- .../Bind/GenerateTransformCommand.cs | 1 - .../Bind/ProcessPropertiesCommand.cs | 101 ++++++++++++++ .../Unbind/UnbindTranformCommand.cs | 2 +- .../WixToolset.Core.WindowsInstaller.csproj | 1 + src/WixToolset.Core/BindContext.cs | 8 +- src/WixToolset.Core/CommandLine/BuildCommand.cs | 4 +- src/WixToolset.Core/Compiler.cs | 4 +- src/WixToolset.Core/CompilerCore.cs | 18 ++- src/WixToolset.Core/Compiler_Bundle.cs | 2 +- src/WixToolset.Core/Compiler_Module.cs | 20 +-- src/WixToolset.Core/Compiler_Package.cs | 46 +++--- src/WixToolset.Core/Compiler_Patch.cs | 9 +- src/WixToolset.Core/Compiler_PatchCreation.cs | 2 +- src/WixToolset.Core/IResolver.cs | 9 +- .../Link/CollateLocalizationsCommand.cs | 4 +- src/WixToolset.Core/Linker.cs | 2 +- src/WixToolset.Core/LocalizationParser.cs | 20 +-- src/WixToolset.Core/ResolveResult.cs | 6 +- src/WixToolset.Core/Resolver.cs | 81 ++++++----- .../LanguageFixture.cs | 155 +++++++++++++++++++++ .../LinkerFixture.cs | 4 +- .../WixToolsetTest.CoreIntegration/ParseFixture.cs | 2 +- .../TestData/Language/Package.en-us.wxl | 7 + .../TestData/Language/Package.ja-jp.wxl | 7 + .../TestData/Language/Package.wxl | 7 + .../TestData/Language/Package.wxs | 16 +++ .../Language/PackageWithEnSummaryInfo.ja-jp.wxl | 7 + .../TestData/Language/data/test.txt | 1 + .../VariableResolverFixture.cs | 2 +- 38 files changed, 619 insertions(+), 235 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj index 2e828eae..85bfae69 100644 --- a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj +++ b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj @@ -22,7 +22,6 @@ - diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs index 5f8df92c..76bcd532 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs @@ -91,15 +91,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList(); // Get the patch id from the WixPatchId symbol. - var patchIdSymbol = symbols.OfType().FirstOrDefault(); + var patchSymbol = symbols.OfType().FirstOrDefault(); - if (String.IsNullOrEmpty(patchIdSymbol.Id?.Id)) + if (String.IsNullOrEmpty(patchSymbol.Id?.Id)) { this.Messaging.Write(ErrorMessages.ExpectedPatchIdInWixMsp()); return subStorages; } - if (String.IsNullOrEmpty(patchIdSymbol.ClientPatchId)) + if (String.IsNullOrEmpty(patchSymbol.ClientPatchId)) { this.Messaging.Write(ErrorMessages.ExpectedClientPatchIdInWixMsp()); return subStorages; @@ -115,7 +115,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } // populate MSP summary information - var patchMetadata = this.PopulateSummaryInformation(summaryInfo, symbols, patchIdSymbol, section.Codepage); + var patchMetadata = this.PopulateSummaryInformation(summaryInfo, symbols, patchSymbol); // enumerate transforms var productCodes = new SortedSet(); @@ -168,7 +168,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind mainTransform.Transform.Tables.Remove("Media"); mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); - var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchIdSymbol, mainTransform.Transform, mediaSymbol, baselineSymbol, out var productCode); + var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchSymbol, mainTransform.Transform, mediaSymbol, baselineSymbol, out var productCode); productCode = productCode.ToUpperInvariant(); productCodes.Add(productCode); @@ -211,14 +211,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind productCodes = FinalizePatchProductCodes(symbols, productCodes); // Semicolon delimited list of the product codes that can accept the patch. - summaryInfo.Add(SummaryInformationType.PatchProductCodes, new SummaryInformationSymbol(patchIdSymbol.SourceLineNumbers) + summaryInfo.Add(SummaryInformationType.PatchProductCodes, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) { PropertyId = SummaryInformationType.PatchProductCodes, Value = String.Join(";", productCodes) }); // Semicolon delimited list of transform substorage names in the order they are applied. - summaryInfo.Add(SummaryInformationType.TransformNames, new SummaryInformationSymbol(patchIdSymbol.SourceLineNumbers) + summaryInfo.Add(SummaryInformationType.TransformNames, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) { PropertyId = SummaryInformationType.TransformNames, Value = String.Join(";", transformNames) @@ -262,25 +262,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind return result; } - private Dictionary PopulateSummaryInformation(Dictionary summaryInfo, List symbols, WixPatchIdSymbol patchIdSymbol, int codepage) + private Dictionary PopulateSummaryInformation(Dictionary summaryInfo, List symbols, WixPatchSymbol patchSymbol) { // PID_CODEPAGE if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage)) { // Set the code page by default to the same code page for the // string pool in the database. - AddSummaryInformation(SummaryInformationType.Codepage, codepage.ToString(CultureInfo.InvariantCulture), patchIdSymbol.SourceLineNumbers); + AddSummaryInformation(SummaryInformationType.Codepage, patchSymbol.Codepage?.ToString(CultureInfo.InvariantCulture) ?? "0", patchSymbol.SourceLineNumbers); } // GUID patch code for the patch. - AddSummaryInformation(SummaryInformationType.PatchCode, patchIdSymbol.Id.Id, patchIdSymbol.SourceLineNumbers); + AddSummaryInformation(SummaryInformationType.PatchCode, patchSymbol.Id.Id, patchSymbol.SourceLineNumbers); // Indicates the minimum Windows Installer version that is required to install the patch. - AddSummaryInformation(SummaryInformationType.PatchInstallerRequirement, ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture), patchIdSymbol.SourceLineNumbers); + AddSummaryInformation(SummaryInformationType.PatchInstallerRequirement, ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture), patchSymbol.SourceLineNumbers); if (!summaryInfo.ContainsKey(SummaryInformationType.Security)) { - AddSummaryInformation(SummaryInformationType.Security, "4", patchIdSymbol.SourceLineNumbers); // Read-only enforced; + AddSummaryInformation(SummaryInformationType.Security, "4", patchSymbol.SourceLineNumbers); // Read-only enforced; } // Use authored comments or default to display name. @@ -1090,7 +1090,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// /// Create the #transform for the given main transform. /// - private WindowsInstallerData BuildPairedTransform(Dictionary summaryInfo, Dictionary patchMetadata, WixPatchIdSymbol patchIdSymbol, WindowsInstallerData mainTransform, MediaSymbol mediaSymbol, WixPatchBaselineSymbol baselineSymbol, out string productCode) + private WindowsInstallerData BuildPairedTransform(Dictionary summaryInfo, Dictionary patchMetadata, WixPatchSymbol patchIdSymbol, WindowsInstallerData mainTransform, MediaSymbol mediaSymbol, WixPatchBaselineSymbol baselineSymbol, out string productCode) { productCode = null; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index fb5d7b83..06b51ba1 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -37,7 +37,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.CabbingThreadCount = context.CabbingThreadCount; this.CabCachePath = context.CabCachePath; - this.Codepage = context.Codepage; this.DefaultCompressionLevel = context.DefaultCompressionLevel; this.DelayedFields = context.DelayedFields; this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; @@ -47,6 +46,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.OutputPath = context.OutputPath; this.OutputPdbPath = context.PdbPath; this.PdbType = context.PdbType; + this.ResolvedCodepage = context.ResolvedCodepage; + this.ResolvedSummaryInformationCodepage = context.ResolvedSummaryInformationCodepage; + this.ResolvedLcid = context.ResolvedLcid; this.SuppressLayout = context.SuppressLayout; this.SubStorages = subStorages; @@ -67,8 +69,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IPathResolver PathResolver { get; } - private int Codepage { get; } - private int CabbingThreadCount { get; } private string CabCachePath { get; } @@ -95,6 +95,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind private string OutputPdbPath { get; } + private int? ResolvedCodepage { get; } + + private int? ResolvedSummaryInformationCodepage { get; } + + private int? ResolvedLcid { get; } + private bool SuppressAddingValidationRows { get; } private bool SuppressLayout { get; } @@ -111,21 +117,22 @@ namespace WixToolset.Core.WindowsInstaller.Bind public IBindResult Execute() { - if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) && !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) + if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) { this.Messaging.Write(ErrorMessages.IntermediatesMustBeResolved(this.Intermediate.Id)); } var section = this.Intermediate.Sections.Single(); + var packageSymbol = (section.Type == SectionType.Product) ? this.GetSingleSymbol(section) : null; + var moduleSymbol = (section.Type == SectionType.Module) ? this.GetSingleSymbol(section) : null; + var patchSymbol = (section.Type == SectionType.Patch) ? this.GetSingleSymbol(section) : null; + var fileTransfers = new List(); var trackedFiles = new List(); var containsMergeModules = false; - // If there are any fields to resolve later, create the cache to populate during bind. - var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; - // Load standard tables, authored custom tables, and extension custom tables. TableDefinitionCollection tableDefinitions; { @@ -135,7 +142,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind tableDefinitions = command.TableDefinitions; } - // Process the summary information table before the other tables. + // Calculate codepage + var codepage = this.CalculateCodepage(packageSymbol, moduleSymbol, patchSymbol); + + // Process properties and create the delayed variable cache if needed. + Dictionary variableCache = null; + string productLanguage = null; + { + var command = new ProcessPropertiesCommand(section, packageSymbol, this.ResolvedLcid ?? 0, this.DelayedFields.Any(), this.WindowsInstallerBackendHelper); + command.Execute(); + + variableCache = command.DelayedVariablesCache; + productLanguage = command.ProductLanguage; + } + + // Process the summary information table after properties are processed. bool compressed; bool longNames; int installerVersion; @@ -144,7 +165,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var branding = this.ServiceProvider.GetService(); - var command = new BindSummaryInfoCommand(section, this.WindowsInstallerBackendHelper, branding); + var command = new BindSummaryInfoCommand(section, this.ResolvedSummaryInformationCodepage, productLanguage, this.WindowsInstallerBackendHelper, branding); command.Execute(); compressed = command.Compressed; @@ -154,49 +175,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind modularizationSuffix = command.ModularizationSuffix; } - // Add binder variables for all properties. - if (SectionType.Product == section.Type || variableCache != null) - { - foreach (var propertyRow in section.Symbols.OfType()) - { - // Set the ProductCode if it is to be generated. - if ("ProductCode".Equals(propertyRow.Id.Id, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal)) - { - propertyRow.Value = this.WindowsInstallerBackendHelper.CreateGuid(); - -#if TODO_PATCHING // Is this still necessary? - - // Update the target ProductCode in any instance transforms. - foreach (SubStorage subStorage in this.Output.SubStorages) - { - Output subStorageOutput = subStorage.Data; - if (OutputType.Transform != subStorageOutput.Type) - { - continue; - } - - Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; - foreach (Row row in instanceSummaryInformationTable.Rows) - { - if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) - { - row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); - break; - } - } - } -#endif - } - - // Add the property name and value to the variableCache. - if (variableCache != null) - { - var key = String.Concat("property.", propertyRow.Id.Id); - variableCache[key] = propertyRow.Value; - } - } - } - // Sequence all the actions. { var command = new SequenceActionsCommand(this.Messaging, section); @@ -415,7 +393,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better. WindowsInstallerData data; { - var command = new CreateWindowsInstallerDataFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions, this.WindowsInstallerBackendHelper); + var command = new CreateWindowsInstallerDataFromIRCommand(this.Messaging, section, tableDefinitions, codepage, this.BackendExtensions, this.WindowsInstallerBackendHelper); data = command.Execute(); } @@ -450,7 +428,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (SectionType.Patch == section.Type && this.DeltaBinaryPatch) { - var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType().FirstOrDefault()); + var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType().FirstOrDefault()); command.Execute(); } @@ -508,7 +486,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var trackMsi = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final); trackedFiles.Add(trackMsi); - var command = new GenerateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, data, trackMsi.Path, tableDefinitions, this.IntermediateFolder, this.Codepage, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false); + var command = new GenerateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, data, trackMsi.Path, tableDefinitions, this.IntermediateFolder, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false); command.Execute(); trackedFiles.AddRange(command.GeneratedTemporaryFiles); @@ -532,7 +510,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (this.Messaging.EncounteredError) { return null; - } + } // Validate the output if there are CUBe files and we're not explicitly suppressing validation. if (this.CubeFiles != null && !this.SuppressValidation) @@ -575,6 +553,43 @@ namespace WixToolset.Core.WindowsInstaller.Bind return result; } + private int CalculateCodepage(WixPackageSymbol packageSymbol, WixModuleSymbol moduleSymbol, WixPatchSymbol patchSymbol) + { + var codepage = packageSymbol?.Codepage ?? moduleSymbol?.Codepage ?? patchSymbol?.Codepage; + + if (String.IsNullOrEmpty(codepage)) + { + codepage = this.ResolvedCodepage?.ToString() ?? "65001"; + + if (packageSymbol != null) + { + packageSymbol.Codepage = codepage; + } + else if (moduleSymbol != null) + { + moduleSymbol.Codepage = codepage; + } + else if (patchSymbol != null) + { + patchSymbol.Codepage = codepage; + } + } + + return this.WindowsInstallerBackendHelper.GetValidCodePage(codepage); + } + + private T GetSingleSymbol(IntermediateSection section) where T : IntermediateSymbol + { + var symbols = section.Symbols.OfType().ToList(); + + if (1 != symbols.Count) + { + throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); + } + + return symbols[0]; + } + private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, WindowsInstallerData data) { WixOutput wixout; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs index babe0c1b..41da2a13 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs @@ -14,15 +14,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class BindSummaryInfoCommand { - public BindSummaryInfoCommand(IntermediateSection section, IBackendHelper backendHelper, IWixBranding branding) + public BindSummaryInfoCommand(IntermediateSection section, int? summaryInformationCodepage, string productLanguage, IBackendHelper backendHelper, IWixBranding branding) { this.Section = section; + this.SummaryInformationCodepage = summaryInformationCodepage; + this.ProductLanguage = productLanguage; this.BackendHelper = backendHelper; this.Branding = branding; } private IntermediateSection Section { get; } + private int? SummaryInformationCodepage { get; } + + private string ProductLanguage { get; } + private IBackendHelper BackendHelper { get; } private IWixBranding Branding { get; } @@ -53,6 +59,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.InstallerVersion = 0; this.ModularizationSuffix = null; + SummaryInformationSymbol summaryInformationCodepageSymbol = null; + SummaryInformationSymbol platformAndLanguageSymbol = null; var foundCreateDateTime = false; var foundLastSaveDataTime = false; var foundCreatingApplication = false; @@ -64,20 +72,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind switch (summaryInformationSymbol.PropertyId) { case SummaryInformationType.Codepage: // PID_CODEPAGE - // make sure the code page is an int and not a web name or null - var codepage = summaryInformationSymbol.Value; - - if (String.IsNullOrEmpty(codepage)) - { - codepage = "0"; - } - else - { - summaryInformationSymbol.Value = this.BackendHelper.GetValidCodePage(codepage, false, false, summaryInformationSymbol.SourceLineNumbers).ToString(CultureInfo.InvariantCulture); - } + summaryInformationCodepageSymbol = summaryInformationSymbol; break; + case SummaryInformationType.PlatformAndLanguage: - this.Platform = GetPlatformFromSummaryInformation(summaryInformationSymbol.Value); + platformAndLanguageSymbol = summaryInformationSymbol; break; case SummaryInformationType.PackageCode: // PID_REVNUMBER @@ -117,7 +116,31 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - // set the revision number (package/patch code) if it should be automatically generated + // Ensure the codepage is set properly. + if (summaryInformationCodepageSymbol == null) + { + summaryInformationCodepageSymbol = this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.Codepage + }); + } + + var codepage = summaryInformationCodepageSymbol.Value; + + if (String.IsNullOrEmpty(codepage)) + { + codepage = this.SummaryInformationCodepage?.ToString(CultureInfo.InvariantCulture) ?? "1252"; + } + + summaryInformationCodepageSymbol.Value = this.BackendHelper.GetValidCodePage(codepage, onlyAnsi: true).ToString(CultureInfo.InvariantCulture); + + // Ensure the language is set properly and figure out what platform we are targeting. + if (platformAndLanguageSymbol != null) + { + this.Platform = EnsureLanguageAndGetPlatformFromSummaryInformation(platformAndLanguageSymbol, this.ProductLanguage); + } + + // Set the revision number (package/patch code) if it should be automatically generated. if (!foundPackageCode) { this.Section.AddSymbol(new SummaryInformationSymbol(null) @@ -158,11 +181,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - private static Platform GetPlatformFromSummaryInformation(string value) + private static Platform EnsureLanguageAndGetPlatformFromSummaryInformation(SummaryInformationSymbol symbol, string language) { + var value = symbol.Value; var separatorIndex = value.IndexOf(';'); var platformValue = separatorIndex > 0 ? value.Substring(0, separatorIndex) : value; + // If the language was provided and there was language value after the separator + // (or the separator was absent) then use the provided language. + if (!String.IsNullOrEmpty(language) && (separatorIndex < 0 || separatorIndex + 1 == value.Length)) + { + symbol.Value = platformValue + ';' + language; + } + switch (platformValue) { case "x64": diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs index 3a9bd545..3379ec5d 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs @@ -438,7 +438,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns) { - var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true); + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true); command.Execute(); } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs index b587e6d9..47d8399f 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs @@ -15,7 +15,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind /// internal class CreateDeltaPatchesCommand { - public CreateDeltaPatchesCommand(List fileFacades, string intermediateFolder, WixPatchIdSymbol wixPatchId) + public CreateDeltaPatchesCommand(List fileFacades, string intermediateFolder, WixPatchSymbol wixPatchId) { this.FileFacades = fileFacades; this.IntermediateFolder = intermediateFolder; @@ -24,7 +24,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IEnumerable FileFacades { get; } - private WixPatchIdSymbol WixPatchId { get; } + private WixPatchSymbol WixPatchId { get; } private string IntermediateFolder { get; } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs index f09a2e47..ff03413c 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs @@ -36,23 +36,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind public void Execute() { // write out the table to an IDT file - Encoding encoding; - - // If UTF8 encoding, use the UTF8-specific constructor to avoid writing - // the byte order mark at the beginning of the file - if (this.Codepage == Encoding.UTF8.CodePage) - { - encoding = new UTF8Encoding(false, true); - } - else - { - if (this.Codepage == 0) - { - this.Codepage = Encoding.ASCII.CodePage; - } - - encoding = Encoding.GetEncoding(this.Codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); - } + var encoding = GetCodepageEncoding(this.Codepage); this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt")); @@ -209,6 +193,30 @@ namespace WixToolset.Core.WindowsInstaller.Bind .Replace('\n', '\x19'); } + private static Encoding GetCodepageEncoding(int codepage) + { + Encoding encoding; + + // If UTF8 encoding, use the UTF8-specific constructor to avoid writing + // the byte order mark at the beginning of the file + if (codepage == Encoding.UTF8.CodePage) + { + encoding = new UTF8Encoding(false, true); + } + else + { + if (codepage == 0) + { + codepage = Encoding.ASCII.CodePage; + } + + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); + } + + return encoding; + } /// /// Gets the type of the column in IDT format. diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs index bc95fd4c..1bd00900 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs @@ -18,11 +18,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind internal class CreateWindowsInstallerDataFromIRCommand { - public CreateWindowsInstallerDataFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, IEnumerable backendExtensions, IWindowsInstallerBackendHelper backendHelper) + public CreateWindowsInstallerDataFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, int codepage, IEnumerable backendExtensions, IWindowsInstallerBackendHelper backendHelper) { this.Messaging = messaging; this.Section = section; this.TableDefinitions = tableDefinitions; + this.Codepage = codepage; this.BackendExtensions = backendExtensions; this.BackendHelper = backendHelper; this.GeneratedShortNames = new Dictionary>(); @@ -36,6 +37,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind private TableDefinitionCollection TableDefinitions { get; } + private int Codepage { get; } + private IntermediateSection Section { get; } private Dictionary> GeneratedShortNames { get; } @@ -46,7 +49,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { this.Data = new WindowsInstallerData(this.Section.Symbols.First().SourceLineNumbers) { - Codepage = this.Section.Codepage, + Codepage = this.Codepage, Type = SectionTypeToOutputType(this.Section.Type) }; @@ -219,6 +222,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.AddWixEnsureTableSymbol((WixEnsureTableSymbol)symbol); break; + case SymbolDefinitionType.WixPackage: + this.AddWixPackageSymbol((WixPackageSymbol)symbol); + break; + // Symbols used internally and are not added to the output. case SymbolDefinitionType.WixBuildInfo: case SymbolDefinitionType.WixBindUpdatedFiles: @@ -237,7 +244,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind case SymbolDefinitionType.WixOrdering: case SymbolDefinitionType.WixPatchBaseline: case SymbolDefinitionType.WixPatchFamilyGroup: - case SymbolDefinitionType.WixPatchId: + case SymbolDefinitionType.WixPatch: case SymbolDefinitionType.WixPatchRef: case SymbolDefinitionType.WixPatchTarget: case SymbolDefinitionType.WixProperty: @@ -1214,6 +1221,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.Data.EnsureTable(tableDefinition); } + private void AddWixPackageSymbol(WixPackageSymbol symbol) + { + // TODO: Remove the following from the compiler and do it here instead. + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); + //if (null != upgradeCode) + //{ + // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); + //} + + //if (isPerMachine) + //{ + // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); + //} + } + private bool AddSymbolFromExtension(IntermediateSymbol symbol) { foreach (var extension in this.BackendExtensions) diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs index f125f497..b8cca752 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs @@ -18,7 +18,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { private const string IdtsSubFolder = "_idts"; - public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, int codepage, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory) + public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory) { this.Messaging = messaging; this.BackendHelper = backendHelper; @@ -27,14 +27,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.OutputPath = outputPath; this.TableDefinitions = tableDefinitions; this.IntermediateFolder = intermediateFolder; - this.Codepage = codepage; this.KeepAddedColumns = keepAddedColumns; this.SuppressAddingValidationRows = suppressAddingValidationRows; this.UseSubDirectory = useSubdirectory; } - private int Codepage { get; } - private IBackendHelper BackendHelper { get; } private FileSystemManager FileSystemManager { get; } @@ -88,12 +85,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind type |= OpenDatabase.OpenPatchFile; } - // Localize the codepage if a value was specified directly. - if (-1 != this.Codepage) - { - this.Data.Codepage = this.Codepage; - } - try { Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs index ef141795..faa03762 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs @@ -9,7 +9,6 @@ namespace WixToolset.Core.WindowsInstaller using WixToolset.Data; using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility.Services; /// diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs new file mode 100644 index 00000000..217609be --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs @@ -0,0 +1,101 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class ProcessPropertiesCommand + { + public ProcessPropertiesCommand(IntermediateSection section, WixPackageSymbol packageSymbol, int fallbackLcid, bool populateDelayedVariables, IBackendHelper backendHelper) + { + this.Section = section; + this.PackageSymbol = packageSymbol; + this.FallbackLcid = fallbackLcid; + this.PopulateDelayedVariables = populateDelayedVariables; + this.BackendHelper = backendHelper; + } + + private IntermediateSection Section { get; } + + private WixPackageSymbol PackageSymbol { get; } + + private int FallbackLcid { get; } + + private bool PopulateDelayedVariables { get; } + + private IBackendHelper BackendHelper { get; } + + public Dictionary DelayedVariablesCache { get; private set; } + + public string ProductLanguage { get; private set; } + + public void Execute() + { + PropertySymbol languageSymbol = null; + var variableCache = this.PopulateDelayedVariables ? new Dictionary(StringComparer.OrdinalIgnoreCase) : null; + + if (SectionType.Product == this.Section.Type || variableCache != null) + { + foreach (var propertySymbol in this.Section.Symbols.OfType()) + { + // Set the ProductCode if it is to be generated. + if ("ProductCode" == propertySymbol.Id.Id && "*".Equals(propertySymbol.Value, StringComparison.Ordinal)) + { + propertySymbol.Value = this.BackendHelper.CreateGuid(); + +#if TODO_PATCHING // Is this still necessary? + // Update the target ProductCode in any instance transforms. + foreach (SubStorage subStorage in this.Output.SubStorages) + { + Output subStorageOutput = subStorage.Data; + if (OutputType.Transform != subStorageOutput.Type) + { + continue; + } + + Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; + foreach (Row row in instanceSummaryInformationTable.Rows) + { + if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) + { + row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); + break; + } + } + } +#endif + } + else if ("ProductLanguage" == propertySymbol.Id.Id) + { + languageSymbol = propertySymbol; + } + + // Add the property name and value to the variableCache. + if (variableCache != null) + { + variableCache[$"property.{propertySymbol.Id.Id}"] = propertySymbol.Value; + } + } + + if (this.Section.Type == SectionType.Product && String.IsNullOrEmpty(languageSymbol?.Value)) + { + if (languageSymbol == null) + { + languageSymbol = this.Section.AddSymbol(new PropertySymbol(this.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, "ProductLanguage"))); + } + + this.PackageSymbol.Language = this.FallbackLcid.ToString(); + languageSymbol.Value = this.FallbackLcid.ToString(); + } + } + + this.DelayedVariablesCache = variableCache; + this.ProductLanguage = languageSymbol?.Value; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs index 9f649435..f40aed4e 100644 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs @@ -302,7 +302,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind private void GenerateDatabase(WindowsInstallerData output, string databaseFile) { - var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); + var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); command.Execute(); } } diff --git a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj b/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj index 56262373..b08f337f 100644 --- a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj +++ b/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj @@ -20,6 +20,7 @@ + diff --git a/src/WixToolset.Core/BindContext.cs b/src/WixToolset.Core/BindContext.cs index 6b0a09a2..09454824 100644 --- a/src/WixToolset.Core/BindContext.cs +++ b/src/WixToolset.Core/BindContext.cs @@ -26,8 +26,6 @@ namespace WixToolset.Core public string CabCachePath { get; set; } - public int Codepage { get; set; } - public CompressionLevel? DefaultCompressionLevel { get; set; } public IEnumerable DelayedFields { get; set; } @@ -50,6 +48,12 @@ namespace WixToolset.Core public string PdbPath { get; set; } + public int? ResolvedCodepage { get; set; } + + public int? ResolvedSummaryInformationCodepage { get; set; } + + public int? ResolvedLcid { get; set; } + public IEnumerable SuppressIces { get; set; } public bool SuppressValidation { get; set; } diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index 9efef830..59aa2f1f 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -338,7 +338,9 @@ namespace WixToolset.Core.CommandLine var context = this.ServiceProvider.GetService(); //context.CabbingThreadCount = this.CabbingThreadCount; context.CabCachePath = cabCachePath; - context.Codepage = resolveResult.Codepage; + context.ResolvedCodepage = resolveResult.Codepage; + context.ResolvedSummaryInformationCodepage = resolveResult.SummaryInformationCodepage; + context.ResolvedLcid = resolveResult.PackageLcid; context.DefaultCompressionLevel = this.DefaultCompressionLevel; context.DelayedFields = resolveResult.DelayedFields; context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index c2783481..926a46c5 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -384,7 +384,7 @@ namespace WixToolset.Core { var id = String.Concat(this.Core.ActiveSection.Id, ".", propertyId.Id); - section = this.Core.CreateSection(id, SectionType.Fragment, this.Core.ActiveSection.Codepage, this.Context.CompilationId); + section = this.Core.CreateSection(id, SectionType.Fragment, this.Context.CompilationId); // Reference the property in the active section. this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, propertyId.Id); @@ -6159,7 +6159,7 @@ namespace WixToolset.Core // NOTE: Id is not required for Fragments, this is a departure from the normal run of the mill processing. - this.Core.CreateActiveSection(id?.Id, SectionType.Fragment, 0, this.Context.CompilationId); + this.Core.CreateActiveSection(id?.Id, SectionType.Fragment, this.Context.CompilationId); var featureDisplay = 0; foreach (var child in node.Elements()) diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index 0bc63d79..df532d74 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -680,7 +680,7 @@ namespace WixToolset.Core Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); - string value = this.GetAttributeValue(sourceLineNumbers, attribute); + var value = this.GetAttributeValue(sourceLineNumbers, attribute); if (0 < value.Length) { @@ -692,7 +692,7 @@ namespace WixToolset.Core { try { - int integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat); + var integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat); if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) { @@ -1036,12 +1036,11 @@ namespace WixToolset.Core /// /// Unique identifier for the section. /// Type of section to create. - /// Codepage for the resulting database for this ection. - /// + /// Unique identifier for the compilation. /// New section. - internal IntermediateSection CreateActiveSection(string id, SectionType type, int codepage, string compilationId) + internal IntermediateSection CreateActiveSection(string id, SectionType type, string compilationId) { - this.ActiveSection = this.CreateSection(id, type, codepage, compilationId); + this.ActiveSection = this.CreateSection(id, type, compilationId); this.activeSectionCachedInlinedDirectoryIds = new Dictionary(); this.activeSectionSimpleReferences = new HashSet(); @@ -1054,12 +1053,11 @@ namespace WixToolset.Core /// /// Unique identifier for the section. /// Type of section to create. - /// Codepage for the resulting database for this ection. - /// + /// Unique identifier for the compilation. /// New section. - internal IntermediateSection CreateSection(string id, SectionType type, int codepage, string compilationId) + internal IntermediateSection CreateSection(string id, SectionType type, string compilationId) { - var section = new IntermediateSection(id, type, codepage, compilationId); + var section = new IntermediateSection(id, type, compilationId); this.intermediate.Sections.Add(section); diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 7e07532e..779ad376 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -259,7 +259,7 @@ namespace WixToolset.Core } this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; - this.Core.CreateActiveSection(this.activeName, SectionType.Bundle, 0, this.Context.CompilationId); + this.Core.CreateActiveSection(this.activeName, SectionType.Bundle, this.Context.CompilationId); // Now that the active section is initialized, process only extension attributes and the special ProviderKey attribute. foreach (var attrib in node.Attributes()) diff --git a/src/WixToolset.Core/Compiler_Module.cs b/src/WixToolset.Core/Compiler_Module.cs index 597bc25c..59fe9164 100644 --- a/src/WixToolset.Core/Compiler_Module.cs +++ b/src/WixToolset.Core/Compiler_Module.cs @@ -104,7 +104,7 @@ namespace WixToolset.Core try { this.compilingModule = true; // notice that we are actually building a Merge Module here - this.Core.CreateActiveSection(this.activeName, SectionType.Module, codepage, this.Context.CompilationId); + this.Core.CreateActiveSection(this.activeName, SectionType.Module, this.Context.CompilationId); foreach (var child in node.Elements()) { @@ -232,15 +232,6 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { - if (!setCodepage) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Codepage, - Value = "1252" - }); - } - if (!setPackageName) { this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) @@ -259,21 +250,20 @@ namespace WixToolset.Core }); } - var symbol = this.Core.AddSymbol(new ModuleSignatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, this.activeName, this.activeLanguage)) + var symbol = this.Core.AddSymbol(new WixModuleSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, this.activeName, this.activeLanguage)) { - ModuleID = this.activeName, + ModuleId = this.activeName, + Language = this.activeLanguage, Version = version }); - symbol.Set((int)ModuleSignatureSymbolFields.Language, this.activeLanguage); - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) { PropertyId = SummaryInformationType.PackageCode, Value = moduleId }); - this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform); + this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, this.activeLanguage); } } finally diff --git a/src/WixToolset.Core/Compiler_Package.cs b/src/WixToolset.Core/Compiler_Package.cs index fed08001..afe02f08 100644 --- a/src/WixToolset.Core/Compiler_Package.cs +++ b/src/WixToolset.Core/Compiler_Package.cs @@ -5,7 +5,6 @@ namespace WixToolset.Core using System; using System.Collections; using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Xml.Linq; @@ -28,10 +27,10 @@ namespace WixToolset.Core var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); var compressed = YesNoDefaultType.Default; var sourceBits = 0; - var codepage = 65001; + string codepage = null; var productCode = "*"; + string productLanguage = null; var isPerMachine = true; - string installScope = null; string upgradeCode = null; string manufacturer = null; string version = null; @@ -53,7 +52,7 @@ namespace WixToolset.Core switch (attrib.Name.LocalName) { case "Codepage": - codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); break; case "Compressed": compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); @@ -62,7 +61,7 @@ namespace WixToolset.Core msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); break; case "Language": - this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + productLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); break; case "Manufacturer": manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); @@ -82,7 +81,7 @@ namespace WixToolset.Core productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); break; case "Scope": - installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + var installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); switch (installScope) { case "perMachine": @@ -129,11 +128,6 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } - if (null == this.activeLanguage) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); - } - if (null == manufacturer) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); @@ -171,11 +165,11 @@ namespace WixToolset.Core try { this.compilingProduct = true; - this.Core.CreateActiveSection(productCode, SectionType.Product, codepage, this.Context.CompilationId); + this.Core.CreateActiveSection(productCode, SectionType.Product, this.Context.CompilationId); this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), this.activeLanguage, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); if (null != upgradeCode) @@ -188,7 +182,7 @@ namespace WixToolset.Core this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); } - this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform); + this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, productLanguage); this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) { @@ -198,7 +192,7 @@ namespace WixToolset.Core var contextValues = new Dictionary { - ["ProductLanguage"] = this.activeLanguage, + ["ProductLanguage"] = productLanguage, ["ProductVersion"] = version, ["UpgradeCode"] = upgradeCode }; @@ -360,14 +354,17 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { - if (!isCodepageSet) + this.Core.AddSymbol(new WixPackageSymbol(sourceLineNumbers) { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Codepage, - Value = "1252" - }); - } + PackageId = productCode, + UpgradeCode = upgradeCode, + Name = this.activeName, + Language = productLanguage, + Version = version, + Manufacturer = manufacturer, + Attributes = isPerMachine ? WixPackageAttributes.PerMachine : WixPackageAttributes.None, + Codepage = codepage, + }); if (!isPackageNameSet) { @@ -435,7 +432,7 @@ namespace WixToolset.Core } } - private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform) + private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform, string language) { if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) { @@ -464,7 +461,7 @@ namespace WixToolset.Core this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) { PropertyId = SummaryInformationType.PlatformAndLanguage, - Value = String.Format(CultureInfo.InvariantCulture, "{0};{1}", platform, this.activeLanguage) + Value = $"{platform};{language}" }); this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) @@ -478,7 +475,6 @@ namespace WixToolset.Core PropertyId = SummaryInformationType.Security, Value = "2" }); - } /// diff --git a/src/WixToolset.Core/Compiler_Patch.cs b/src/WixToolset.Core/Compiler_Patch.cs index a2cadd67..c9cae183 100644 --- a/src/WixToolset.Core/Compiler_Patch.cs +++ b/src/WixToolset.Core/Compiler_Patch.cs @@ -24,7 +24,7 @@ namespace WixToolset.Core { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string patchId = null; - var codepage = 0; + string codepage = null; ////bool versionMismatches = false; ////bool productMismatches = false; var allowRemoval = false; @@ -53,7 +53,7 @@ namespace WixToolset.Core patchId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); break; case "Codepage": - codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); break; case "AllowMajorVersionMismatches": ////versionMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); @@ -149,7 +149,7 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); } - this.Core.CreateActiveSection(this.activeName, SectionType.Patch, codepage, this.Context.CompilationId); + this.Core.CreateActiveSection(this.activeName, SectionType.Patch, this.Context.CompilationId); foreach (var child in node.Elements()) { @@ -197,8 +197,9 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { - this.Core.AddSymbol(new WixPatchIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, patchId)) + this.Core.AddSymbol(new WixPatchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, patchId)) { + Codepage = codepage, ClientPatchId = clientPatchId, OptimizePatchSizeForLargeFiles = optimizePatchSizeForLargeFiles, ApiPatchingSymbolFlags = apiPatchingSymbolFlags, diff --git a/src/WixToolset.Core/Compiler_PatchCreation.cs b/src/WixToolset.Core/Compiler_PatchCreation.cs index 7675a5c0..81ae4121 100644 --- a/src/WixToolset.Core/Compiler_PatchCreation.cs +++ b/src/WixToolset.Core/Compiler_PatchCreation.cs @@ -82,7 +82,7 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } - this.Core.CreateActiveSection(this.activeName, SectionType.PatchCreation, codepage, this.Context.CompilationId); + this.Core.CreateActiveSection(this.activeName, SectionType.PatchCreation, this.Context.CompilationId); foreach (var child in node.Elements()) { diff --git a/src/WixToolset.Core/IResolver.cs b/src/WixToolset.Core/IResolver.cs index 3004ad2c..db25edbe 100644 --- a/src/WixToolset.Core/IResolver.cs +++ b/src/WixToolset.Core/IResolver.cs @@ -4,9 +4,16 @@ namespace WixToolset.Core { using WixToolset.Extensibility.Data; -#pragma warning disable 1591 // TODO: add documentation + /// + /// Resolves localization and bind variables. + /// public interface IResolver { + /// + /// Resolve localization and bind variables. + /// + /// Resolve context. + /// Resolve result. IResolveResult Resolve(IResolveContext context); } } diff --git a/src/WixToolset.Core/Link/CollateLocalizationsCommand.cs b/src/WixToolset.Core/Link/CollateLocalizationsCommand.cs index ffa66210..d5c69838 100644 --- a/src/WixToolset.Core/Link/CollateLocalizationsCommand.cs +++ b/src/WixToolset.Core/Link/CollateLocalizationsCommand.cs @@ -1,4 +1,4 @@ -// 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. +// 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.Link { @@ -65,7 +65,7 @@ namespace WixToolset.Core.Link } } - return new Localization(existingLocalization.Codepage, existingLocalization.Culture, variables, controls); + return new Localization(existingLocalization.Codepage ?? localization.Codepage, existingLocalization.SummaryInformationCodepage ?? localization.SummaryInformationCodepage, existingLocalization.Culture, variables, controls); } } } diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index 320f7d1f..2c8508a8 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -180,7 +180,7 @@ namespace WixToolset.Core // Create a new section to hold the linked content. Start with the entry section's // metadata. - var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type, find.EntrySection.Codepage); + var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type); var sectionCount = 0; diff --git a/src/WixToolset.Core/LocalizationParser.cs b/src/WixToolset.Core/LocalizationParser.cs index dd5144ca..d6113fc6 100644 --- a/src/WixToolset.Core/LocalizationParser.cs +++ b/src/WixToolset.Core/LocalizationParser.cs @@ -86,7 +86,8 @@ namespace WixToolset.Core private static Localization ParseWixLocalizationElement(IMessaging messaging, XElement node) { var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); - var codepage = -1; + int? codepage = null; + int? summaryInformationCodepage = null; string culture = null; foreach (var attrib in node.Attributes()) @@ -98,6 +99,9 @@ namespace WixToolset.Core case "Codepage": codepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); break; + case "SummaryInformationCodepage": + summaryInformationCodepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); + break; case "Culture": culture = attrib.Value; break; @@ -143,7 +147,7 @@ namespace WixToolset.Core } } - return messaging.EncounteredError ? null : new Localization(codepage, culture, variables, localizedControls); + return messaging.EncounteredError ? null : new Localization(codepage, summaryInformationCodepage, culture, variables, localizedControls); } /// @@ -254,24 +258,12 @@ namespace WixToolset.Core break; case "RightToLeft": rightToLeft = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - //if (YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib)) - //{ - // attribs |= MsiInterop.MsidbControlAttributesRTLRO; - //} break; case "RightAligned": rightAligned = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - //if (YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib)) - //{ - // attribs |= MsiInterop.MsidbControlAttributesRightAligned; - //} break; case "LeftScroll": leftScroll = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - //if (YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib)) - //{ - // attribs |= MsiInterop.MsidbControlAttributesLeftScroll; - //} break; default: Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); diff --git a/src/WixToolset.Core/ResolveResult.cs b/src/WixToolset.Core/ResolveResult.cs index 6b6bc7c4..38b432b0 100644 --- a/src/WixToolset.Core/ResolveResult.cs +++ b/src/WixToolset.Core/ResolveResult.cs @@ -8,7 +8,11 @@ namespace WixToolset.Core internal class ResolveResult : IResolveResult { - public int Codepage { get; set; } + public int? Codepage { get; set; } + + public int? SummaryInformationCodepage { get; set; } + + public int? PackageLcid { get; set; } public IEnumerable DelayedFields { get; set; } diff --git a/src/WixToolset.Core/Resolver.cs b/src/WixToolset.Core/Resolver.cs index 92c2a9c9..f4cb2fd6 100644 --- a/src/WixToolset.Core/Resolver.cs +++ b/src/WixToolset.Core/Resolver.cs @@ -4,6 +4,7 @@ namespace WixToolset.Core { using System; using System.Collections.Generic; + using System.Globalization; using System.Linq; using WixToolset.Core.Bind; using WixToolset.Data; @@ -22,26 +23,12 @@ namespace WixToolset.Core this.ServiceProvider = serviceProvider; this.Messaging = serviceProvider.GetService(); - - this.VariableResolver = serviceProvider.GetService(); } private IServiceProvider ServiceProvider { get; } private IMessaging Messaging { get; } - private IVariableResolver VariableResolver { get; set; } - - public IEnumerable BindPaths { get; set; } - - public string IntermediateFolder { get; set; } - - public Intermediate IntermediateRepresentation { get; set; } - - public IEnumerable Localizations { get; set; } - - public IEnumerable FilterCultures { get; set; } - public IResolveResult Resolve(IResolveContext context) { foreach (var extension in context.Extensions) @@ -52,11 +39,26 @@ namespace WixToolset.Core ResolveResult resolveResult = null; try { - var codepage = this.PopulateVariableResolver(context); + var filteredLocalizations = FilterLocalizations(context); + + var variableResolver = this.CreateVariableResolver(context, filteredLocalizations); + + this.LocalizeUI(variableResolver, context.IntermediateRepresentation); + + resolveResult = this.DoResolve(context, variableResolver); + + var primaryLocalization = filteredLocalizations.FirstOrDefault(); + + if (primaryLocalization != null) + { + this.TryGetCultureInfo(primaryLocalization.Culture, out var cultureInfo); - this.LocalizeUI(context); + resolveResult.Codepage = primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; - resolveResult = this.DoResolve(context, codepage); + resolveResult.SummaryInformationCodepage = primaryLocalization.SummaryInformationCodepage ?? primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; + + resolveResult.PackageLcid = cultureInfo?.LCID; + } } finally { @@ -69,7 +71,7 @@ namespace WixToolset.Core return resolveResult; } - private ResolveResult DoResolve(IResolveContext context, int? codepage) + private ResolveResult DoResolve(IResolveContext context, IVariableResolver variableResolver) { var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch); @@ -80,7 +82,7 @@ namespace WixToolset.Core var command = new ResolveFieldsCommand(); command.Messaging = this.Messaging; command.BuildingPatch = buildingPatch; - command.VariableResolver = this.VariableResolver; + command.VariableResolver = variableResolver; command.BindPaths = context.BindPaths; command.Extensions = context.Extensions; command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; @@ -118,7 +120,6 @@ namespace WixToolset.Core return new ResolveResult { - Codepage = codepage.HasValue ? codepage.Value : -1, ExpectedEmbeddedFiles = expectedEmbeddedFiles, DelayedFields = delayedFields, IntermediateRepresentation = context.IntermediateRepresentation @@ -128,13 +129,13 @@ namespace WixToolset.Core /// /// Localize dialogs and controls. /// - private void LocalizeUI(IResolveContext context) + private void LocalizeUI(IVariableResolver variableResolver, Intermediate intermediate) { - foreach (var section in context.IntermediateRepresentation.Sections) + foreach (var section in intermediate.Sections) { foreach (var symbol in section.Symbols.OfType()) { - if (this.VariableResolver.TryGetLocalizedControl(symbol.Id.Id, null, out var localizedControl)) + if (variableResolver.TryGetLocalizedControl(symbol.Id.Id, null, out var localizedControl)) { if (CompilerConstants.IntegerNotSet != localizedControl.X) { @@ -169,7 +170,7 @@ namespace WixToolset.Core foreach (var symbol in section.Symbols.OfType()) { - if (this.VariableResolver.TryGetLocalizedControl(symbol.DialogRef, symbol.Control, out var localizedControl)) + if (variableResolver.TryGetLocalizedControl(symbol.DialogRef, symbol.Control, out var localizedControl)) { if (CompilerConstants.IntegerNotSet != localizedControl.X) { @@ -204,24 +205,42 @@ namespace WixToolset.Core } } - private int? PopulateVariableResolver(IResolveContext context) + private IVariableResolver CreateVariableResolver(IResolveContext context, IEnumerable filteredLocalizations) { - var localizations = FilterLocalizations(context); - var codepage = localizations.FirstOrDefault()?.Codepage; + var variableResolver = this.ServiceProvider.GetService(); - foreach (var localization in localizations) + foreach (var localization in filteredLocalizations) { - this.VariableResolver.AddLocalization(localization); + variableResolver.AddLocalization(localization); } // Gather all the wix variables. var wixVariableSymbols = context.IntermediateRepresentation.Sections.SelectMany(s => s.Symbols).OfType(); foreach (var symbol in wixVariableSymbols) { - this.VariableResolver.AddVariable(symbol.SourceLineNumbers, symbol.Id.Id, symbol.Value, symbol.Overridable); + variableResolver.AddVariable(symbol.SourceLineNumbers, symbol.Id.Id, symbol.Value, symbol.Overridable); + } + + return variableResolver; + } + + private bool TryGetCultureInfo(string culture, out CultureInfo cultureInfo) + { + cultureInfo = null; + + if (!String.IsNullOrEmpty(culture)) + { + try + { + cultureInfo = new CultureInfo(culture, useUserOverride: false); + } + catch + { + this.Messaging.Write(""); + } } - return codepage; + return cultureInfo != null; } private static IEnumerable FilterLocalizations(IResolveContext context) diff --git a/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs b/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs new file mode 100644 index 00000000..319b0788 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs @@ -0,0 +1,155 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class LanguageFixture + { + [Fact] + public void CanBuildWithDefaultProductLanguage() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("0", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;0", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("1252", summaryCodepage.Value); + } + } + + [Fact] + public void CanBuildEnuWxl() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("1033", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;1033", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("1252", summaryCodepage.Value); + } + } + + [Fact] + public void CanBuildJpnWxl() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.ja-jp.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("1041", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;1041", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("932", summaryCodepage.Value); + } + } + + [Fact] + public void CanBuildJpnWxlWithEnuSummaryInfo() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "PackageWithEnSummaryInfo.ja-jp.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("1041", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;1041", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("1252", summaryCodepage.Value); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs b/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs index 41c1d773..f73a57d0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs @@ -19,8 +19,8 @@ namespace WixToolsetTest.CoreIntegration [Fact] public void MustCompileBeforeLinking() { - var intermediate1 = new Intermediate("TestIntermediate1", new[] { new IntermediateSection("test1", SectionType.Product, 65001) }, null); - var intermediate2 = new Intermediate("TestIntermediate2", new[] { new IntermediateSection("test2", SectionType.Fragment, 65001) }, null); + var intermediate1 = new Intermediate("TestIntermediate1", new[] { new IntermediateSection("test1", SectionType.Product) }, null); + var intermediate2 = new Intermediate("TestIntermediate2", new[] { new IntermediateSection("test2", SectionType.Fragment) }, null); var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); var listener = new TestMessageListener(); diff --git a/src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs index 2d98a66c..cdba85de 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs @@ -16,7 +16,7 @@ namespace WixToolsetTest.CoreIntegration public void GeneratesCorrectCustomActionIdentifiers() { var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var section = new IntermediateSection("section", SectionType.Fragment, 0); + var section = new IntermediateSection("section", SectionType.Fragment); var parseHelper = serviceProvider.GetService(); parseHelper.CreateCustomActionReference(null, section, "CustomAction32", Platform.X86, CustomActionPlatforms.X86); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl new file mode 100644 index 00000000..f7453566 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl new file mode 100644 index 00000000..ef287da7 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl new file mode 100644 index 00000000..10ebf2c5 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs new file mode 100644 index 00000000..2bbc14f9 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl new file mode 100644 index 00000000..596ee077 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs b/src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs index 28c68e99..15e5d334 100644 --- a/src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs @@ -25,7 +25,7 @@ namespace WixToolsetTest.CoreIntegration { "ProductNameEditionVersion", new BindVariable() { Id = "ProductNameEditionVersion", Value = "!(loc.ProductNameEdition) v1.2.3" } }, }; - var localization = new Localization(0, "x-none", variables, new Dictionary()); + var localization = new Localization(0, null, "x-none", variables, new Dictionary()); variableResolver.AddLocalization(localization); -- cgit v1.2.3-55-g6feb From 860f77f7c9d522074dc7e44cfe11281efd20687f Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 5 Apr 2021 12:55:26 -0700 Subject: Introduce "Subdirectory" which simplifies inline directory syntax Completes wixtoolset/issues#4727 --- .../CreateWindowsInstallerDataFromIRCommand.cs | 58 +++++- src/WixToolset.Core/Compiler.cs | 199 ++++++++++++--------- src/WixToolset.Core/CompilerCore.cs | 17 +- src/WixToolset.Core/Compiler_Package.cs | 116 ++++++++---- .../ExtensibilityServices/ParseHelper.cs | 126 +++---------- src/WixToolset.Core/Linker.cs | 1 - .../DirectoryFixture.cs | 35 ++-- .../ExtensionFixture.cs | 2 +- .../LanguageFixture.cs | 19 ++ .../LinkerFixture.cs | 2 +- .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 12 +- .../TestData/CopyFile/CopyFile.wxs | 4 +- .../TestData/DuplicateDir/DuplicateDir.wxs | 4 +- .../TestData/Language/Package.wxs | 4 +- .../TestData/Media/MultiMedia.wxs | 8 +- .../TestData/PatchNoFileChanges/Package.wxs | 4 +- .../ProductWithComponentGroupRef/Product.wxs | 6 +- .../TestData/SameFileFolders/TestComponents.wxs | 8 +- .../TestData/UsingProvides/Package.wxs | 4 +- 19 files changed, 346 insertions(+), 283 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs index 1bd00900..cee87df0 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs @@ -18,6 +18,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind internal class CreateWindowsInstallerDataFromIRCommand { + private static readonly char[] PathSeparatorChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; + public CreateWindowsInstallerDataFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, int codepage, IEnumerable backendExtensions, IWindowsInstallerBackendHelper backendHelper) { this.Messaging = messaging; @@ -488,18 +490,23 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void AddDirectorySymbol(DirectorySymbol symbol) { - if (String.IsNullOrEmpty(symbol.ShortName) && symbol.Name != null && !symbol.Name.Equals(".") && !symbol.Name.Equals("SourceDir") && !this.BackendHelper.IsValidShortFilename(symbol.Name, false)) + (var name, var parentDir) = this.AddDirectorySubdirectories(symbol); + + var shortName = symbol.ShortName; + var sourceShortname = symbol.SourceShortName; + + if (String.IsNullOrEmpty(shortName) && name != null && name != "." && name != "SourceDir" && !this.BackendHelper.IsValidShortFilename(name, false)) { - symbol.ShortName = this.CreateShortName(symbol.Name, false, "Directory", symbol.ParentDirectoryRef); + shortName = this.CreateShortName(name, false, "Directory", symbol.ParentDirectoryRef); } - if (String.IsNullOrEmpty(symbol.SourceShortName) && !String.IsNullOrEmpty(symbol.SourceName) && !this.BackendHelper.IsValidShortFilename(symbol.SourceName, false)) + if (String.IsNullOrEmpty(sourceShortname) && !String.IsNullOrEmpty(symbol.SourceName) && !this.BackendHelper.IsValidShortFilename(symbol.SourceName, false)) { - symbol.SourceShortName = this.CreateShortName(symbol.SourceName, false, "Directory", symbol.ParentDirectoryRef); + sourceShortname = this.CreateShortName(symbol.SourceName, false, "Directory", symbol.ParentDirectoryRef); } - var sourceName = CreateMsiFilename(symbol.SourceShortName, symbol.SourceName); - var targetName = CreateMsiFilename(symbol.ShortName, symbol.Name); + var sourceName = CreateMsiFilename(sourceShortname, symbol.SourceName); + var targetName = CreateMsiFilename(shortName, name); if (String.IsNullOrEmpty(targetName)) { @@ -510,7 +517,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var row = this.CreateRow(symbol, "Directory"); row[0] = symbol.Id.Id; - row[1] = symbol.ParentDirectoryRef; + row[1] = parentDir; row[2] = defaultDir; if (OutputType.Module == this.Data.Type) @@ -1267,6 +1274,43 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } + private (string, string) AddDirectorySubdirectories(DirectorySymbol symbol) + { + var directory = symbol.Name.Trim(PathSeparatorChars); + var parentDir = symbol.ParentDirectoryRef ?? (symbol.Id.Id == "TARGETDIR" ? null : "TARGETDIR"); + + var start = 0; + var end = directory.IndexOfAny(PathSeparatorChars); + var path = String.Empty; + + while (start <= end) + { + var subdirectoryName = directory.Substring(start, end - start); + + if (!String.IsNullOrEmpty(subdirectoryName)) + { + path = Path.Combine(path, subdirectoryName); + + var id = this.BackendHelper.GenerateIdentifier("d", symbol.ParentDirectoryRef, path); + var shortnameSubdirectory = this.BackendHelper.IsValidShortFilename(subdirectoryName, false) ? null : this.CreateShortName(subdirectoryName, false, "Directory", symbol.ParentDirectoryRef); + + var subdirectoryRow = this.CreateRow(symbol, "Directory"); + subdirectoryRow[0] = id; + subdirectoryRow[1] = parentDir; + subdirectoryRow[2] = CreateMsiFilename(shortnameSubdirectory, subdirectoryName); + + parentDir = id; + } + + start = end + 1; + end = symbol.Name.IndexOfAny(PathSeparatorChars, start); + } + + var name = (start == 0) ? directory : directory.Substring(start); + + return (name, parentDir); + } + private void EnsureRequiredTables() { // check for missing table and add them or display an error as appropriate diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 926a46c5..184c5b3e 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -5,7 +5,6 @@ namespace WixToolset.Core using System; using System.Collections.Generic; using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -2107,6 +2106,7 @@ namespace WixToolset.Core var comPlusBits = CompilerConstants.IntegerNotSet; string condition = null; + string subdirectory = null; var encounteredODBCDataSource = false; var files = 0; var guid = "*"; @@ -2163,16 +2163,16 @@ namespace WixToolset.Core break; case "DisableRegistryReflection": disableRegistryReflection = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - //{ - // bits |= MsiInterop.MsidbComponentAttributesDisableRegistryReflection; - //} break; case "Condition": condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Directory": - directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, directoryId); + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); break; case "DiskId": diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); @@ -2196,14 +2196,12 @@ namespace WixToolset.Core { case "either": location = ComponentLocation.Either; - //bits |= MsiInterop.MsidbComponentAttributesOptional; break; case "local": // this is the default location = ComponentLocation.LocalOnly; break; case "source": location = ComponentLocation.SourceOnly; - //bits |= MsiInterop.MsidbComponentAttributesSourceOnly; break; case "": break; @@ -2217,45 +2215,21 @@ namespace WixToolset.Core break; case "NeverOverwrite": neverOverwrite = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - //{ - // bits |= MsiInterop.MsidbComponentAttributesNeverOverwrite; - //} break; case "Permanent": permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - //{ - // bits |= MsiInterop.MsidbComponentAttributesPermanent; - //} break; case "Shared": shared = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - //{ - // bits |= MsiInterop.MsidbComponentAttributesShared; - //} break; case "SharedDllRefCount": sharedDllRefCount = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - //{ - // bits |= MsiInterop.MsidbComponentAttributesSharedDllRefCount; - //} break; case "Transitive": transitive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - //{ - // bits |= MsiInterop.MsidbComponentAttributesTransitive; - //} break; case "UninstallWhenSuperseded": uninstallWhenSuperseded = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - //{ - // bits |= MsiInterop.MsidbComponentAttributesUninstallOnSupersedence; - //} break; default: this.Core.UnexpectedAttribute(node, attrib); @@ -2275,17 +2249,22 @@ namespace WixToolset.Core id = new Identifier(AccessModifier.Section, componentIdPlaceholder); } - if (null == directoryId) + if (String.IsNullOrEmpty(directoryId)) { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory")); } - if (String.IsNullOrEmpty(guid) && shared /*MsiInterop.MsidbComponentAttributesShared == (bits & MsiInterop.MsidbComponentAttributesShared)*/) + if (!String.IsNullOrEmpty(subdirectory)) + { + directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); + } + + if (String.IsNullOrEmpty(guid) && shared) { this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Shared", "yes", "Guid", "")); } - if (String.IsNullOrEmpty(guid) && permanent /*MsiInterop.MsidbComponentAttributesPermanent == (bits & MsiInterop.MsidbComponentAttributesPermanent)*/) + if (String.IsNullOrEmpty(guid) && permanent) { this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Permanent", "yes", "Guid", "")); } @@ -2587,6 +2566,7 @@ namespace WixToolset.Core var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; string directoryId = null; + string subdirectory = null; string source = null; foreach (var attrib in node.Attributes()) @@ -2599,9 +2579,11 @@ namespace WixToolset.Core id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "Directory": - // If the inline syntax is invalid it returns null. Use a static error identifier so the null - // directory identifier here doesn't trickle down false errors into child elements. - directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null) ?? "ErrorParsingInlineSyntax"; + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); break; case "Source": source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -2623,6 +2605,8 @@ namespace WixToolset.Core id = Identifier.Invalid; } + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + if (!String.IsNullOrEmpty(source) && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { source = String.Concat(source, Path.DirectorySeparatorChar); @@ -2898,18 +2882,24 @@ namespace WixToolset.Core private string ParseCreateFolderElement(XElement node, string componentId, string directoryId, bool win64Component) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string subdirectory = null; + foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { - case "Directory": - directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, directoryId); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; } } else @@ -2918,24 +2908,26 @@ namespace WixToolset.Core } } + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) { switch (child.Name.LocalName) { - case "Shortcut": - this.ParseShortcutElement(child, componentId, node.Name.LocalName, directoryId, YesNoType.No); - break; - case "Permission": - this.ParsePermissionElement(child, directoryId, "CreateFolder"); - break; - case "PermissionEx": - this.ParsePermissionExElement(child, directoryId, "CreateFolder"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; + case "Shortcut": + this.ParseShortcutElement(child, componentId, node.Name.LocalName, directoryId, YesNoType.No); + break; + case "Permission": + this.ParsePermissionElement(child, directoryId, "CreateFolder"); + break; + case "PermissionEx": + this.ParsePermissionExElement(child, directoryId, "CreateFolder"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; } } else @@ -2969,10 +2961,12 @@ namespace WixToolset.Core Identifier id = null; var delete = false; string destinationDirectory = null; + string destinationSubdirectory = null; string destinationName = null; string destinationShortName = null; string destinationProperty = null; string sourceDirectory = null; + string sourceSubdirectory = null; string sourceFolder = null; string sourceName = null; string sourceProperty = null; @@ -2990,16 +2984,20 @@ namespace WixToolset.Core delete = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; case "DestinationDirectory": - destinationDirectory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); + destinationDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, destinationDirectory); + break; + case "DestinationSubdirectory": + destinationSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); break; case "DestinationName": - destinationName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + destinationName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib); break; case "DestinationProperty": destinationProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; case "DestinationShortName": - destinationShortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + destinationShortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib); break; case "FileId": if (null != fileId) @@ -3010,7 +3008,11 @@ namespace WixToolset.Core this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, fileId); break; case "SourceDirectory": - sourceDirectory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); + sourceDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, sourceDirectory); + break; + case "SourceSubdirectory": + sourceSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); break; case "SourceName": sourceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -3044,11 +3046,15 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "SourceDirectory")); } + sourceDirectory = this.HandleSubdirectory(sourceLineNumbers, node, sourceDirectory, sourceSubdirectory, "SourceDirectory", "SourceSubdirectory"); + if (null != destinationDirectory && null != destinationProperty) // DestinationDirectory and DestinationProperty cannot coexist { this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationProperty", "DestinationDirectory")); } + destinationDirectory = this.HandleSubdirectory(sourceLineNumbers, node, destinationDirectory, destinationSubdirectory, "DestinationDirectory", "DestinationSubdirectory"); + if (null == id) { id = this.Core.CreateIdentifier("cf", sourceFolder, sourceDirectory, sourceProperty, destinationDirectory, destinationProperty, destinationName); @@ -3139,6 +3145,7 @@ namespace WixToolset.Core var explicitWin64 = false; string scriptFile = null; + string subdirectory = null; CustomActionSourceType? sourceType = null; CustomActionTargetType? targetType = null; @@ -3194,8 +3201,9 @@ namespace WixToolset.Core { this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileRef", "Property", "Script")); } - source = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); sourceType = CustomActionSourceType.Directory; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, source); break; case "DllEntry": if (null != target) @@ -3355,6 +3363,9 @@ namespace WixToolset.Core case "ScriptSourceFile": scriptFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; case "SuppressModularization": suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; @@ -3399,6 +3410,18 @@ namespace WixToolset.Core win64 = true; } + if (!String.IsNullOrEmpty(subdirectory)) + { + if (sourceType == CustomActionSourceType.Directory) + { + source = this.HandleSubdirectory(sourceLineNumbers, node, source, subdirectory, "Directory", "Subdirectory"); + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Subdirectory", "Directory")); + } + } + // if we have an in-lined Script CustomAction ensure no source or target attributes were provided if (inlineScript) { @@ -4168,7 +4191,6 @@ namespace WixToolset.Core var fileSourceAttribSet = false; XAttribute nameAttribute = null; var name = "."; // default to parent directory. - string inlineSyntax = null; string shortName = null; string sourceName = null; string shortSourceName = null; @@ -4194,7 +4216,7 @@ namespace WixToolset.Core fileSourceAttribSet = true; break; case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); nameAttribute = attrib; break; case "ShortName": @@ -4268,37 +4290,22 @@ namespace WixToolset.Core } } - // Create the directory rows for the inline. - if (nameAttribute != null) + if (null == id) { - var lastSlash = name.LastIndexOf('\\'); - if (lastSlash > 0) - { - inlineSyntax = name; - name = inlineSyntax.Substring(lastSlash + 1); - - parentId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, nameAttribute, parentId, inlineSyntax.Substring(0, lastSlash)); - - if (!this.Core.IsValidLongFilename(name, false, false)) - { - this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, nameAttribute.Name.LocalName, nameAttribute.Value, name)); - } - } + id = this.Core.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); } - - if (null == id) + else if (WindowsInstallerStandard.IsStandardDirectory(id.Id)) { - id = this.Core.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName); - - if (!String.IsNullOrEmpty(inlineSyntax)) + if (String.IsNullOrEmpty(sourceName)) { - this.Core.AddInlineDirectoryId(inlineSyntax, id.Id); + this.Core.Write(CompilerWarnings.DefiningStandardDirectoryDeprecated(sourceLineNumbers, id.Id)); } - } - else if ("TARGETDIR".Equals(id.Id, StringComparison.Ordinal) && !("SourceDir".Equals(name, StringComparison.Ordinal) && shortName == null && shortSourceName == null && sourceName == null)) + + if (id.Id == "TARGETDIR" && name != "SourceDir" && shortName == null && shortSourceName == null && sourceName == null) { this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name)); } + } // Update the file source path appropriately. if (fileSourceAttribSet) @@ -4761,7 +4768,8 @@ namespace WixToolset.Core disallowAdvertise = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); break; case "ConfigurableDirectory": - configurableDirectory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); + configurableDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, configurableDirectory); break; case "Description": description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -8403,5 +8411,22 @@ namespace WixToolset.Core } } } + + private string HandleSubdirectory(SourceLineNumber sourceLineNumbers, XElement element, string directoryId, string subdirectory, string directoryAttributeName, string subdirectoryAttributename) + { + if (!String.IsNullOrEmpty(subdirectory)) + { + if (String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, element.Name.LocalName, subdirectoryAttributename, directoryAttributeName)); + } + else + { + directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); + } + } + + return directoryId; + } } } diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index df532d74..8705cacd 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -386,13 +386,12 @@ namespace WixToolset.Core /// Creates directories using the inline directory syntax. /// /// Source line information. - /// Attribute containing the inline syntax. /// Optional identifier of parent directory. /// Optional inline syntax to override attribute's value. /// Identifier of the leaf directory created. - public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax = null) + public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, string parentId, string inlineSyntax = null) { - return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds); + return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute: null, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds); } /// @@ -784,7 +783,7 @@ namespace WixToolset.Core /// The attribute containing the value to get. /// true if wildcards are allowed in the filename. /// The attribute's short filename value. - public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards) + public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false) { if (null == attribute) { @@ -1021,16 +1020,6 @@ namespace WixToolset.Core } } - /// - /// Adds inline directory syntax generated identifier. - /// - /// Inline directory syntax the identifier was generated. - /// Generated identifier for inline syntax. - internal void AddInlineDirectoryId(string inlineSyntax, string id) - { - this.activeSectionCachedInlinedDirectoryIds.Add(inlineSyntax, id); - } - /// /// Creates a new section and makes it the active section in the core. /// diff --git a/src/WixToolset.Core/Compiler_Package.cs b/src/WixToolset.Core/Compiler_Package.cs index afe02f08..576450f4 100644 --- a/src/WixToolset.Core/Compiler_Package.cs +++ b/src/WixToolset.Core/Compiler_Package.cs @@ -2146,11 +2146,12 @@ namespace WixToolset.Core { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; - string directory = null; + string directoryId = null; + string subdirectory = null; string name = null; bool? onInstall = null; bool? onUninstall = null; - string property = null; + string propertyId = null; string shortName = null; foreach (var attrib in node.Attributes()) @@ -2163,7 +2164,11 @@ namespace WixToolset.Core id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "Directory": - directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory); + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + directoryId = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); break; case "Name": name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, true); @@ -2185,7 +2190,7 @@ namespace WixToolset.Core } break; case "Property": - property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; case "ShortName": shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, true); @@ -2211,15 +2216,23 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); } - if (null != directory && null != property) + if (String.IsNullOrEmpty(propertyId)) + { + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); + } + else if (!String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); + } + else if (!String.IsNullOrEmpty(subdirectory)) { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory)); + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); } if (null == id) { var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; - id = this.Core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); + id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); } this.Core.ParseForExtensionElements(node); @@ -2231,7 +2244,7 @@ namespace WixToolset.Core ComponentRef = componentId, FileName = name, ShortFileName = shortName, - DirPropertyRef = directory ?? property ?? parentDirectory, + DirPropertyRef = directoryId ?? propertyId ?? parentDirectory, OnInstall = onInstall, OnUninstall = onUninstall, }); @@ -2248,10 +2261,11 @@ namespace WixToolset.Core { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; - string directory = null; + string directoryId = null; + string subdirectory = null; bool? onInstall = null; bool? onUninstall = null; - string property = null; + string propertyId = null; foreach (var attrib in node.Attributes()) { @@ -2263,7 +2277,11 @@ namespace WixToolset.Core id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "Directory": - directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory); + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); break; case "On": var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -2282,7 +2300,7 @@ namespace WixToolset.Core } break; case "Property": - property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); @@ -2300,15 +2318,23 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); } - if (null != directory && null != property) + if (String.IsNullOrEmpty(propertyId)) + { + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); + } + else if (!String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); + } + else if (!String.IsNullOrEmpty(subdirectory)) { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory)); + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); } if (null == id) { var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; - id = this.Core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, on.ToString()); + id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId, on.ToString()); } this.Core.ParseForExtensionElements(node); @@ -2318,7 +2344,7 @@ namespace WixToolset.Core this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) { ComponentRef = componentId, - DirPropertyRef = directory ?? property ?? parentDirectory, + DirPropertyRef = directoryId ?? propertyId, OnInstall = onInstall, OnUninstall = onUninstall }); @@ -2335,6 +2361,7 @@ namespace WixToolset.Core { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; + string subdirectory = null; var runFromSource = CompilerConstants.IntegerNotSet; var runLocal = CompilerConstants.IntegerNotSet; @@ -2348,7 +2375,11 @@ namespace WixToolset.Core id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "Directory": - directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, directoryId); + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); break; case "RunFromSource": runFromSource = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); @@ -2367,6 +2398,8 @@ namespace WixToolset.Core } } + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + if (null == id) { id = this.Core.CreateIdentifier("rc", componentId, directoryId); @@ -4001,7 +4034,8 @@ namespace WixToolset.Core string description = null; string descriptionResourceDll = null; int? descriptionResourceId = null; - string directory = null; + string directoryId = null; + string subdirectory = null; string displayResourceDll = null; int? displayResourceId = null; int? hotkey = null; @@ -4011,7 +4045,8 @@ namespace WixToolset.Core string shortName = null; ShortcutShowType? show = null; string target = null; - string workingDirectory = null; + string workingDirectoryId = null; + string workingSubdirectory = null; foreach (var attrib in node.Attributes()) { @@ -4038,7 +4073,11 @@ namespace WixToolset.Core descriptionResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); break; case "Directory": - directory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); break; case "DisplayResourceDll": displayResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -4086,7 +4125,11 @@ namespace WixToolset.Core target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "WorkingDirectory": - workingDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + workingDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, workingDirectoryId); + break; + case "WorkingSubdirectory": + workingSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); break; default: this.Core.UnexpectedAttribute(node, attrib); @@ -4104,11 +4147,11 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes")); } - if (null == directory) + if (null == directoryId) { if ("Component" == parentElementLocalName) { - directory = defaultTarget; + directoryId = defaultTarget; } else { @@ -4116,6 +4159,8 @@ namespace WixToolset.Core } } + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + if (null != descriptionResourceDll) { if (!descriptionResourceId.HasValue) @@ -4151,6 +4196,8 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); } + workingDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, workingDirectoryId, workingSubdirectory, "WorkingDirectory", "WorkingSubdirectory"); + if ("Component" != parentElementLocalName && null != target) { this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName)); @@ -4158,7 +4205,7 @@ namespace WixToolset.Core if (null == id) { - id = this.Core.CreateIdentifier("sct", directory, LowercaseOrNull(name)); + id = this.Core.CreateIdentifier("sct", directoryId, LowercaseOrNull(name)); } foreach (var child in node.Elements()) @@ -4209,7 +4256,7 @@ namespace WixToolset.Core this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) { - DirectoryRef = directory, + DirectoryRef = directoryId, Name = name, ShortName = shortName, ComponentRef = componentId, @@ -4220,7 +4267,7 @@ namespace WixToolset.Core IconRef = icon, IconIndex = iconIndex, Show = show, - WorkingDirectory = workingDirectory, + WorkingDirectory = workingDirectoryId, DisplayResourceDll = displayResourceDll, DisplayResourceId = displayResourceId, DescriptionResourceDll = descriptionResourceDll, @@ -4309,7 +4356,8 @@ namespace WixToolset.Core var cost = CompilerConstants.IntegerNotSet; string description = null; var flags = 0; - string helpDirectory = null; + string helpDirectoryId = null; + string helpSubdirectory = null; var language = CompilerConstants.IntegerNotSet; var majorVersion = CompilerConstants.IntegerNotSet; var minorVersion = CompilerConstants.IntegerNotSet; @@ -4346,7 +4394,11 @@ namespace WixToolset.Core } break; case "HelpDirectory": - helpDirectory = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null); + helpDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, helpDirectoryId); + break; + case "HelpSubdirectory": + helpSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); break; case "Hidden": if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) @@ -4394,6 +4446,8 @@ namespace WixToolset.Core language = CompilerConstants.IllegalInteger; } + helpDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, helpDirectoryId, helpSubdirectory, "HelpDirectory", "HelpSubdirectory"); + // build up the typelib version string for the registry if the major or minor version was specified string registryVersion = null; if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) @@ -4488,7 +4542,7 @@ namespace WixToolset.Core Language = language, ComponentRef = componentId, Description = description, - DirectoryRef = helpDirectory, + DirectoryRef = helpDirectoryId, FeatureRef = Guid.Empty.ToString("B") }); @@ -4534,10 +4588,10 @@ namespace WixToolset.Core // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags] this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId); - if (null != helpDirectory) + if (null != helpDirectoryId) { // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory] - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectory, "]"), componentId); + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectoryId, "]"), componentId); } } } diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs index a9a0fa9d..4b7b5ca4 100644 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs @@ -18,8 +18,6 @@ namespace WixToolset.Core.ExtensibilityServices internal class ParseHelper : IParseHelper { - private static readonly char[] InlineDirectorySeparators = new char[] { ':', '\\', '/' }; - public ParseHelper(IServiceProvider serviceProvider) { this.ServiceProvider = serviceProvider; @@ -56,12 +54,9 @@ namespace WixToolset.Core.ExtensibilityServices public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) { - // For anonymous directories, create the identifier. If this identifier already exists in the - // active section, bail so we don't add duplicate anonymous directory symbols (which are legal - // but bloat the intermediate and ultimately make the linker do "busy work"). if (null == id) { - id = this.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName); + id = this.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); } var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) @@ -78,28 +73,37 @@ namespace WixToolset.Core.ExtensibilityServices public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax, IDictionary sectionCachedInlinedDirectoryIds) { - if (String.IsNullOrEmpty(inlineSyntax)) + if (String.IsNullOrEmpty(parentId)) { - inlineSyntax = attribute.Value; + throw new ArgumentNullException(nameof(parentId)); } - // If no separator is found, the string is a simple reference. - var separatorFound = inlineSyntax.IndexOfAny(InlineDirectorySeparators); - if (separatorFound == -1) + if (String.IsNullOrEmpty(inlineSyntax)) { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, inlineSyntax); - return inlineSyntax; + inlineSyntax = this.GetAttributeLongFilename(sourceLineNumbers, attribute, false, true); } - // If a parent id was provided and the inline syntax does not start with a directory reference, prepend the parent id. - if (!String.IsNullOrEmpty(parentId) && inlineSyntax[separatorFound] != ':') + if (String.IsNullOrEmpty(inlineSyntax)) { - inlineSyntax = String.Concat(parentId, ":", inlineSyntax); + return parentId; } - inlineSyntax = inlineSyntax.TrimEnd('\\', '/'); + inlineSyntax = inlineSyntax.Trim('\\', '/'); + + var cacheKey = String.Concat(parentId, ":", inlineSyntax); + + if (!sectionCachedInlinedDirectoryIds.TryGetValue(cacheKey, out var id)) + { + var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, id: null, parentId, inlineSyntax); + + id = identifier.Id; + } + else + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id); + } - return this.ParseInlineSyntax(section, sourceLineNumbers, attribute, inlineSyntax, sectionCachedInlinedDirectoryIds); + return id; //this.ParseInlineSyntax(section, sourceLineNumbers, attribute, inlineSyntax, sectionCachedInlinedDirectoryIds); } public string CreateGuid(Guid namespaceGuid, string value) @@ -444,7 +448,7 @@ namespace WixToolset.Core.ExtensibilityServices var value = this.GetAttributeValue(sourceLineNumbers, attribute); - if (0 < value.Length) + if (!String.IsNullOrEmpty(value)) { if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value)) { @@ -840,90 +844,6 @@ namespace WixToolset.Core.ExtensibilityServices this.Creator = this.ServiceProvider.GetService(); } - private string ParseInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string inlineSyntax, IDictionary sectionCachedInlinedDirectoryIds) - { - if (!sectionCachedInlinedDirectoryIds.TryGetValue(inlineSyntax, out var id)) - { - string parentId; - int nameIndex; - - var separatorIndex = inlineSyntax.LastIndexOfAny(InlineDirectorySeparators); - if (separatorIndex == -1) - { - nameIndex = 0; - parentId = "TARGETDIR"; - } - else if (inlineSyntax[separatorIndex] == '\\' || inlineSyntax[separatorIndex] == '/') - { - nameIndex = separatorIndex + 1; - - if (separatorIndex == 0) - { - parentId = "TARGETDIR"; - } - else if (inlineSyntax[separatorIndex - 1] == ':') - { - parentId = this.ParseParentReference(section, sourceLineNumbers, attribute, inlineSyntax, separatorIndex - 1); - } - else - { - var parentInlineDirectory = inlineSyntax.Substring(0, separatorIndex); - parentId = this.ParseInlineSyntax(section, sourceLineNumbers, attribute, parentInlineDirectory.TrimEnd('\\', '/'), sectionCachedInlinedDirectoryIds); - } - } - else - { - nameIndex = separatorIndex + 1; - parentId = this.ParseParentReference(section, sourceLineNumbers, attribute, inlineSyntax, separatorIndex); - } - - if (nameIndex == inlineSyntax.Length) - { - id = parentId; - } - else - { - var name = nameIndex != -1 ? inlineSyntax.Substring(nameIndex) : null; - - if (!this.IsValidLongFilename(name, false, false)) - { - this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, name)); - return null; - } - - var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, null, parentId, name); - - id = identifier.Id; - } - - sectionCachedInlinedDirectoryIds.Add(inlineSyntax, id); - } - - return id; - } - - private string ParseParentReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string reference, int colonIndex) - { - if (colonIndex == 0) - { - this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, String.Empty)); - return null; - } - else - { - var parentId = reference.Substring(0, colonIndex); - - if (!Common.IsIdentifier(parentId)) - { - this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, attribute.Value, parentId)); - return null; - } - - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, parentId); - return parentId; - } - } - private static bool TryFindExtension(IEnumerable extensions, XNamespace ns, out ICompilerExtension extension) { extension = null; diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index 2c8508a8..bf7130db 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs @@ -587,7 +587,6 @@ namespace WixToolset.Core { var removeSymbols = new List(); - // Count down because we'll sometimes remove items from the list. foreach (var symbol in section.Symbols) { // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature, diff --git a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs index 2d6e4802..56f8ba82 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs @@ -7,6 +7,7 @@ namespace WixToolsetTest.CoreIntegration using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; using Xunit; public class DirectoryFixture @@ -40,11 +41,11 @@ namespace WixToolsetTest.CoreIntegration var dirSymbols = section.Symbols.OfType().ToList(); Assert.Equal(new[] { - "INSTALLFOLDER", - "ProgramFiles6432Folder", - "ProgramFilesFolder", - "TARGETDIR" - }, dirSymbols.Select(d => d.Id.Id).ToArray()); + "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", + "ProgramFiles6432Folder:ProgramFilesFolder:.", + "ProgramFilesFolder:TARGETDIR:PFiles", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); } } @@ -78,11 +79,11 @@ namespace WixToolsetTest.CoreIntegration var dirSymbols = section.Symbols.OfType().ToList(); Assert.Equal(new[] { - "INSTALLFOLDER", - "ProgramFiles6432Folder", - "ProgramFiles64Folder", - "TARGETDIR" - }, dirSymbols.Select(d => d.Id.Id).ToArray()); + "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", + "ProgramFiles6432Folder:ProgramFiles64Folder:.", + "ProgramFiles64Folder:TARGETDIR:PFiles64", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); } } @@ -116,12 +117,14 @@ namespace WixToolsetTest.CoreIntegration var dirSymbols = section.Symbols.OfType().ToList(); Assert.Equal(new[] { - "dirZsSsu81KcG46xXTwc4mTSZO5Zx4", - "INSTALLFOLDER", - "ProgramFiles6432Folder", - "ProgramFiles64Folder", - "TARGETDIR" - }, dirSymbols.Select(d => d.Id.Id).ToArray()); + "dZsSsu81KcG46xXTwc4mTSZO5Zx4:INSTALLFOLDER:dupe", + "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", + "ProgramFiles6432Folder:ProgramFiles64Folder:.", + "ProgramFiles64Folder:TARGETDIR:PFiles64", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); + } + } } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs index ff0c3258..089658e6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs @@ -53,7 +53,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi"))); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\example.txt"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\example.txt"))); var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); var section = intermediate.Sections.Single(); diff --git a/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs b/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs index 319b0788..610e44b8 100644 --- a/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs @@ -8,6 +8,7 @@ namespace WixToolsetTest.CoreIntegration using WixToolset.Core.TestPackage; using WixToolset.Data; using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; using Xunit; public class LanguageFixture @@ -36,6 +37,14 @@ namespace WixToolsetTest.CoreIntegration var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); var section = intermediate.Sections.Single(); + var directorySymbols = section.Symbols.OfType(); + Assert.Equal(new[] + { + "INSTALLFOLDER:Example Corporation\\MsiPackage", + "ProgramFilesFolder:PFiles", + "TARGETDIR:SourceDir" + }, directorySymbols.OrderBy(s => s.Id.Id).Select(s => s.Id.Id + ":" + s.Name).ToArray()); + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); Assert.Equal("0", propertySymbol.Value); @@ -44,6 +53,16 @@ namespace WixToolsetTest.CoreIntegration var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); Assert.Equal("1252", summaryCodepage.Value); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + Assert.Equal(new[] + { + "d4EceYatXTyy8HXPt5B6DT9Rj.wE:u7-b4gch|Example Corporation", + "INSTALLFOLDER:oekcr5lq|MsiPackage", + "ProgramFilesFolder:PFiles", + "TARGETDIR:SourceDir" + }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(2)).ToArray()); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs b/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs index f73a57d0..676d7d87 100644 --- a/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs @@ -68,7 +68,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); var section = intermediate.Sections.Single(); diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs index a8ea0a18..3bdfa0ef 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -40,7 +40,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); @@ -240,7 +240,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); var section = intermediate.Sections.Single(); @@ -351,7 +351,7 @@ namespace WixToolsetTest.CoreIntegration var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); Assert.True(File.Exists(pdbPath)); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\test.txt"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\test.txt"))); var intermediate = Intermediate.Load(pdbPath); var section = intermediate.Sections.Single(); @@ -527,7 +527,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); var section = intermediate.Sections.Single(); @@ -563,7 +563,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\AssemblyMsiPackage\candle.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\AssemblyMsiPackage\candle.exe"))); var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); var section = intermediate.Sections.Single(); @@ -721,7 +721,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\Foo.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\Foo.exe"))); var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); var section = intermediate.Sections.Single(); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs index 66208806..90d66cc3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs @@ -10,6 +10,8 @@ - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs index ffee969d..a58b68c8 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs @@ -8,7 +8,7 @@ - + @@ -16,7 +16,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs index 2bbc14f9..db15796d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs @@ -11,6 +11,8 @@ - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs index 8a555bda..e7492db4 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs @@ -8,19 +8,19 @@ - + - + - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs index 9d530376..5ad21a75 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs @@ -4,7 +4,9 @@ - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs index f297c9e9..5a4e2c07 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -1,4 +1,4 @@ - + @@ -9,6 +9,8 @@ - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs index 765e6778..bbad63e6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs @@ -1,14 +1,14 @@ - - + + - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs index 9ddcdc90..70fdbb46 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs @@ -9,6 +9,8 @@ - + + + -- cgit v1.2.3-55-g6feb From 86e59fdbc94ae661ca682f04cddb60d7830ae8a8 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 6 Apr 2021 09:34:57 -0700 Subject: Introduce StandardDirectory for referencing standard directories Completes wixtoolset/issues#6416 --- .../Decompile/Decompiler.cs | 70 ++++++++++++--------- .../Decompile/Names.cs | 1 + src/WixToolset.Core/Compiler.cs | 72 ++++++++++++++++++++++ src/WixToolset.Core/CompilerWarnings.cs | 12 ++++ src/WixToolset.Core/Compiler_Module.cs | 3 + src/WixToolset.Core/Compiler_Package.cs | 3 + .../DirectoryFixture.cs | 46 ++++++++++++++ .../DecompiledNestedDirSearchUnderRegSearch.wxs | 14 ++--- .../TestData/Assembly/Package.wxs | 10 ++- .../TestData/Class/DecompiledOldClassTableDef.wxs | 26 ++++---- .../TestData/ComplexExampleExtension/Package.wxs | 10 ++- .../TestData/Components/Package.wxs | 10 ++- .../TestData/CustomTable/CustomTable-Expected.wxs | 17 +++-- .../TestData/DecompileNullComponent/Expected.wxs | 16 +++-- .../DecompileSingleFileCompressed/Expected.wxs | 16 +++-- .../DecompileSingleFileCompressed64/Expected.wxs | 16 +++-- .../TestData/Directory/Nested.wxs | 11 ++++ .../TestData/ErrorsInUI/Package.wxs | 10 ++- .../TestData/ExampleExtension/Package.wxs | 10 ++- .../TestData/ForEach/Package.wxs | 10 ++- .../TestData/IncludePath/Package.wxs | 8 +-- .../TestData/InstanceTransform/Package.wxs | 10 ++- .../TestData/Language/Package.wxs | 6 +- .../TestData/ManualUpgrade/Package.wxs | 10 ++- .../TestData/MultiFileCompressed/Package.wxs | 10 ++- .../TestData/OverridableActions/Package.wxs | 10 ++- .../TestData/PatchNoFileChanges/Package.wxs | 4 +- .../TestData/PatchNonSpecific/PackageA/Package.wxs | 8 +-- .../TestData/PatchSingle/Package.wxs | 10 ++- .../TestData/ProductTag/PackageWithTag.wxs | 8 +-- .../ProductWithComponentGroupRef/Product.wxs | 4 +- .../TestData/ProgId/Package.wxs | 10 ++- .../TestData/PublishComponent/Package.wxs | 10 ++- .../SequenceTables/DecompiledSequenceTables.wxs | 14 ++--- .../TestData/SetProperty/Package.wxs | 10 ++- .../TestData/Shortcut/DecompiledShortcuts.wxs | 20 +++--- .../TestData/SimpleMerge/Package.wxs | 14 ++--- .../TestData/SingleFile/Package.wxs | 10 ++- .../TestData/SingleFileCompressed/Package.wxs | 10 ++- .../TestData/UsingProvides/Package.wxs | 4 +- .../TestData/WixVariableOverride/Package.wxs | 10 ++- .../TestData/Wixipl/Package.wxs | 10 ++- 42 files changed, 345 insertions(+), 248 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index 8e477dd1..eb23f497 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -4289,53 +4289,57 @@ namespace WixToolset.Core.WindowsInstaller.Decompile foreach (var row in table.Rows) { var id = row.FieldAsString(0); - var xDirectory = new XElement(Names.DirectoryElement, + var elementName = WindowsInstallerStandard.IsStandardDirectory(id) ? Names.StandardDirectoryElement : Names.DirectoryElement; + var xDirectory = new XElement(elementName, new XAttribute("Id", id)); - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); - - if (String.Equals(id, "TARGETDIR", StringComparison.Ordinal) && !String.Equals(names[0], "SourceDir", StringComparison.Ordinal)) - { - this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir()); - xDirectory.SetAttributeValue("Name", "SourceDir"); - } - else + if (!WindowsInstallerStandard.IsStandardDirectory(id)) { - if (null != names[0] && "." != names[0]) + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); + + if (id == "TARGETDIR" && names[0] != "SourceDir") { - if (null != names[1]) + this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir()); + xDirectory.SetAttributeValue("Name", "SourceDir"); + } + else + { + if (null != names[0] && "." != names[0]) { - xDirectory.SetAttributeValue("ShortName", names[0]); + if (null != names[1]) + { + xDirectory.SetAttributeValue("ShortName", names[0]); + } + else + { + xDirectory.SetAttributeValue("Name", names[0]); + } } - else + + if (null != names[1]) { - xDirectory.SetAttributeValue("Name", names[0]); + xDirectory.SetAttributeValue("Name", names[1]); } } - if (null != names[1]) + if (null != names[2]) { - xDirectory.SetAttributeValue("Name", names[1]); + if (null != names[3]) + { + xDirectory.SetAttributeValue("ShortSourceName", names[2]); + } + else + { + xDirectory.SetAttributeValue("SourceName", names[2]); + } } - } - if (null != names[2]) - { if (null != names[3]) { - xDirectory.SetAttributeValue("ShortSourceName", names[2]); - } - else - { - xDirectory.SetAttributeValue("SourceName", names[2]); + xDirectory.SetAttributeValue("SourceName", names[3]); } } - if (null != names[3]) - { - xDirectory.SetAttributeValue("SourceName", names[3]); - } - this.IndexElement(row, xDirectory); } @@ -4344,7 +4348,13 @@ namespace WixToolset.Core.WindowsInstaller.Decompile { var xDirectory = this.GetIndexedElement(row); - if (row.IsColumnNull(1)) + var id = row.FieldAsString(0); + + if (id == "TARGETDIR") + { + // Skip TARGETDIR. + } + else if (row.IsColumnNull(1) || WindowsInstallerStandard.IsStandardDirectory(id)) { this.RootElement.Add(xDirectory); } diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs index 82258c57..db65bbf7 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs @@ -69,6 +69,7 @@ namespace WixToolset.Core.WindowsInstaller.Decompile public static readonly XName LevelElement = WxsNamespace + "Level"; public static readonly XName DialogElement = WxsNamespace + "Dialog"; + public static readonly XName StandardDirectoryElement = WxsNamespace + "StandardDirectory"; public static readonly XName DirectoryElement = WxsNamespace + "Directory"; public static readonly XName DirectorySearchElement = WxsNamespace + "DirectorySearch"; public static readonly XName CopyFileElement = WxsNamespace + "CopyFile"; diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 184c5b3e..ca9385f6 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -4427,6 +4427,10 @@ namespace WixToolset.Core { this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); } + else if (WindowsInstallerStandard.IsStandardDirectory(id)) + { + this.Core.Write(CompilerWarnings.DirectoryRefStandardDirectoryDeprecated(sourceLineNumbers, id)); + } if (!String.IsNullOrEmpty(fileSource) && !fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { @@ -6324,6 +6328,9 @@ namespace WixToolset.Core string parentName = null; this.ParseSFPCatalogElement(child, ref parentName); break; + case "StandardDirectory": + this.ParseStandardDirectoryElement(child); + break; case "UI": this.ParseUIElement(child); break; @@ -7558,6 +7565,71 @@ namespace WixToolset.Core } } + /// + /// Parses a standard directory element. + /// + /// Element to parse. + private void ParseStandardDirectoryElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(id)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (!WindowsInstallerStandard.IsStandardDirectory(id)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Id", id, String.Join(", \"", WindowsInstallerStandard.StandardDirectories().Select(d => d.Id.Id)))); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId: CompilerConstants.IntegerNotSet, id, srcPath: String.Empty); + break; + case "Directory": + this.ParseDirectoryElement(child, id, diskId: CompilerConstants.IntegerNotSet, fileSource: String.Empty); + break; + case "Merge": + this.ParseMergeElement(child, id, diskId: CompilerConstants.IntegerNotSet); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + /// /// Parses a configuration data element. /// diff --git a/src/WixToolset.Core/CompilerWarnings.cs b/src/WixToolset.Core/CompilerWarnings.cs index 3b9666dd..eb838ae2 100644 --- a/src/WixToolset.Core/CompilerWarnings.cs +++ b/src/WixToolset.Core/CompilerWarnings.cs @@ -6,6 +6,16 @@ namespace WixToolset.Core internal static class CompilerWarnings { + public static Message DirectoryRefStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) + { + return Message(sourceLineNumbers, Ids.DirectoryRefStandardDirectoryDeprecated, "Using DirectoryRef to reference the standard directory '{0}' is deprecated. Use the StandardDirectory element instead.", directoryId); + } + + public static Message DefiningStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) + { + return Message(sourceLineNumbers, Ids.DefiningStandardDirectoryDeprecated, "It is no longer necessary to define the standard directory '{0}'. Use the StandardDirectory element instead.", directoryId); + } + public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers) { return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified in an MSI package. The ProductVersion will be used by default."); @@ -48,6 +58,8 @@ namespace WixToolset.Core PropertyRemoved = 5433, DiscouragedVersionAttribute = 5434, Win64Component = 5435, + DirectoryRefStandardDirectoryDeprecated = 5436, + DefiningStandardDirectoryDeprecated = 5437, } } } diff --git a/src/WixToolset.Core/Compiler_Module.cs b/src/WixToolset.Core/Compiler_Module.cs index 59fe9164..3986c8da 100644 --- a/src/WixToolset.Core/Compiler_Module.cs +++ b/src/WixToolset.Core/Compiler_Module.cs @@ -203,6 +203,9 @@ namespace WixToolset.Core string parentName = null; this.ParseSFPCatalogElement(child, ref parentName); break; + case "StandardDirectory": + this.ParseStandardDirectoryElement(child); + break; case "Substitution": this.ParseSubstitutionElement(child); break; diff --git a/src/WixToolset.Core/Compiler_Package.cs b/src/WixToolset.Core/Compiler_Package.cs index 576450f4..f9d77873 100644 --- a/src/WixToolset.Core/Compiler_Package.cs +++ b/src/WixToolset.Core/Compiler_Package.cs @@ -316,6 +316,9 @@ namespace WixToolset.Core case "SoftwareTag": this.ParsePackageTagElement(child); break; + case "StandardDirectory": + this.ParseStandardDirectoryElement(child); + break; case "SummaryInformation": this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); break; diff --git a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs index 56f8ba82..fe5bb531 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs @@ -125,6 +125,52 @@ namespace WixToolsetTest.CoreIntegration }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); } } + + [Fact] + public void CanGetWithMultiNestedSubdirectory() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "Directory", "Nested.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "BinFolder:ProgramFilesFolder:Example Corporation\\Test Product\\bin", + "ProgramFilesFolder:TARGETDIR:PFiles", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + Assert.Equal(new[] + { + "d4EceYatXTyy8HXPt5B6DT9Rj.wE:ProgramFilesFolder:u7-b4gch|Example Corporation", + "dSJ1pgiASlW7kJTu0wqsGBklJsS0:d4EceYatXTyy8HXPt5B6DT9Rj.wE:vjj-gxay|Test Product", + "BinFolder:dSJ1pgiASlW7kJTu0wqsGBklJsS0:bin", + "ProgramFilesFolder:TARGETDIR:PFiles", + "TARGETDIR::SourceDir" + }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).ToArray()); } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs index b67abbde..6b9fe013 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs @@ -1,14 +1,12 @@ - - - - - - - + + + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs index 50282522..c345305d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs @@ -1,4 +1,4 @@ - + @@ -10,10 +10,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs index 43af2ffe..514f9243 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs @@ -1,20 +1,18 @@ - - - - - - - - - - - - - + + + + + + + + + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs index 23923426..db07af2c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs @@ -1,4 +1,4 @@ - + @@ -15,10 +15,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs index 0607c718..d7b5bdc0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs @@ -1,4 +1,4 @@ - + @@ -10,10 +10,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs index 935cc8b3..d7d86008 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs @@ -12,17 +12,14 @@ - - - - - - - - - + + + + + - + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs index 050adbd5..71553e2a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs @@ -1,18 +1,16 @@ - - - - - - - + + + + + - + - \ No newline at end of file + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs index e1fb426e..246bcafc 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs @@ -1,18 +1,16 @@ - - - - - - - + + + + + - + - \ No newline at end of file + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs index fed69a1e..81915759 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs @@ -1,18 +1,16 @@ - - - - - - - + + + + + - + - \ No newline at end of file + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs new file mode 100644 index 00000000..cc87b49f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs index 75707f3d..287085e8 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs @@ -1,4 +1,4 @@ - + @@ -13,10 +13,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs index b3fd3672..5c84f33e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs @@ -1,4 +1,4 @@ - + @@ -14,10 +14,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs index 91ac6537..8fff563e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs @@ -1,4 +1,4 @@ - + @@ -12,10 +12,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs index 48a38e85..0bd80c50 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs @@ -11,10 +11,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs index befab53e..7826d673 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs @@ -1,4 +1,4 @@ - + @@ -16,10 +16,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs index db15796d..13c79e90 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs @@ -11,8 +11,8 @@ - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs index 090c7724..4fd3493a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs @@ -1,4 +1,4 @@ - + @@ -17,10 +17,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs index bc7450e3..2b1a1a0f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs @@ -1,4 +1,4 @@ - + @@ -19,10 +19,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs index 6d9e854d..0bf0e963 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs @@ -1,4 +1,4 @@ - + @@ -41,10 +41,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs index 5ad21a75..dab959d5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs @@ -4,9 +4,9 @@ - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs index 2d5fbc6d..62a89af3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs @@ -15,11 +15,9 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs index 72424986..e3845382 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs @@ -1,15 +1,13 @@ - + - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs index 17543c1a..5bf78a9d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs @@ -11,10 +11,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs index 5a4e2c07..b71627a2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -9,8 +9,8 @@ - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs index 0d2d5032..d3b31db5 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs @@ -1,4 +1,4 @@ - + @@ -10,10 +10,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs index 6a95e9da..8f4f661d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs @@ -1,4 +1,4 @@ - + @@ -13,11 +13,9 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs index 6924f37a..d5379e7b 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs @@ -1,15 +1,13 @@ - - - - - - - + + + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs index c8289464..d3f8accf 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs @@ -1,4 +1,4 @@ - + @@ -12,10 +12,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs index ab57b0cd..da1e4f38 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs @@ -1,17 +1,15 @@ - - - - - - - - - - + + + + + + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs index a858b351..3c999812 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs @@ -1,4 +1,4 @@ - + @@ -10,13 +10,11 @@ - - - - - - + + + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs index 0607c718..d7b5bdc0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs @@ -1,4 +1,4 @@ - + @@ -10,10 +10,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs index 852d1aed..baa0c6b1 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs @@ -1,4 +1,4 @@ - + @@ -18,10 +18,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs index 70fdbb46..59839f30 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs @@ -9,8 +9,8 @@ - + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs index e1cf7394..f8203a07 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs @@ -1,4 +1,4 @@ - + @@ -12,10 +12,8 @@ - - - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs index d9714e7a..7e6eee9f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs @@ -1,4 +1,4 @@ - + @@ -12,10 +12,8 @@ - - - - - + + + -- cgit v1.2.3-55-g6feb From f3a228eaf7d40bcd46b64c3d49aa23df23e79aec Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 6 Apr 2021 15:56:45 -0700 Subject: Discards source directory name when identical to target name Fixes wixtoolset/issues#6418 --- .../CreateWindowsInstallerDataFromIRCommand.cs | 2 +- .../DirectoryFixture.cs | 47 ++++++++++++++++++++++ .../Directory/DuplicateTargetSourceName.wxs | 11 +++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs index cee87df0..d34ca3fe 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs @@ -513,7 +513,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind targetName = "."; } - var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName; + var defaultDir = String.IsNullOrEmpty(sourceName) || sourceName == targetName ? targetName : targetName + ":" + sourceName; var row = this.CreateRow(symbol, "Directory"); row[0] = symbol.Id.Id; diff --git a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs index fe5bb531..e0b85509 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs @@ -2,6 +2,7 @@ namespace WixToolsetTest.CoreIntegration { + using System; using System.IO; using System.Linq; using WixBuildTools.TestSupport; @@ -173,5 +174,51 @@ namespace WixToolsetTest.CoreIntegration }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).ToArray()); } } + + [Fact] + public void CanGetDuplicateTargetSourceName() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "Directory", "DuplicateTargetSourceName.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "BinFolder\tProgramFilesFolder\tbin", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + Assert.Equal(new[] + { + "BinFolder\tProgramFilesFolder\tbin", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs new file mode 100644 index 00000000..6e9a4495 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 999621b156ae6be4c06205e3e992b2a76dce7926 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 8 Apr 2021 10:32:40 -0700 Subject: Re-enable MSI usage of only a "." in Directory/@Name --- src/WixToolset.Core/Compiler.cs | 9 ++++- .../DirectoryFixture.cs | 47 ++++++++++++++++++++++ .../TestData/Directory/DefaultName.wxs | 13 ++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index ca9385f6..c5f3a763 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -4216,7 +4216,14 @@ namespace WixToolset.Core fileSourceAttribSet = true; break; case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + if ("." == attrib.Value) + { + name = attrib.Value; + } + else + { + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + } nameAttribute = attrib; break; case "ShortName": diff --git a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs index e0b85509..a61bdff3 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs @@ -88,6 +88,53 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void CanGetDefaultName() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Directory", "DefaultName.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + WixAssert.CompareLineByLine(new[] + { + "BinFolder\tCompanyFolder\t.", + "CompanyFolder\tProgramFilesFolder\tExample Corporation", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + WixAssert.CompareLineByLine(new[] + { + "BinFolder\tCompanyFolder\t.", + "CompanyFolder\tProgramFilesFolder\tu7-b4gch|Example Corporation", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); + } + } + [Fact] public void CanGetDuplicateDir() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs new file mode 100644 index 00000000..3e7887c4 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From abfcbadf4e9c47ed813416fa4754e5a0d20958ff Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 11 Apr 2021 14:49:33 -0500 Subject: Auto-generate ProductCode for ProductWithComponentGroupRef if undefined --- src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs | 1 + .../TestData/ProductWithComponentGroupRef/Product.wxs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs index f9cd2c70..ee93b03a 100644 --- a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs @@ -205,6 +205,7 @@ namespace WixToolsetTest.CoreIntegration var result = WixRunner.Execute(new[] { "build", + "-d", "ProductCode=83f9c623-26fe-42ab-951e-170022117f54", Path.Combine(folder, "CustomTable", "CustomTable.wxs"), Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs index b71627a2..433be7f0 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -1,5 +1,8 @@ - + + + + -- cgit v1.2.3-55-g6feb From 3441afb46c4dc056493ab84f9b27434c4185d713 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 10 Apr 2021 15:04:04 -0700 Subject: Improve implicit Component/@Id generation and duplicate GUID errors --- .../Bind/BindDatabaseCommand.cs | 9 +- .../Bind/CalculateComponentGuids.cs | 181 -------------- .../Bind/FinalizeComponentGuids.cs | 262 +++++++++++++++++++++ .../Bind/ValidateComponentGuidsCommand.cs | 63 ----- src/WixToolset.Core/Compiler.cs | 23 +- .../ComponentFixture.cs | 45 ++++ .../TestData/Component/GuidCollision.wxs | 14 ++ 7 files changed, 336 insertions(+), 261 deletions(-) delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ValidateComponentGuidsCommand.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 06b51ba1..9f36cd78 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -354,14 +354,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - // Set generated component guids. + // Set generated component guids and validate all guids. { - var command = new CalculateComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); - command.Execute(); - } - - { - var command = new ValidateComponentGuidsCommand(this.Messaging, section); + var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); command.Execute(); } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs deleted file mode 100644 index 5cb297e5..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs +++ /dev/null @@ -1,181 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Set the guids for components with generatable guids. - /// - internal class CalculateComponentGuids - { - internal CalculateComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform) - { - this.Messaging = messaging; - this.BackendHelper = helper; - this.PathResolver = pathResolver; - this.Section = section; - this.Platform = platform; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private IPathResolver PathResolver { get; } - - private IntermediateSection Section { get; } - - private Platform Platform { get; } - - public void Execute() - { - Dictionary registryKeyRows = null; - Dictionary targetPathsByDirectoryId = null; - Dictionary componentIdGenSeeds = null; - Dictionary> filesByComponentId = null; - - // Find components with generatable guids. - foreach (var componentSymbol in this.Section.Symbols.OfType()) - { - // Skip components that do not specify generate guid. - if (componentSymbol.ComponentId != "*") - { - continue; - } - - if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType) - { - this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers)); - continue; - } - - if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType) - { - if (registryKeyRows is null) - { - registryKeyRows = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - } - - if (registryKeyRows.TryGetValue(componentSymbol.KeyPath, out var foundRow)) - { - var bitness = componentSymbol.Win64 ? "64" : String.Empty; - var regkey = String.Concat(bitness, foundRow.Root, "\\", foundRow.Key, "\\", foundRow.Name); - componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); - } - } - else // must be a File KeyPath. - { - // If the directory table hasn't been loaded into an indexed hash - // of directory ids to target names do that now. - if (targetPathsByDirectoryId is null) - { - var directories = this.Section.Symbols.OfType().ToList(); - - targetPathsByDirectoryId = new Dictionary(directories.Count); - - // Get the target paths for all directories. - foreach (var directory in directories) - { - // If the directory Id already exists, we will skip it here since - // checking for duplicate primary keys is done later when importing tables - // into database - if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) - { - continue; - } - - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); - targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); - } - } - - // If the component id generation seeds have not been indexed - // from the Directory symbols do that now. - if (componentIdGenSeeds is null) - { - // If there are any Directory symbols, build up the Component Guid - // generation seeds indexed by Directory/@Id. - componentIdGenSeeds = this.Section.Symbols.OfType() - .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) - .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed); - } - - // if the file rows have not been indexed by File.Component yet - // then do that now - if (filesByComponentId is null) - { - var files = this.Section.Symbols.OfType().ToList(); - - filesByComponentId = new Dictionary>(files.Count); - - foreach (var file in files) - { - if (!filesByComponentId.TryGetValue(file.ComponentRef, out var componentFiles)) - { - componentFiles = new List(); - filesByComponentId.Add(file.ComponentRef, componentFiles); - } - - componentFiles.Add(file); - } - } - - // validate component meets all the conditions to have a generated guid - var currentComponentFiles = filesByComponentId[componentSymbol.Id.Id]; - var numFilesInComponent = currentComponentFiles.Count; - string path = null; - - foreach (var fileRow in currentComponentFiles) - { - if (fileRow.Id.Id == componentSymbol.KeyPath) - { - // calculate the key file's canonical target path - var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform); - var fileName = this.BackendHelper.GetMsiFileName(fileRow.Name, false, true).ToLowerInvariant(); - path = Path.Combine(directoryPath, fileName); - - // find paths that are not canonicalized - if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || - path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || - path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || - path.StartsWith("TARGETDIR", StringComparison.Ordinal) || - path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || - path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) - { - this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileRow.ComponentRef, path)); - } - - // if component has more than one file, the key path must be versioned - if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version)) - { - this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.SourceLineNumbers)); - } - } - else - { - // not a key path, so it must be an unversioned file if component has more than one file - if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version)) - { - this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers)); - } - } - } - - // if the rules were followed, reward with a generated guid - if (!this.Messaging.EncounteredError) - { - componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path); - } - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs new file mode 100644 index 00000000..3cdc0c28 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs @@ -0,0 +1,262 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Set the guids for components with generatable guids and validate all are appropriately unique. + /// + internal class FinalizeComponentGuids + { + internal FinalizeComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform) + { + this.Messaging = messaging; + this.BackendHelper = helper; + this.PathResolver = pathResolver; + this.Section = section; + this.Platform = platform; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private IntermediateSection Section { get; } + + private Platform Platform { get; } + + private Dictionary ComponentIdGenSeeds { get; set; } + + private ILookup FilesByComponentId { get; set; } + + private Dictionary RegistrySymbolsById { get; set; } + + private Dictionary TargetPathsByDirectoryId { get; set; } + + public void Execute() + { + var componentGuidConditions = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var guidCollisions = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var componentSymbol in this.Section.Symbols.OfType()) + { + if (componentSymbol.ComponentId == "*") + { + this.GenerateComponentGuid(componentSymbol); + } + + // Now check for GUID collisions, but we don't care about unmanaged components and + // if there's a * GUID remaining, there's already an error that explained why it + // was not replaced with a real GUID. + if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && componentSymbol.ComponentId != "*") + { + if (!componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var components)) + { + components = new List(); + componentGuidConditions.Add(componentSymbol.ComponentId, components); + } + + components.Add(componentSymbol); + if (components.Count > 1) + { + guidCollisions.Add(componentSymbol.ComponentId); + } + } + } + + if (guidCollisions.Count > 0) + { + this.ReportGuidCollisions(guidCollisions, componentGuidConditions); + } + } + + private void GenerateComponentGuid(ComponentSymbol componentSymbol) + { + if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType) + { + this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers)); + return; + } + + if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType) + { + if (this.RegistrySymbolsById is null) + { + this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + if (this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol)) + { + var bitness = componentSymbol.Win64 ? "64" : String.Empty; + var regkey = String.Concat(bitness, registrySymbol.Root, "\\", registrySymbol.Key, "\\", registrySymbol.Name); + componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); + } + } + else // must be a File KeyPath. + { + // If the directory table hasn't been loaded into an indexed hash + // of directory ids to target names do that now. + if (this.TargetPathsByDirectoryId is null) + { + this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); + } + + // If the component id generation seeds have not been indexed + // from the Directory symbols do that now. + if (this.ComponentIdGenSeeds is null) + { + // If there are any Directory symbols, build up the Component Guid + // generation seeds indexed by Directory/@Id. + this.ComponentIdGenSeeds = this.Section.Symbols.OfType() + .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) + .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed); + } + + // If the file symbols have not been indexed by File's ComponentRef yet + // then do that now. + if (this.FilesByComponentId is null) + { + this.FilesByComponentId = this.Section.Symbols.OfType().ToLookup(f => f.ComponentRef); + } + + // validate component meets all the conditions to have a generated guid + var currentComponentFiles = this.FilesByComponentId[componentSymbol.Id.Id]; + var numFilesInComponent = currentComponentFiles.Count(); + string path = null; + + foreach (var fileSymbol in currentComponentFiles) + { + if (fileSymbol.Id.Id == componentSymbol.KeyPath) + { + // calculate the key file's canonical target path + var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, this.ComponentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform); + var fileName = this.BackendHelper.GetMsiFileName(fileSymbol.Name, false, true).ToLowerInvariant(); + path = Path.Combine(directoryPath, fileName); + + // find paths that are not canonicalized + if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || + path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || + path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || + path.StartsWith("TARGETDIR", StringComparison.Ordinal) || + path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || + path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) + { + this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileSymbol.ComponentRef, path)); + } + + // if component has more than one file, the key path must be versioned + if (1 < numFilesInComponent && String.IsNullOrEmpty(fileSymbol.Version)) + { + this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.SourceLineNumbers)); + } + } + else + { + // not a key path, so it must be an unversioned file if component has more than one file + if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileSymbol.Version)) + { + this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers)); + } + } + } + + // if the rules were followed, reward with a generated guid + if (!this.Messaging.EncounteredError) + { + componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path); + } + } + } + + private void ReportGuidCollisions(HashSet guidCollisions, Dictionary> componentGuidConditions) + { + Dictionary fileSymbolsById = null; + + foreach (var guid in guidCollisions) + { + var collidingComponents = componentGuidConditions[guid]; + var allComponentsHaveConditions = collidingComponents.All(c => !String.IsNullOrEmpty(c.Condition)); + + foreach (var componentSymbol in collidingComponents) + { + string path; + string type; + + if (componentSymbol.KeyPathType == ComponentKeyPathType.File) + { + if (fileSymbolsById is null) + { + fileSymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + path = fileSymbolsById.TryGetValue(componentSymbol.KeyPath, out var fileSymbol) ? fileSymbol.Source.Path : componentSymbol.KeyPath; + type = "source path"; + } + else if (componentSymbol.KeyPathType == ComponentKeyPathType.Registry) + { + if (this.RegistrySymbolsById is null) + { + this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + path = this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol) ? String.Concat(registrySymbol.Key, "\\", registrySymbol.Name) : componentSymbol.KeyPath; + type = "registry path"; + } + else + { + if (this.TargetPathsByDirectoryId is null) + { + this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); + } + + path = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, componentIdGenSeeds: null, componentSymbol.DirectoryRef, this.Platform); + type = "directory"; + } + + if (allComponentsHaveConditions) + { + this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); + } + } + } + } + + private Dictionary ResolveDirectoryTargetPaths() + { + var directories = this.Section.Symbols.OfType().ToList(); + + var targetPathsByDirectoryId = new Dictionary(directories.Count); + + // Get the target paths for all directories. + foreach (var directory in directories) + { + // If the directory Id already exists, we will skip it here since + // checking for duplicate primary keys is done later when importing tables + // into database + if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) + { + continue; + } + + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); + targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); + } + + return targetPathsByDirectoryId; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ValidateComponentGuidsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ValidateComponentGuidsCommand.cs deleted file mode 100644 index 5cad9247..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ValidateComponentGuidsCommand.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - /// - /// Validate that there are no duplicate GUIDs in the output. - /// - /// - /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a - /// warning, as the conditions might be mutually exclusive. - /// - internal class ValidateComponentGuidsCommand - { - internal ValidateComponentGuidsCommand(IMessaging messaging, IntermediateSection section) - { - this.Messaging = messaging; - this.Section = section; - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - public void Execute() - { - var componentGuidConditions = new Dictionary(); - - foreach (var componentSymbol in this.Section.Symbols.OfType()) - { - // We don't care about unmanaged components and if there's a * GUID remaining, - // there's already an error that prevented it from being replaced with a real GUID. - if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && "*" != componentSymbol.ComponentId) - { - var thisComponentHasCondition = !String.IsNullOrEmpty(componentSymbol.Condition); - var allComponentsHaveConditions = thisComponentHasCondition; - - if (componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var alreadyCheckedCondition)) - { - allComponentsHaveConditions = thisComponentHasCondition && alreadyCheckedCondition; - - if (allComponentsHaveConditions) - { - this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId)); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId)); - } - } - - componentGuidConditions[componentSymbol.ComponentId] = allComponentsHaveConditions; - } - } - } - } -} diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index c5f3a763..c39bec70 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -2454,24 +2454,27 @@ namespace WixToolset.Core } } - // check for conditions that exclude this component from using generated guids - var isGeneratableGuidOk = "*" == guid; - if (isGeneratableGuidOk) + // Check for conditions that exclude this component from using implicit ids and/or generated guids. + var allowImplicitIds = true; + if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType) { - if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType) + allowImplicitIds = false; + if (guid == "*") { this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers)); - isGeneratableGuidOk = false; } - else if (0 < files && ComponentKeyPathType.Registry == keyPathType) + } + else if (0 < files && ComponentKeyPathType.Registry == keyPathType) + { + allowImplicitIds = false; + if (guid == "*") { this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true)); - isGeneratableGuidOk = false; } } - // check for implicit KeyPath which can easily be accidentally changed - if (this.ShowPedanticMessages && !keyFound && !isGeneratableGuidOk) + // Check for implicit KeyPath which can easily be accidentally changed + if (this.ShowPedanticMessages && !keyFound && !allowImplicitIds) { this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id)); } @@ -2481,7 +2484,7 @@ namespace WixToolset.Core // generatable guid must be met. if (componentIdPlaceholder == id.Id) { - if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath)) + if (allowImplicitIds || keyFound && !String.IsNullOrEmpty(keyPath)) { this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath); diff --git a/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs new file mode 100644 index 00000000..d24ba08c --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs @@ -0,0 +1,45 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class ComponentFixture + { + [Fact] + public void CanDetectDuplicateComponentGuids() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Component", "GuidCollision.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + var errors = result.Messages.Where(m => m.Level == MessageLevel.Error); + Array.Equals(new[] + { + 369, + 369 + }, errors.Select(e => e.Id).ToArray()); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs new file mode 100644 index 00000000..a0e921cb --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 3f4bd928ab4c048792bf4c5c10004a1f22e8aa19 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 12 Apr 2021 15:13:08 -0400 Subject: Fix decompilation of directories as children TARGETDIR. --- .../Decompile/Decompiler.cs | 16 ++++++++++++--- .../DecompileFixture.cs | 8 ++++++-- .../ModuleFixture.cs | 17 ++++++++++------ .../DecompileTargetDirMergeModule/Expected.wxs | 18 +++++++++++++++++ .../DecompileTargetDirMergeModule/MergeModule1.msm | Bin 0 -> 32768 bytes .../TestData/SimpleModule/Module.wxs | 22 ++++++++++++--------- 6 files changed, 61 insertions(+), 20 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs index eb23f497..0b45a8b3 100644 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -4352,7 +4352,7 @@ namespace WixToolset.Core.WindowsInstaller.Decompile if (id == "TARGETDIR") { - // Skip TARGETDIR. + // Skip TARGETDIR (but see below!). } else if (row.IsColumnNull(1) || WindowsInstallerStandard.IsStandardDirectory(id)) { @@ -4360,7 +4360,9 @@ namespace WixToolset.Core.WindowsInstaller.Decompile } else { - if (!this.TryGetIndexedElement("Directory", out var xParentDirectory, row.FieldAsString(1))) + var parentDirectoryId = row.FieldAsString(1); + + if (!this.TryGetIndexedElement("Directory", out var xParentDirectory, parentDirectoryId)) { this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", row.FieldAsString(1), "Directory")); } @@ -4370,7 +4372,15 @@ namespace WixToolset.Core.WindowsInstaller.Decompile } else { - xParentDirectory.Add(xDirectory); + // TARGETDIR is omitted but if this directory is a first-generation descendant, add it as a root. + if (parentDirectoryId == "TARGETDIR") + { + this.RootElement.Add(xDirectory); + } + else + { + xParentDirectory.Add(xDirectory); + } } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs index b07f5bda..ab04da15 100644 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -3,10 +3,8 @@ namespace WixToolsetTest.CoreIntegration { using System.IO; - using System.Xml.Linq; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; - using WixToolset.Extensibility.Services; using Xunit; public class DecompileFixture @@ -78,5 +76,11 @@ namespace WixToolsetTest.CoreIntegration { DecompileAndCompare(@"TestData\DecompileNullComponent", "example.msi", "Expected.wxs"); } + + [Fact] + public void CanDecompileMergeModuleWithTargetDirComponent() + { + DecompileAndCompare(@"TestData\DecompileTargetDirMergeModule", "MergeModule1.msm", "Expected.wxs"); + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs index 349bad2c..17e91692 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs @@ -46,19 +46,23 @@ namespace WixToolsetTest.CoreIntegration WixAssert.CompareLineByLine(new[] { "MergeRedirectFolder\tTARGETDIR\t.", + "NotTheMergeRedirectFolder\tTARGETDIR\t.", "TARGETDIR\t\tSourceDir" }, dirSymbols.Select(d => String.Join("\t", d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal("filyIq8rqcxxf903Hsn5K9L0SWV73g", fileSymbol.Id.Id); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + var fileSymbols = section.Symbols.OfType().OrderBy(d => d.Id.Id).ToList(); + WixAssert.CompareLineByLine(new[] + { + $"File1\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", + $"File2\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", + }, fileSymbols.Select(fileSymbol => String.Join("\t", fileSymbol.Id.Id, fileSymbol[FileSymbolFields.Source].AsPath().Path, fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path)).ToArray()); var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); var fileRows = data.Tables["File"].Rows; Assert.Equal(new[] { - "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" + "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", }, fileRows.Select(r => r.FieldAsString(0)).ToArray()); var cabPath = Path.Combine(intermediateFolder, "msm-test.cab"); @@ -66,7 +70,8 @@ namespace WixToolsetTest.CoreIntegration var files = Query.GetCabinetFiles(cabPath); Assert.Equal(new[] { - "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" + "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", }, files.Select(f => Path.Combine(f.Path, f.Name)).ToArray()); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs new file mode 100644 index 00000000..7c5fe3cf --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm new file mode 100644 index 00000000..2a7b5e3a Binary files /dev/null and b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs index 9a523162..8317e7af 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs @@ -1,13 +1,17 @@ - - + + - + + + + + - - - - - - + + + + + + -- cgit v1.2.3-55-g6feb From 930b0ca136824d7f4917482fe7a7a577d28f6903 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 14 Apr 2021 22:03:30 -0500 Subject: Fix infinite loop in ResolveDelayedFieldsCommand and add failing test. --- .../Bind/ResolveDelayedFieldsCommand.cs | 1 + .../BindVariablesFixture.cs | 28 ++++++++++++++++++++++ .../CacheIdFromPackageDescription.wxs | 8 +++++++ 3 files changed, 37 insertions(+) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs index 0afcc2b3..4ad8f764 100644 --- a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs +++ b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs @@ -149,6 +149,7 @@ namespace WixToolset.Core.Bind else { this.Messaging.Write(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value)); + break; } } else diff --git a/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs index 857b84cc..44f9c802 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs @@ -10,6 +10,34 @@ namespace WixToolsetTest.CoreIntegration public class BindVariablesFixture { + [Fact(Skip = "Test demonstrates failure")] + public void CanBuildBundleWithPackageBindVariables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleBindVariables", "CacheIdFromPackageDescription.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + [Fact] public void CanBuildWithDefaultValue() { diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs new file mode 100644 index 00000000..7f5ea456 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs @@ -0,0 +1,8 @@ + + + + + + + + -- cgit v1.2.3-55-g6feb From bb40dc8a911ec0679016cbbf7132ea813ea1a3ad Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 19 Apr 2021 15:50:24 -0700 Subject: Detect duplicate CacheIds Fixes wixtoolset/issues#4628 --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 27 ++++++++++++++++++++-- src/WixToolset.Core.Burn/BurnBackendErrors.cs | 16 +++++++++---- .../BundleFixture.cs | 4 ++-- .../TestData/BadInput/DuplicateCacheIds.wxs | 7 ++++-- 4 files changed, 43 insertions(+), 11 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 5ddb6be1..37fc17f9 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -359,7 +359,6 @@ namespace WixToolset.Core.Burn this.BackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); } - Dictionary dependencySymbolsByKey; { var command = new ProcessDependencyProvidersCommand(this.Messaging, section, facades); command.Execute(); @@ -368,7 +367,6 @@ namespace WixToolset.Core.Burn { bundleSymbol.ProviderKey = command.BundleProviderKey; // set the overridable bundle provider key. } - dependencySymbolsByKey = command.DependencySymbolsByKey; } // Update the bundle per-machine/per-user scope based on the chained packages. @@ -381,6 +379,13 @@ namespace WixToolset.Core.Burn command.Execute(); } + this.DetectDuplicateCacheIds(facades); + + if (this.Messaging.EncounteredError) + { + return; + } + // Give the extension one last hook before generating the output files. foreach (var extension in this.BackendExtensions) { @@ -581,6 +586,24 @@ namespace WixToolset.Core.Burn } } + private void DetectDuplicateCacheIds(IDictionary facades) + { + var duplicateCacheIdDetector = new Dictionary(); + + foreach (var facade in facades.Values) + { + if (duplicateCacheIdDetector.TryGetValue(facade.PackageSymbol.CacheId, out var collisionPackage)) + { + this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds(collisionPackage.SourceLineNumbers, facade.PackageSymbol.CacheId)); + this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds2(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.CacheId)); + } + else + { + duplicateCacheIdDetector.Add(facade.PackageSymbol.CacheId, facade.PackageSymbol); + } + } + } + private IEnumerable GetRequiredSymbols() where T : IntermediateSymbol { var symbols = this.Output.Sections.Single().Symbols.OfType().ToList(); diff --git a/src/WixToolset.Core.Burn/BurnBackendErrors.cs b/src/WixToolset.Core.Burn/BurnBackendErrors.cs index 106c3b31..4c846e8a 100644 --- a/src/WixToolset.Core.Burn/BurnBackendErrors.cs +++ b/src/WixToolset.Core.Burn/BurnBackendErrors.cs @@ -6,10 +6,15 @@ namespace WixToolset.Core.Burn internal static class BurnBackendErrors { - //public static Message ReplaceThisWithTheFirstError(SourceLineNumber sourceLineNumbers) - //{ - // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstError, "format string", arg1, arg2); - //} + public static Message DuplicateCacheIds(SourceLineNumber originalLineNumber, string cacheId) + { + return Message(originalLineNumber, Ids.DuplicateCacheIds, "The cache id '{0}' has been duplicated as indicated in the following message.", cacheId); + } + + public static Message DuplicateCacheIds2(SourceLineNumber duplicateLineNumber, string cacheId) + { + return Message(duplicateLineNumber, Ids.DuplicateCacheIds2, "Each cache id must be unique. '{0}' has been used before as indicated in the previous message.", cacheId); + } private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) { @@ -18,7 +23,8 @@ namespace WixToolset.Core.Burn public enum Ids { - // ReplaceThisWithTheFirstError = 8000, + DuplicateCacheIds = 8000, + DuplicateCacheIds2 = 8001, } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index 38554b70..d121da0f 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -280,7 +280,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/4628")] + [Fact] public void CantBuildWithDuplicateCacheIds() { var folder = TestData.Get(@"TestData"); @@ -302,7 +302,7 @@ namespace WixToolsetTest.CoreIntegration "-o", exePath, }); - Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + Assert.Equal(8001, result.ExitCode); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs index 5c58ef50..0c350042 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs @@ -2,8 +2,11 @@ - - + + + + + -- cgit v1.2.3-55-g6feb From 0531df3e9f7b3e41434def0c569bd748adc721f6 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 21 Apr 2021 16:51:22 -0500 Subject: Detect payload collisions. #4574 --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 21 +++- .../Bundles/CreateNonUXContainers.cs | 10 +- .../Bundles/DetectPayloadCollisionsCommand.cs | 137 +++++++++++++++++++++ src/WixToolset.Core.Burn/BurnBackendErrors.cs | 36 ++++++ src/WixToolset.Core.Burn/BurnBackendWarnings.cs | 16 ++- .../WixToolset.Core.Burn.csproj | 3 + .../BundleFixture.cs | 40 +++++- .../MsiTransactionFixture.cs | 8 +- .../TestData/BadInput/DuplicatePayloadNames.wxs | 26 +++- .../BundleCustomTable/BundleCustomTable.wxs | 2 +- .../TestData/MsiTransaction/X64AfterX86Bundle.wxs | 8 +- .../TestData/MsiTransaction/X86AfterX64Bundle.wxs | 8 +- 12 files changed, 281 insertions(+), 34 deletions(-) create mode 100644 src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 4be72eec..b24481dc 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -436,13 +436,23 @@ namespace WixToolset.Core.Burn trackedFiles.Add(this.BackendHelper.TrackFile(bextManifestPath, TrackedFileType.Temporary)); } + var containers = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + { + var command = new DetectPayloadCollisionsCommand(this.Messaging, containers, facades.Values, payloadSymbols, packagesPayloads); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return; + } + // Create all the containers except the UX container first so the manifest (that goes in the UX container) // can contain all size and hash information about the non-UX containers. WixBundleContainerSymbol uxContainer; IEnumerable uxPayloads; - IEnumerable containers; { - var command = new CreateNonUXContainers(this.BackendHelper, section, bundleApplicationDllSymbol, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); + var command = new CreateNonUXContainers(this.BackendHelper, section, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); command.Execute(); fileTransfers.AddRange(command.FileTransfers); @@ -450,12 +460,11 @@ namespace WixToolset.Core.Burn uxContainer = command.UXContainer; uxPayloads = command.UXContainerPayloads; - containers = command.Containers; } // Resolve the download URLs now that we have all of the containers and payloads calculated. { - var command = new ResolveDownloadUrlsCommand(this.Messaging, this.BackendExtensions, containers, payloadSymbols); + var command = new ResolveDownloadUrlsCommand(this.Messaging, this.BackendExtensions, containers.Values, payloadSymbols); command.Execute(); } @@ -464,7 +473,7 @@ namespace WixToolset.Core.Burn { var executableName = Path.GetFileName(this.OutputPath); - var command = new CreateBurnManifestCommand(executableName, section, bundleSymbol, containers, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, packagesPayloads, orderedSearches, this.IntermediateFolder); + var command = new CreateBurnManifestCommand(executableName, section, bundleSymbol, containers.Values, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, packagesPayloads, orderedSearches, this.IntermediateFolder); command.Execute(); manifestPath = command.OutputPath; @@ -483,7 +492,7 @@ namespace WixToolset.Core.Burn } { - var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers); + var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers.Values); command.Execute(); fileTransfers.Add(command.Transfer); diff --git a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs index 0dd2ba15..7b5984c0 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs @@ -14,11 +14,12 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateNonUXContainers { - public CreateNonUXContainers(IBackendHelper backendHelper, IntermediateSection section, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) + public CreateNonUXContainers(IBackendHelper backendHelper, IntermediateSection section, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable containerSymbols, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) { this.BackendHelper = backendHelper; this.Section = section; this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; + this.Containers = containerSymbols; this.PayloadSymbols = payloadSymbols; this.IntermediateFolder = intermediateFolder; this.LayoutFolder = layoutFolder; @@ -33,7 +34,7 @@ namespace WixToolset.Core.Burn.Bundles public IEnumerable UXContainerPayloads { get; private set; } - public IEnumerable Containers { get; private set; } + private IEnumerable Containers { get; } private IBackendHelper BackendHelper { get; } @@ -57,11 +58,9 @@ namespace WixToolset.Core.Burn.Bundles var attachedContainerIndex = 1; // count starts at one because UX container is "0". - var containerSymbols = this.Section.Symbols.OfType().ToList(); - var payloadsByContainer = this.PayloadSymbols.Values.ToLookup(p => p.ContainerRef); - foreach (var container in containerSymbols) + foreach (var container in this.Containers) { var containerId = container.Id.Id; @@ -120,7 +119,6 @@ namespace WixToolset.Core.Burn.Bundles } } - this.Containers = containerSymbols; this.UXContainerPayloads = uxPayloadSymbols; this.FileTransfers = fileTransfers; this.TrackedFiles = trackedFiles; diff --git a/src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs b/src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs new file mode 100644 index 00000000..bfb6b918 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs @@ -0,0 +1,137 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class DetectPayloadCollisionsCommand + { + public DetectPayloadCollisionsCommand(IMessaging messaging, Dictionary containerSymbols, IEnumerable packages, Dictionary payloadSymbols, Dictionary> packagePayloads) + { + this.Messaging = messaging; + this.Containers = containerSymbols; + this.Packages = packages; + this.PayloadSymbols = payloadSymbols; + this.PackagePayloads = packagePayloads; + } + + private IMessaging Messaging { get; } + + private Dictionary Containers { get; } + + private IEnumerable Packages { get; } + + private Dictionary PayloadSymbols { get; } + + private Dictionary> PackagePayloads { get; } + + public void Execute() + { + this.DetectAttachedContainerCollisions(); + this.DetectExternalCollisions(); + this.DetectPackageCacheCollisions(); + } + + public void DetectAttachedContainerCollisions() + { + var attachedContainerPayloadsByNameByContainer = new Dictionary>(); + + foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.Embedded)) + { + var containerId = payload.ContainerRef; + var container = this.Containers[containerId]; + if (container.Type == ContainerType.Attached) + { + if (!attachedContainerPayloadsByNameByContainer.TryGetValue(containerId, out var attachedContainerPayloadsByName)) + { + attachedContainerPayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + attachedContainerPayloadsByNameByContainer.Add(containerId, attachedContainerPayloadsByName); + } + + if (!attachedContainerPayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) + { + attachedContainerPayloadsByName.Add(payload.Name, payload); + } + else + { + if (containerId == BurnConstants.BurnUXContainerName) + { + this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); + this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); + } + else + { + this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); + this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); + } + } + } + } + } + + public void DetectExternalCollisions() + { + var externalPayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.External)) + { + if (!externalPayloadsByName.TryGetValue(payload.Name, out var collisionSymbol)) + { + externalPayloadsByName.Add(payload.Name, payload); + } + else + { + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(payload.SourceLineNumbers, "Payload", payload.Id.Id, payload.Name)); + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); + } + } + + foreach (var container in this.Containers.Values.Where(c => c.Type == ContainerType.Detached)) + { + if (!externalPayloadsByName.TryGetValue(container.Name, out var collisionSymbol)) + { + externalPayloadsByName.Add(container.Name, container); + } + else + { + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(container.SourceLineNumbers, "Container", container.Id.Id, container.Name)); + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); + } + } + } + + public void DetectPackageCacheCollisions() + { + var packageCachePayloadsByNameByCacheId = new Dictionary>(); + + foreach (var packageFacade in this.Packages) + { + var packagePayloads = this.PackagePayloads[packageFacade.PackageId]; + if (!packageCachePayloadsByNameByCacheId.TryGetValue(packageFacade.PackageSymbol.CacheId, out var packageCachePayloadsByName)) + { + packageCachePayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + packageCachePayloadsByNameByCacheId.Add(packageFacade.PackageSymbol.CacheId, packageCachePayloadsByName); + } + + foreach (var payload in packagePayloads.Values) + { + if (!packageCachePayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) + { + packageCachePayloadsByName.Add(payload.Name, payload); + } + else + { + this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name, packageFacade.PackageId)); + this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision2(collisionPayload.SourceLineNumbers)); + } + } + } + } + } +} diff --git a/src/WixToolset.Core.Burn/BurnBackendErrors.cs b/src/WixToolset.Core.Burn/BurnBackendErrors.cs index 02ab1b5d..6f9a3706 100644 --- a/src/WixToolset.Core.Burn/BurnBackendErrors.cs +++ b/src/WixToolset.Core.Burn/BurnBackendErrors.cs @@ -6,6 +6,16 @@ namespace WixToolset.Core.Burn internal static class BurnBackendErrors { + public static Message BAContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) + { + return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the BA container. When extracting the container at runtime, the file will get overwritten.", payloadId, payloadName); + } + + public static Message BAContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision2, "The location of the payload related to the previous error."); + } + public static Message DuplicateCacheIds(SourceLineNumber originalLineNumber, string cacheId, string packageId) { return Message(originalLineNumber, Ids.DuplicateCacheIds, "The CacheId '{0}' for package '{1}' is duplicated. Each package must have a unique CacheId.", cacheId, packageId); @@ -16,6 +26,26 @@ namespace WixToolset.Core.Burn return Message(duplicateLineNumber, Ids.DuplicateCacheIds2, "The location of the package related to the previous error."); } + public static Message ExternalPayloadCollision(SourceLineNumber sourceLineNumbers, string symbolName, string payloadId, string payloadName) + { + return Message(sourceLineNumbers, Ids.ExternalPayloadCollision, "The external {0} '{1}' has a duplicate Name '{2}'. When building the bundle or laying out the bundle, the file will get overwritten.", symbolName, payloadId, payloadName); + } + + public static Message ExternalPayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.ExternalPayloadCollision2, "The location of the symbol related to the previous error."); + } + + public static Message PackageCachePayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName, string packageId) + { + return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in package '{2}'. When caching the package, the file will get overwritten.", payloadId, payloadName, packageId); + } + + public static Message PackageCachePayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision2, "The location of the payload related to the previous error."); + } + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) { return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); @@ -25,6 +55,12 @@ namespace WixToolset.Core.Burn { DuplicateCacheIds = 8000, DuplicateCacheIds2 = 8001, + BAContainerPayloadCollision = 8002, + BAContainerPayloadCollision2 = 8003, + ExternalPayloadCollision = 8004, + ExternalPayloadCollision2 = 8005, + PackageCachePayloadCollision = 8006, + PackageCachePayloadCollision2 = 8007, } } } diff --git a/src/WixToolset.Core.Burn/BurnBackendWarnings.cs b/src/WixToolset.Core.Burn/BurnBackendWarnings.cs index 9b2fa6c9..cbbc954e 100644 --- a/src/WixToolset.Core.Burn/BurnBackendWarnings.cs +++ b/src/WixToolset.Core.Burn/BurnBackendWarnings.cs @@ -6,10 +6,15 @@ namespace WixToolset.Core.Burn internal static class BurnBackendWarnings { - //public static Message ReplaceThisWithTheFirstWarning(SourceLineNumber sourceLineNumbers) - //{ - // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstWarning, "format string", arg1, arg2); - //} + public static Message AttachedContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) + { + return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", payloadId, payloadName); + } + + public static Message AttachedContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision2, "The location of the payload related to the previous error."); + } private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) { @@ -18,7 +23,8 @@ namespace WixToolset.Core.Burn public enum Ids { - // ReplaceThisWithTheFirstWarning = 8500, + AttachedContainerPayloadCollision = 8500, + AttachedContainerPayloadCollision2 = 8501, } } } diff --git a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj index 85bfae69..f2da8a50 100644 --- a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj +++ b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj @@ -19,6 +19,9 @@ <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index d121da0f..cc91d212 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -10,6 +10,7 @@ namespace WixToolsetTest.CoreIntegration using System.Xml; using Example.Extension; using WixBuildTools.TestSupport; + using WixToolset.Core.Burn; using WixToolset.Core.TestPackage; using WixToolset.Data; using WixToolset.Data.Burn; @@ -306,7 +307,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/4574")] + [Fact] public void CantBuildWithDuplicatePayloadNames() { var folder = TestData.Get(@"TestData"); @@ -328,7 +329,42 @@ namespace WixToolsetTest.CoreIntegration "-o", exePath, }); - Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + var attachedContainerWarnings = result.Messages.Where(m => m.Id == (int)BurnBackendWarnings.Ids.AttachedContainerPayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'Auto2' has a duplicate Name 'burn.exe' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", + }, attachedContainerWarnings); + + var baContainerErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.BAContainerPayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'DuplicatePayloadNames.wxs' has a duplicate Name 'fakeba.dll' in the BA container. When extracting the container at runtime, the file will get overwritten.", + "The Payload 'uxTxMXPVMXwQrPTMIGa5WGt93w0Ns' has a duplicate Name 'BootstrapperApplicationData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", + "The Payload 'uxYRbgitOs0K878jn5L_z7LdJ21KI' has a duplicate Name 'BundleExtensionData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", + }, baContainerErrors); + + var externalErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.ExternalPayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The external Payload 'HiddenPersistedBundleVariable.wxs' has a duplicate Name 'PayloadCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", + "The external Container 'MsiPackagesContainer' has a duplicate Name 'ContainerCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", + }, externalErrors); + + var packageCacheErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.PackageCachePayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'test.msi' has a duplicate Name 'test.msi' in package 'test.msi'. When caching the package, the file will get overwritten.", + }, packageCacheErrors); + + Assert.Equal(14, result.Messages.Length); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs index 7ec0ea93..a566b490 100644 --- a/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs @@ -81,7 +81,7 @@ namespace WixToolsetTest.CoreIntegration Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), "-bindpath", Path.Combine(folder, "SingleFile", "data"), "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX86.msi"), + "-o", Path.Combine(binFolder, "FirstX86", "FirstX86.msi"), }); result.AssertSuccess(); @@ -94,7 +94,7 @@ namespace WixToolsetTest.CoreIntegration Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), "-bindpath", Path.Combine(folder, "SingleFile", "data"), "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "SecondX86.msi"), + "-o", Path.Combine(binFolder, "SecondX86", "SecondX86.msi"), }); result.AssertSuccess(); @@ -108,7 +108,7 @@ namespace WixToolsetTest.CoreIntegration "-bindpath", Path.Combine(folder, "SingleFile", "data"), "-intermediateFolder", intermediateFolder, "-arch", "x64", - "-o", Path.Combine(binFolder, "FirstX64.msi"), + "-o", Path.Combine(binFolder, "FirstX64", "FirstX64.msi"), }); result.AssertSuccess(); @@ -122,7 +122,7 @@ namespace WixToolsetTest.CoreIntegration "-bindpath", Path.Combine(folder, "SingleFile", "data"), "-intermediateFolder", intermediateFolder, "-arch", "x64", - "-o", Path.Combine(binFolder, "SecondX64.msi"), + "-o", Path.Combine(binFolder, "SecondX64", "SecondX64.msi"), }); result.AssertSuccess(); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs index 2d4e8a3c..4fe7e097 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs @@ -2,8 +2,30 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs index db755171..e52302d4 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs @@ -48,6 +48,6 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs index 8f4fc8bd..e6527a36 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs @@ -2,11 +2,11 @@ - + - - - + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs index 221f06c5..f1c939db 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs @@ -2,11 +2,11 @@ - + - - - + + + -- cgit v1.2.3-55-g6feb From f4709371fa21ca1d0c06e04d1b53c0b10bfafeed Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 24 Apr 2021 16:28:44 -0500 Subject: Perform more bundle validation during linking. #5273, #6291, #6398 --- src/WixToolset.Core.Burn/BurnBackendErrors.cs | 2 +- src/WixToolset.Core.Burn/BurnBackendWarnings.cs | 2 +- .../WindowsInstallerBackendErrors.cs | 4 +- .../WindowsInstallerBackendWarnings.cs | 4 +- src/WixToolset.Core/CompilerErrors.cs | 2 +- src/WixToolset.Core/CompilerWarnings.cs | 2 +- src/WixToolset.Core/Compiler_Bundle.cs | 1 + .../Link/FlattenAndProcessBundleTablesCommand.cs | 121 +++++++++++-- src/WixToolset.Core/Link/WixGroupingOrdering.cs | 11 +- src/WixToolset.Core/LinkerErrors.cs | 48 +++++ src/WixToolset.Core/LinkerWarnings.cs | 30 ++++ src/WixToolset.Core/WixToolset.Core.csproj | 12 ++ .../BundleFixture.cs | 63 ++++++- .../BundleManifestFixture.cs | 6 +- .../ContainerFixture.cs | 197 +++++++++++++-------- .../PayloadFixture.cs | 5 +- .../TestData/BadInput/OrphanPayload.wxs | 11 ++ .../BadInput/PackageInMultipleContainers.wxs | 14 ++ .../TestData/BadInput/UnscheduledPackage.wxs | 6 +- .../BadInput/UnscheduledRollbackBoundary.wxs | 4 +- .../Container/LayoutPayloadInContainer.wxs | 28 +++ .../Container/PayloadInMultipleContainers.wxs | 28 +++ .../Payload/SharedBAAndPackagePayloadBundle.wxs | 2 +- .../SharedPayloadsBetweenPackages.wxs | 4 +- 24 files changed, 492 insertions(+), 115 deletions(-) create mode 100644 src/WixToolset.Core/LinkerErrors.cs create mode 100644 src/WixToolset.Core/LinkerWarnings.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/BurnBackendErrors.cs b/src/WixToolset.Core.Burn/BurnBackendErrors.cs index f23db47d..854c84e0 100644 --- a/src/WixToolset.Core.Burn/BurnBackendErrors.cs +++ b/src/WixToolset.Core.Burn/BurnBackendErrors.cs @@ -67,6 +67,6 @@ namespace WixToolset.Core.Burn PackageCachePayloadCollision = 8006, PackageCachePayloadCollision2 = 8007, MultipleAttachedContainersUnsupported = 8008, - } + } // last available is 8499. 8500 is BurnBackendWarnings. } } diff --git a/src/WixToolset.Core.Burn/BurnBackendWarnings.cs b/src/WixToolset.Core.Burn/BurnBackendWarnings.cs index 5edbbd67..a0ffa1dc 100644 --- a/src/WixToolset.Core.Burn/BurnBackendWarnings.cs +++ b/src/WixToolset.Core.Burn/BurnBackendWarnings.cs @@ -31,6 +31,6 @@ namespace WixToolset.Core.Burn AttachedContainerPayloadCollision = 8500, AttachedContainerPayloadCollision2 = 8501, EmptyContainer = 8502, - } + } // last available is 8999. 9000 is VerboseMessages. } } diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs index c1232dcc..0c15ad05 100644 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs +++ b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs @@ -18,7 +18,7 @@ namespace WixToolset.Core.WindowsInstaller public enum Ids { - // ReplaceThisWithTheFirstError = 7000, - } + // ReplaceThisWithTheFirstError = 7500, + } // last available is 7999. 8000 is BurnBackendErrors. } } diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs index 0eaadbe1..d0986a4d 100644 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs +++ b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs @@ -18,7 +18,7 @@ namespace WixToolset.Core.WindowsInstaller public enum Ids { - // ReplaceThisWithTheFirstWarning = 7500, - } + // ReplaceThisWithTheFirstWarning = 7100, + } // last available is 7499. 7500 is WindowsInstallerBackendErrors. } } diff --git a/src/WixToolset.Core/CompilerErrors.cs b/src/WixToolset.Core/CompilerErrors.cs index 9b3d85b9..10646dfd 100644 --- a/src/WixToolset.Core/CompilerErrors.cs +++ b/src/WixToolset.Core/CompilerErrors.cs @@ -38,6 +38,6 @@ namespace WixToolset.Core IllegalName = 6601, ExampleRegid = 6602, - } + } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. } } diff --git a/src/WixToolset.Core/CompilerWarnings.cs b/src/WixToolset.Core/CompilerWarnings.cs index eb838ae2..5c11b878 100644 --- a/src/WixToolset.Core/CompilerWarnings.cs +++ b/src/WixToolset.Core/CompilerWarnings.cs @@ -60,6 +60,6 @@ namespace WixToolset.Core Win64Component = 5435, DirectoryRefStandardDirectoryDeprecated = 5436, DefiningStandardDirectoryDeprecated = 5437, - } + } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. } } diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 779ad376..e09246df 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -2375,6 +2375,7 @@ namespace WixToolset.Core } this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, after); + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ContainerPackage, id.Id, ComplexReferenceChildType.Unknown, null); } return id.Id; diff --git a/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs b/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs index 0fa48d8c..e8df25ed 100644 --- a/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs +++ b/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs @@ -42,46 +42,143 @@ namespace WixToolset.Core.Link // We need to flatten the nested PayloadGroups and PackageGroups under // UX, Chain, and any Containers. When we're done, the WixGroups table // will hold Payloads under UX, ChainPackages (references?) under Chain, - // and ChainPackages/Payloads under the attached and any detatched - // Containers. + // and ContainerPackages/Payloads under any authored Containers. var groups = new WixGroupingOrdering(this.EntrySection, this.Messaging); - // Create UX payloads and Package payloads + // Create UX payloads and Package payloads and Container packages groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, - new[] { ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload }); + new[] { ComplexReferenceChildType.ContainerPackage, ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload }); groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Package, false); groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Container, false); groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Layout, false); // Create Chain packages... groups.UseTypes(new[] { ComplexReferenceParentType.PackageGroup }, new[] { ComplexReferenceChildType.Package, ComplexReferenceChildType.PackageGroup }); - groups.FlattenAndRewriteRows(ComplexReferenceChildType.PackageGroup, "WixChain", false); + groups.FlattenAndRewriteRows(ComplexReferenceParentType.PackageGroup, "WixChain", false); groups.RemoveUsedGroupRows(); } private void ProcessBundleComplexReferences() { + var containersById = this.EntrySection.Symbols.OfType().ToDictionary(c => c.Id.Id); var groups = this.EntrySection.Symbols.OfType().ToList(); var payloadsById = this.EntrySection.Symbols.OfType().ToDictionary(c => c.Id.Id); + var containerByPackage = new Dictionary(); + var referencedPackages = new HashSet(); + var payloadsInBA = new HashSet(); + var payloadsInPackageOrLayout = new HashSet(); + + foreach (var groupSymbol in groups) + { + switch (groupSymbol.ChildType) + { + case ComplexReferenceChildType.ContainerPackage: + switch (groupSymbol.ParentType) + { + case ComplexReferenceParentType.Container: + if (containerByPackage.TryGetValue(groupSymbol.ChildId, out var collisionContainer)) + { + this.Messaging.Write(LinkerErrors.PackageInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, groupSymbol.ParentId, collisionContainer.Id.Id)); + } + else + { + containerByPackage.Add(groupSymbol.ChildId, containersById[groupSymbol.ParentId]); + } + break; + } + break; + case ComplexReferenceChildType.Package: + switch (groupSymbol.ParentType) + { + case ComplexReferenceParentType.PackageGroup: + if (groupSymbol.ParentId == "WixChain") + { + referencedPackages.Add(groupSymbol.ChildId); + } + break; + } + break; + case ComplexReferenceChildType.Payload: + switch (groupSymbol.ParentType) + { + case ComplexReferenceParentType.Container: + if (groupSymbol.ParentId == BurnConstants.BurnUXContainerName) + { + payloadsInBA.Add(groupSymbol.ChildId); + } + break; + case ComplexReferenceParentType.Layout: + payloadsById[groupSymbol.ChildId].LayoutOnly = true; + payloadsInPackageOrLayout.Add(groupSymbol.ChildId); + break; + case ComplexReferenceParentType.Package: + payloadsInPackageOrLayout.Add(groupSymbol.ChildId); + break; + } + break; + } + } + + foreach (var package in this.EntrySection.Symbols.OfType()) + { + if (!referencedPackages.Contains(package.Id.Id)) + { + this.Messaging.Write(LinkerErrors.UnscheduledChainPackage(package.SourceLineNumbers, package.Id.Id)); + } + } + + foreach (var rollbackBoundary in this.EntrySection.Symbols.OfType()) + { + if (!referencedPackages.Contains(rollbackBoundary.Id.Id)) + { + this.Messaging.Write(LinkerErrors.UnscheduledRollbackBoundary(rollbackBoundary.SourceLineNumbers, rollbackBoundary.Id.Id)); + } + } + + foreach (var payload in payloadsById.Values) + { + var payloadId = payload.Id.Id; + if (payloadsInBA.Contains(payloadId)) + { + if (payloadsInPackageOrLayout.Contains(payloadId)) + { + this.Messaging.Write(LinkerErrors.PayloadSharedWithBA(payload.SourceLineNumbers, payloadId)); + } + } + else if (!payloadsInPackageOrLayout.Contains(payloadId)) + { + this.Messaging.Write(LinkerErrors.OrphanedPayload(payload.SourceLineNumbers, payloadId)); + } + } + + if (this.Messaging.EncounteredError) + { + return; + } + // Assign authored payloads to authored containers. // Compressed Payloads not assigned to a container here will get assigned to the default attached container during binding. foreach (var groupSymbol in groups) { - if (ComplexReferenceChildType.Payload == groupSymbol.ChildType) + if (groupSymbol.ChildType == ComplexReferenceChildType.Payload && groupSymbol.ParentType == ComplexReferenceParentType.Container) { var payloadSymbol = payloadsById[groupSymbol.ChildId]; + var containerId = groupSymbol.ParentId; - if (ComplexReferenceParentType.Container == groupSymbol.ParentType) + if (String.IsNullOrEmpty(payloadSymbol.ContainerRef)) + { + payloadSymbol.ContainerRef = containerId; + } + else { - // TODO: v3 didn't warn if we overwrote the payload's container. - // Should we warn now? - payloadSymbol.ContainerRef = groupSymbol.ParentId; + this.Messaging.Write(LinkerWarnings.PayloadInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId, payloadSymbol.ContainerRef)); } - else if (ComplexReferenceParentType.Layout == groupSymbol.ParentType) + + if (payloadSymbol.LayoutOnly) { - payloadSymbol.LayoutOnly = true; + this.Messaging.Write(LinkerWarnings.LayoutPayloadInContainer(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); } } } diff --git a/src/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/WixToolset.Core/Link/WixGroupingOrdering.cs index 99220900..f9de82a9 100644 --- a/src/WixToolset.Core/Link/WixGroupingOrdering.cs +++ b/src/WixToolset.Core/Link/WixGroupingOrdering.cs @@ -62,7 +62,7 @@ namespace WixToolset.Core.Link /// The group type for the parent group to flatten. /// The identifier of the parent group to flatten. /// Whether to remove used group rows before returning. - public void FlattenAndRewriteRows(ComplexReferenceChildType parentType, string parentId, bool removeUsedRows) + public void FlattenAndRewriteRows(ComplexReferenceParentType parentType, string parentId, bool removeUsedRows) { var parentTypeString = parentType.ToString(); Debug.Assert(this.groupTypes.Contains(parentTypeString)); @@ -648,14 +648,11 @@ namespace WixToolset.Core.Link // We *don't* propagate ordering information from Packages or // Containers to their children, because ordering doesn't matter // for them, and a Payload in two Packages (or Containers) can - // cause a circular reference to occur. We do, however, need to - // track the ordering in the UX Container, because we need the - // first payload to be the entrypoint. + // cause a circular reference to occur. private bool ShouldItemPropagateChildOrdering() { - if (String.Equals(nameof(ComplexReferenceChildType.Package), this.Type, StringComparison.Ordinal) || - (String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal) && - !String.Equals(BurnConstants.BurnUXContainerName, this.Id, StringComparison.Ordinal))) + if (String.Equals(nameof(ComplexReferenceParentType.Package), this.Type, StringComparison.Ordinal) || + String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal)) { return false; } diff --git a/src/WixToolset.Core/LinkerErrors.cs b/src/WixToolset.Core/LinkerErrors.cs new file mode 100644 index 00000000..7ce8c00e --- /dev/null +++ b/src/WixToolset.Core/LinkerErrors.cs @@ -0,0 +1,48 @@ +// 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 +{ + using WixToolset.Data; + + internal static class LinkerErrors + { + public static Message OrphanedPayload(SourceLineNumber sourceLineNumbers, string payloadId) + { + return Message(sourceLineNumbers, Ids.OrphanedPayload, "Found orphaned Payload '{0}'. Make sure to reference it from a Package, the BootstrapperApplication, or the Bundle or move it into its own Fragment so it only gets linked in when actually used.", payloadId); + } + + public static Message PackageInMultipleContainers(SourceLineNumber sourceLineNumbers, string packageId, string containerId1, string containerId2) + { + return Message(sourceLineNumbers, Ids.PackageInMultipleContainers, "The Package '{0}' is referenced from multiple containers - Container '{1}' and Container '{2}'. This is not currently supported.", packageId, containerId1, containerId2); + } + + public static Message PayloadSharedWithBA(SourceLineNumber sourceLineNumbers, string payloadId) + { + return Message(sourceLineNumbers, Ids.PayloadSharedWithBA, "The Payload '{0}' is shared with the BootstrapperApplication. This is not currently supported.", payloadId); + } + + public static Message UnscheduledChainPackage(SourceLineNumber sourceLineNumbers, string packageId) + { + return Message(sourceLineNumbers, Ids.UnscheduledChainPackage, "Found orphaned Package '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", packageId); + } + + public static Message UnscheduledRollbackBoundary(SourceLineNumber sourceLineNumbers, string rollbackBoundaryId) + { + return Message(sourceLineNumbers, Ids.UnscheduledRollbackBoundary, "Found orphaned RollbackBoundary '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", rollbackBoundaryId); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + OrphanedPayload = 7000, + PackageInMultipleContainers = 7001, + PayloadSharedWithBA = 7002, + UnscheduledChainPackage = 7003, + UnscheduledRollbackBoundary = 7004, + } // last available is 7099. 7100 is WindowsInstallerBackendWarnings. + } +} diff --git a/src/WixToolset.Core/LinkerWarnings.cs b/src/WixToolset.Core/LinkerWarnings.cs new file mode 100644 index 00000000..0eca090e --- /dev/null +++ b/src/WixToolset.Core/LinkerWarnings.cs @@ -0,0 +1,30 @@ +// 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 +{ + using WixToolset.Data; + + internal static class LinkerWarnings + { + public static Message LayoutPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) + { + return Message(sourceLineNumbers, Ids.LayoutPayloadInContainer, "The layout-only Payload '{0}' is being added to Container '{1}'. It will not be extracted during layout.", payloadId, containerId); + } + + public static Message PayloadInMultipleContainers(SourceLineNumber sourceLineNumbers, string payloadId, string containerId1, string containerId2) + { + return Message(sourceLineNumbers, Ids.PayloadInMultipleContainers, "The Payload '{0}' can't be added to Container '{1}' because it was already added to Container '{2}'.", payloadId, containerId1, containerId2); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); + } + + public enum Ids + { + LayoutPayloadInContainer = 6900, + PayloadInMultipleContainers = 6901, + } // last available is 6999. 7000 is LinkerErrors. + } +} diff --git a/src/WixToolset.Core/WixToolset.Core.csproj b/src/WixToolset.Core/WixToolset.Core.csproj index 902f63ce..7242d500 100644 --- a/src/WixToolset.Core/WixToolset.Core.csproj +++ b/src/WixToolset.Core/WixToolset.Core.csproj @@ -13,6 +13,18 @@ true + + + <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs index cc91d212..ab644080 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -10,6 +10,7 @@ namespace WixToolsetTest.CoreIntegration using System.Xml; using Example.Extension; using WixBuildTools.TestSupport; + using WixToolset.Core; using WixToolset.Core.Burn; using WixToolset.Core.TestPackage; using WixToolset.Data; @@ -368,7 +369,61 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6291")] + [Fact] + public void CantBuildWithOrphanPayload() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "OrphanPayload.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.OrphanedPayload, result.ExitCode); + } + } + + [Fact] + public void CantBuildWithPackageInMultipleContainers() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "PackageInMultipleContainers.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.PackageInMultipleContainers, result.ExitCode); + } + } + + [Fact] public void CantBuildWithUnscheduledPackage() { var folder = TestData.Get(@"TestData"); @@ -390,11 +445,11 @@ namespace WixToolsetTest.CoreIntegration "-o", exePath, }); - Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + Assert.Equal((int)LinkerErrors.Ids.UnscheduledChainPackage, result.ExitCode); } } - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6291")] + [Fact] public void CantBuildWithUnscheduledRollbackBoundary() { var folder = TestData.Get(@"TestData"); @@ -416,7 +471,7 @@ namespace WixToolsetTest.CoreIntegration "-o", exePath, }); - Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + Assert.Equal((int)LinkerErrors.Ids.UnscheduledRollbackBoundary, result.ExitCode); } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs index 4a8473df..29c741dc 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -291,7 +291,7 @@ namespace WixToolsetTest.CoreIntegration var baFolderPath = Path.Combine(baseFolder, "ba"); var extractFolderPath = Path.Combine(baseFolder, "extract"); - var result = WixRunner.Execute(false, new[] + var result = WixRunner.Execute(new[] { "build", Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), @@ -315,8 +315,8 @@ namespace WixToolsetTest.CoreIntegration { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, }; Assert.Equal(2, exePackageElements.Count); - Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", exePackageElements[1].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", exePackageElements[1].GetTestXml(ignoreAttributesByElementName)); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs index f24429f7..ffeda069 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs @@ -8,10 +8,9 @@ namespace WixToolsetTest.CoreIntegration using System.Linq; using System.Xml; using WixBuildTools.TestSupport; + using WixToolset.Core; using WixToolset.Core.Burn; using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; using Xunit; public class ContainerFixture @@ -30,33 +29,9 @@ namespace WixToolsetTest.CoreIntegration var baFolderPath = Path.Combine(baseFolder, "ba"); var extractFolderPath = Path.Combine(baseFolder, "extract"); - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX86.msi"), - }); - - result.AssertSuccess(); + this.BuildMsis(folder, intermediateFolder, binFolder); - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX64.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] + var result = WixRunner.Execute(new[] { "build", Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), @@ -98,33 +73,9 @@ namespace WixToolsetTest.CoreIntegration var baFolderPath = Path.Combine(baseFolder, "ba"); var extractFolderPath = Path.Combine(baseFolder, "extract"); - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX86.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX64.msi"), - }); - - result.AssertSuccess(); + this.BuildMsis(folder, intermediateFolder, binFolder); - result = WixRunner.Execute(new[] + var result = WixRunner.Execute(new[] { "build", Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), @@ -174,7 +125,7 @@ namespace WixToolsetTest.CoreIntegration } [Fact] - public void MultipleAttachedContainersAreNotCurrentlySupported() + public void LayoutPayloadIsPutInContainer() { var folder = TestData.Get(@"TestData"); @@ -187,36 +138,92 @@ namespace WixToolsetTest.CoreIntegration var baFolderPath = Path.Combine(baseFolder, "ba"); var extractFolderPath = Path.Combine(baseFolder, "extract"); - var result = WixRunner.Execute(new[] + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(false, new[] { "build", - Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), + Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX86.msi"), + "-o", bundlePath }); + WixAssert.CompareLineByLine(new string[] + { + "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", + }, result.Messages.Select(m => m.ToString()).ToArray()); result.AssertSuccess(); - result = WixRunner.Execute(new[] + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributes)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, payloads); + } + } + + [Fact] + public void MultipleAttachedContainersAreNotCurrentlySupported() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] { "build", - Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), + Path.Combine(folder, "Container", "MultipleAttachedContainers.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX64.msi"), + "-o", bundlePath }); - result.AssertSuccess(); + Assert.Equal((int)BurnBackendErrors.Ids.MultipleAttachedContainersUnsupported, result.ExitCode); + } + } + + [Fact] + public void PayloadIsNotPutInMultipleContainers() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); - result = WixRunner.Execute(new[] + var result = WixRunner.Execute(false, new[] { "build", - Path.Combine(folder, "Container", "MultipleAttachedContainers.wxs"), + Path.Combine(folder, "Container", "PayloadInMultipleContainers.wxs"), Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), "-bindpath", binFolder, @@ -224,8 +231,56 @@ namespace WixToolsetTest.CoreIntegration "-o", bundlePath }); - Assert.Equal((int)BurnBackendErrors.Ids.MultipleAttachedContainersUnsupported, result.ExitCode); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'SharedPayload' can't be added to Container 'FirstX64' because it was already added to Container 'FirstX86'.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributes)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, payloads); } } + + private void BuildMsis(string folder, string intermediateFolder, string binFolder) + { + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "FirstX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "FirstX64.msi"), + }); + + result.AssertSuccess(); + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs index e9e59b9e..da87bf6c 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs @@ -8,6 +8,7 @@ namespace WixToolsetTest.CoreIntegration using System.Linq; using System.Xml; using WixBuildTools.TestSupport; + using WixToolset.Core; using WixToolset.Core.TestPackage; using WixToolset.Data; using WixToolset.Data.Symbols; @@ -117,7 +118,7 @@ namespace WixToolsetTest.CoreIntegration } } - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/5273")] + [Fact] public void RejectsPayloadSharedBetweenPackageAndBA() { var folder = TestData.Get(@"TestData"); @@ -139,7 +140,7 @@ namespace WixToolsetTest.CoreIntegration "-o", bundlePath, }); - Assert.InRange(result.ExitCode, 2, int.MaxValue); + Assert.Equal((int)LinkerErrors.Ids.PayloadSharedWithBA, result.ExitCode); } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs new file mode 100644 index 00000000..92a9602f --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs new file mode 100644 index 00000000..a00874ce --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs index ab86982d..fc53c4a2 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs @@ -2,15 +2,15 @@ - - + + - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs index 8015ed92..6cf8528e 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs @@ -2,8 +2,8 @@ - - + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs new file mode 100644 index 00000000..0c5f8c7e --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs new file mode 100644 index 00000000..c7f549a3 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs index 4cfeb99f..5263cbd4 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs @@ -2,7 +2,7 @@ - + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs index 3457a3b7..f16fce0d 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs @@ -2,10 +2,10 @@ - + - + -- cgit v1.2.3-55-g6feb From 8a957275b6a1185f3b74fb31fff258be7f628347 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 24 Apr 2021 17:00:13 -0500 Subject: Ignore Compressed attribute for payloads when authored into a container #6406 --- src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs | 6 ++++++ src/WixToolset.Core/LinkerWarnings.cs | 6 ++++++ src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs | 7 ++++++- .../TestData/Payload/DownloadUrlPlaceholdersBundle.wxs | 2 +- 4 files changed, 19 insertions(+), 2 deletions(-) (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs b/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs index 52734141..16593c7d 100644 --- a/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs +++ b/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs @@ -169,6 +169,12 @@ namespace WixToolset.Core.Link if (String.IsNullOrEmpty(payloadSymbol.ContainerRef)) { + if (payloadSymbol.Compressed == false) + { + this.Messaging.Write(LinkerWarnings.UncompressedPayloadInContainer(payloadSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); + } + + payloadSymbol.Compressed = true; payloadSymbol.ContainerRef = containerId; } else diff --git a/src/WixToolset.Core/LinkerWarnings.cs b/src/WixToolset.Core/LinkerWarnings.cs index 0eca090e..968fa4ea 100644 --- a/src/WixToolset.Core/LinkerWarnings.cs +++ b/src/WixToolset.Core/LinkerWarnings.cs @@ -16,6 +16,11 @@ namespace WixToolset.Core return Message(sourceLineNumbers, Ids.PayloadInMultipleContainers, "The Payload '{0}' can't be added to Container '{1}' because it was already added to Container '{2}'.", payloadId, containerId1, containerId2); } + public static Message UncompressedPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) + { + return Message(sourceLineNumbers, Ids.UncompressedPayloadInContainer, "The Payload '{0}' is being added to Container '{1}', overriding its Compressed value of 'no'.", payloadId, containerId); + } + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) { return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); @@ -25,6 +30,7 @@ namespace WixToolset.Core { LayoutPayloadInContainer = 6900, PayloadInMultipleContainers = 6901, + UncompressedPayloadInContainer = 6902, } // last available is 6999. 7000 is LinkerErrors. } } diff --git a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs index da87bf6c..23f6a9ba 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs @@ -157,7 +157,7 @@ namespace WixToolsetTest.CoreIntegration var baFolderPath = Path.Combine(baseFolder, "ba"); var extractFolderPath = Path.Combine(baseFolder, "extract"); - var result = WixRunner.Execute(new[] + var result = WixRunner.Execute(false, new[] { "build", Path.Combine(folder, "Payload", "DownloadUrlPlaceholdersBundle.wxs"), @@ -170,6 +170,11 @@ namespace WixToolsetTest.CoreIntegration result.AssertSuccess(); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'burn.exe' is being added to Container 'PackagesContainer', overriding its Compressed value of 'no'.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + Assert.True(File.Exists(bundlePath)); var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs index 87bb79f9..f8f38ea6 100644 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs @@ -14,7 +14,7 @@ - + -- cgit v1.2.3-55-g6feb From b4210b23ea28e0acc5a8b7ca5357d7d3926071b4 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 25 Apr 2021 15:36:31 -0500 Subject: Allow DownloadUrl on embedded payloads. #5253 --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 2 +- .../Bind/ResolveDownloadUrlsCommand.cs | 17 +++--- src/WixToolset.Core.Burn/Bundles/BurnCommon.cs | 3 +- .../Bundles/CreateBurnManifestCommand.cs | 61 ++++++++++++---------- .../Bundles/CreateContainerCommand.cs | 7 --- src/WixToolset.Core/Compile/CompilerPayload.cs | 11 +--- .../ContainerFixture.cs | 50 ++++++++++++++++-- .../Container/HarvestIntoAttachedContainer.wxs | 17 ++++++ 8 files changed, 110 insertions(+), 58 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index deab4d78..4a4f06f3 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -300,7 +300,7 @@ namespace WixToolset.Core.Burn if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) { - payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAttachedContainerEmbeddedIdFormat, payloadIndex); + payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAuthoredContainerEmbeddedIdFormat, payloadIndex); ++payloadIndex; } } diff --git a/src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs b/src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs index e41c1058..c678b114 100644 --- a/src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs @@ -5,6 +5,7 @@ namespace WixToolset.Core.Burn.Bind using System; using System.Collections.Generic; using WixToolset.Data; + using WixToolset.Data.Burn; using WixToolset.Data.Symbols; using WixToolset.Extensibility; using WixToolset.Extensibility.Services; @@ -60,7 +61,14 @@ namespace WixToolset.Core.Burn.Bind { foreach (var payload in this.PayloadsById.Values) { - if (payload.Packaging == PackagingType.External) + if (payload.Packaging == PackagingType.Embedded && payload.ContainerRef == BurnConstants.BurnUXContainerName) + { + if (!String.IsNullOrEmpty(payload.DownloadUrl)) + { + this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(payload.SourceLineNumbers, payload.Id.Id)); + } + } + else { var packageId = payload.ParentPackagePayloadRef; var parentUrl = payload.ParentPackagePayloadRef == null ? null : this.PayloadsById[payload.ParentPackagePayloadRef].DownloadUrl; @@ -70,13 +78,6 @@ namespace WixToolset.Core.Burn.Bind payload.DownloadUrl = resolvedUrl; } } - else if (payload.Packaging == PackagingType.Embedded) - { - if (!String.IsNullOrEmpty(payload.DownloadUrl)) - { - this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForEmbeddedPayloads(payload.SourceLineNumbers, payload.Id.Id)); - } - } } } diff --git a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs index ab3b7896..1eb3563a 100644 --- a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs +++ b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs @@ -19,8 +19,7 @@ namespace WixToolset.Core.Burn.Bundles { public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; public const string BurnUXContainerEmbeddedIdFormat = "u{0}"; - public const string BurnUXContainerPayloadIdFormat = "p{0}"; - public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}"; + public const string BurnAuthoredContainerEmbeddedIdFormat = "a{0}"; public const string BADataFileName = "BootstrapperApplicationData.xml"; public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index db09b540..5655d23d 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -166,9 +166,7 @@ namespace WixToolset.Core.Burn.Bundles // write the UX allPayloads... foreach (var payload in this.UXContainerPayloads) { - writer.WriteStartElement("Payload"); - this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); - writer.WriteEndElement(); + this.WriteBurnManifestUXPayload(writer, payload); } writer.WriteEndElement(); // @@ -183,20 +181,9 @@ namespace WixToolset.Core.Burn.Bundles } } - foreach (var payload in this.Payloads.Values) + foreach (var payload in this.Payloads.Values.Where(p => p.ContainerRef != BurnConstants.BurnUXContainerName)) { - if (PackagingType.Embedded == payload.Packaging && BurnConstants.BurnUXContainerName != payload.ContainerRef) - { - writer.WriteStartElement("Payload"); - this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); - writer.WriteEndElement(); - } - else if (PackagingType.External == payload.Packaging) - { - writer.WriteStartElement("Payload"); - this.WriteBurnManifestPayloadAttributes(writer, payload, false, this.Payloads); - writer.WriteEndElement(); - } + this.WriteBurnManifestPayload(writer, payload); } foreach (var rollbackBoundary in this.RollbackBoundaries) @@ -654,9 +641,9 @@ namespace WixToolset.Core.Burn.Bundles } } - private void WriteBurnManifestPayloadAttributes(XmlTextWriter writer, WixBundlePayloadSymbol payload, bool embeddedOnly, Dictionary allPayloads) + private void WriteBurnManifestPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) { - Debug.Assert(!embeddedOnly || PackagingType.Embedded == payload.Packaging); + writer.WriteStartElement("Payload"); writer.WriteAttributeString("Id", payload.Id.Id); writer.WriteAttributeString("FilePath", payload.Name); @@ -668,28 +655,46 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteAttributeString("LayoutOnly", "yes"); } + if (!String.IsNullOrEmpty(payload.DownloadUrl)) + { + writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); + } + switch (payload.Packaging) { case PackagingType.Embedded: // this means it's in a container. + Debug.Assert(BurnConstants.BurnUXContainerName != payload.ContainerRef); + writer.WriteAttributeString("Packaging", "embedded"); writer.WriteAttributeString("SourcePath", payload.EmbeddedId); - - if (BurnConstants.BurnUXContainerName != payload.ContainerRef) - { - writer.WriteAttributeString("Container", payload.ContainerRef); - } + writer.WriteAttributeString("Container", payload.ContainerRef); break; case PackagingType.External: - if (!String.IsNullOrEmpty(payload.DownloadUrl)) - { - writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); - } - writer.WriteAttributeString("Packaging", "external"); writer.WriteAttributeString("SourcePath", payload.Name); break; } + + writer.WriteEndElement(); + } + + private void WriteBurnManifestUXPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) + { + Debug.Assert(PackagingType.Embedded == payload.Packaging); + Debug.Assert(BurnConstants.BurnUXContainerName == payload.ContainerRef); + + writer.WriteStartElement("Payload"); + + // TODO: The engine should be updated to not require FileSize, Hash, or Packaging for UX payloads since the values are never used. + writer.WriteAttributeString("Id", payload.Id.Id); + writer.WriteAttributeString("FilePath", payload.Name); + writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Hash", payload.Hash); + writer.WriteAttributeString("Packaging", "embedded"); + writer.WriteAttributeString("SourcePath", payload.EmbeddedId); + + writer.WriteEndElement(); } } } diff --git a/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs index 8f361626..87a63cc3 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs @@ -44,13 +44,6 @@ namespace WixToolset.Core.Burn.Bundles public void Execute() { - var payloadCount = this.Payloads.Count(); // The number of embedded payloads - - if (!String.IsNullOrEmpty(this.ManifestFile)) - { - ++payloadCount; - } - var cabinetPath = Path.GetFullPath(this.OutputPath); var files = new List(); diff --git a/src/WixToolset.Core/Compile/CompilerPayload.cs b/src/WixToolset.Core/Compile/CompilerPayload.cs index 4c7e843d..3f423034 100644 --- a/src/WixToolset.Core/Compile/CompilerPayload.cs +++ b/src/WixToolset.Core/Compile/CompilerPayload.cs @@ -15,8 +15,6 @@ namespace WixToolset.Core public string Description { get; set; } - public string DisplayName { get; set; } - public string DownloadUrl { get; set; } public string Hash { get; set; } @@ -158,7 +156,7 @@ namespace WixToolset.Core if (!String.IsNullOrEmpty(this.DownloadUrl)) { - this.Core.Write(WarningMessages.DownloadUrlNotSupportedForEmbeddedPayloads(this.SourceLineNumbers, this.Id.Id)); + this.Core.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(this.SourceLineNumbers, this.Id.Id)); } this.Compressed = YesNoDefaultType.Yes; @@ -174,7 +172,7 @@ namespace WixToolset.Core DownloadUrl = this.DownloadUrl, Compressed = (this.Compressed == YesNoDefaultType.Yes) ? true : (this.Compressed == YesNoDefaultType.No) ? (bool?)false : null, UnresolvedSourceFile = this.SourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. - DisplayName = this.DisplayName ?? this.ProductName, + DisplayName = this.ProductName, Description = this.Description, Hash = this.Hash, FileSize = this.Size, @@ -245,11 +243,6 @@ namespace WixToolset.Core this.Description = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); } - public void ParseDisplayName(XAttribute attrib) - { - this.DisplayName = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - public void ParseDownloadUrl(XAttribute attrib) { this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); diff --git a/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs index ff48ee05..dd381dfe 100644 --- a/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs @@ -15,6 +15,50 @@ namespace WixToolsetTest.CoreIntegration public class ContainerFixture { + [Fact(Skip = "Test demonstrates failure")] + public void CanBuildWithCustomAttachedContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder, buildToSubfolder: true); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "HarvestIntoAttachedContainer.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); + Assert.Equal(4, payloads.Count); + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + Assert.Equal(@"", payloads[0].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[1].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[2].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[3].GetTestXml(ignoreAttributes)); + } + } + [Fact] public void HarvestedPayloadsArePutInCorrectContainer() { @@ -309,7 +353,7 @@ namespace WixToolsetTest.CoreIntegration } } - private void BuildMsis(string folder, string intermediateFolder, string binFolder) + private void BuildMsis(string folder, string intermediateFolder, string binFolder, bool buildToSubfolder = false) { var result = WixRunner.Execute(new[] { @@ -319,7 +363,7 @@ namespace WixToolsetTest.CoreIntegration Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), "-bindpath", Path.Combine(folder, "SingleFile", "data"), "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX86.msi"), + "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX86" : ".", "FirstX86.msi"), }); result.AssertSuccess(); @@ -332,7 +376,7 @@ namespace WixToolsetTest.CoreIntegration Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), "-bindpath", Path.Combine(folder, "SingleFile", "data"), "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX64.msi"), + "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX64" : ".", "FirstX64.msi"), }); result.AssertSuccess(); diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs new file mode 100644 index 00000000..ec757c5d --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb From 8cf0427984a88b0b3ddfb2061e5be721afffe82e Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 22 Apr 2021 17:19:56 -0700 Subject: Move Core into wix --- .editorconfig | 37 - README.md | 3 - WixToolset.Core.sln | 156 - WixToolset.Core.v3.ncrunchsolution | 6 - appveyor.cmd | 20 - appveyor.yml | 44 - nuget.config | 13 - src/.editorconfig | 37 + src/Custom.Build.props | 6 - src/Directory.Build.props | 27 - src/Directory.Build.targets | 51 - src/Directory.csproj.props | 13 - src/Directory.csproj.targets | 26 - src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs | 27 - src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 650 -- .../Bind/ExtensionSearchFacade.cs | 24 - .../Bind/GenerateManifestDataFromIRCommand.cs | 237 - .../Bind/LegacySearchFacade.cs | 185 - .../Bind/ProcessBundleSoftwareTagsCommand.cs | 133 - .../Bind/ProcessDependencyProvidersCommand.cs | 147 - .../Bind/ResolveDownloadUrlsCommand.cs | 128 - .../Bind/SetVariableSearchFacade.cs | 48 - src/WixToolset.Core.Burn/BundleBackend.cs | 77 - .../AutomaticallySlipstreamPatchesCommand.cs | 117 - .../Bundles/BundleHashAlgorithm.cs | 30 - src/WixToolset.Core.Burn/Bundles/BurnCommon.cs | 385 - src/WixToolset.Core.Burn/Bundles/BurnReader.cs | 212 - src/WixToolset.Core.Burn/Bundles/BurnWriter.cs | 245 - ...CreateBootstrapperApplicationManifestCommand.cs | 290 - .../Bundles/CreateBundleExeCommand.cs | 325 - .../CreateBundleExtensionManifestCommand.cs | 99 - .../Bundles/CreateBurnManifestCommand.cs | 700 -- .../Bundles/CreateContainerCommand.cs | 70 - .../Bundles/CreateNonUXContainers.cs | 151 - .../Bundles/DetectPayloadCollisionsCommand.cs | 137 - .../Bundles/GetPackageFacadesCommand.cs | 181 - .../OrderPackagesAndRollbackBoundariesCommand.cs | 171 - .../Bundles/OrderSearchesCommand.cs | 367 - src/WixToolset.Core.Burn/Bundles/PackageFacade.cs | 25 - .../Bundles/ProcessExePackageCommand.cs | 39 - .../Bundles/ProcessMsiPackageCommand.cs | 558 -- .../Bundles/ProcessMspPackageCommand.cs | 183 - .../Bundles/ProcessMsuPackageCommand.cs | 37 - .../Bundles/ProcessPayloadsCommand.cs | 108 - src/WixToolset.Core.Burn/BurnBackendErrors.cs | 72 - src/WixToolset.Core.Burn/BurnBackendFactory.cs | 30 - src/WixToolset.Core.Burn/BurnBackendWarnings.cs | 36 - src/WixToolset.Core.Burn/BurnExtensionFactory.cs | 22 - .../ExtensibilityServices/BurnBackendHelper.cs | 214 - .../ExtensibilityServices/PayloadHarvester.cs | 68 - .../IInternalBurnBackendHelper.cs | 14 - src/WixToolset.Core.Burn/ISearchFacade.cs | 15 - .../Inscribe/InscribeBundleCommand.cs | 54 - .../Inscribe/InscribeBundleEngineCommand.cs | 63 - .../Interfaces/IPayloadHarvester.cs | 23 - src/WixToolset.Core.Burn/RowIndexedList.cs | 299 - .../WixToolset.Core.Burn.csproj | 39 - .../WixToolsetCoreServiceProviderExtensions.cs | 45 - .../CachedExtension.cs | 20 - .../ExtensionCacheManager.cs | 248 - .../ExtensionCacheManagerCommand.cs | 181 - .../ExtensionCacheManagerExtensionCommandLine.cs | 41 - .../ExtensionCacheManagerExtensionFactory.cs | 30 - .../WixToolset.Core.ExtensionCache.csproj | 29 - .../WixToolsetCoreServiceProviderExtensions.cs | 36 - src/WixToolset.Core.TestPackage/BundleExtractor.cs | 139 - .../ExtractBAContainerResult.cs | 116 - .../TestMessageListener.cs | 55 - src/WixToolset.Core.TestPackage/WixRunner.cs | 88 - src/WixToolset.Core.TestPackage/WixRunnerResult.cs | 62 - .../WixToolset.Core.TestPackage.csproj | 34 - .../XmlNodeExtensions.cs | 90 - .../Bind/AddBackSuppressedSequenceTablesCommand.cs | 52 - .../Bind/AddCreateFoldersCommand.cs | 38 - .../Bind/AddRequiredStandardDirectories.cs | 95 - .../Bind/AssemblyName.cs | 60 - .../Bind/AssemblyNameReader.cs | 214 - .../Bind/AssignMediaCommand.cs | 302 - .../Bind/AttachPatchTransformsCommand.cs | 1305 --- .../Bind/BindDatabaseCommand.cs | 646 -- .../Bind/BindSummaryInfoCommand.cs | 211 - .../Bind/BindTransformCommand.cs | 445 - .../Bind/CabinetBuilder.cs | 171 - .../Bind/CabinetResolver.cs | 132 - .../Bind/CabinetWorkItem.cs | 68 - .../Bind/CreateCabinetsCommand.cs | 455 -- .../Bind/CreateDeltaPatchesCommand.cs | 81 - .../Bind/CreateIdtFileCommand.cs | 250 - .../Bind/CreateInstanceTransformsCommand.cs | 260 - .../Bind/CreatePatchTransformsCommand.cs | 93 - .../Bind/CreateSpecialPropertiesCommand.cs | 83 - .../CreateWindowsInstallerDataFromIRCommand.cs | 1621 ---- .../Bind/ExtractMergeModuleFilesCommand.cs | 221 - .../Bind/FileSystemManager.cs | 77 - .../Bind/FinalizeComponentGuids.cs | 262 - .../Bind/GenerateDatabaseCommand.cs | 408 - .../Bind/GenerateTransformCommand.cs | 582 -- .../Bind/GetFileFacadesCommand.cs | 157 - .../Bind/GetFileFacadesFromTransforms.cs | 174 - .../Bind/LoadTableDefinitionsCommand.cs | 215 - .../Bind/MergeModulesCommand.cs | 331 - .../Bind/ModularizeCommand.cs | 236 - .../Bind/OptimizeFileFacadesOrderCommand.cs | 119 - .../Bind/PatchTransform.cs | 19 - .../Bind/ProcessDependencyReferencesCommand.cs | 114 - .../Bind/ProcessPackageSoftwareTagsCommand.cs | 131 - .../Bind/ProcessPropertiesCommand.cs | 101 - .../Bind/ProcessUncompressedFilesCommand.cs | 125 - .../Bind/SequenceActionsCommand.cs | 714 -- .../Bind/UpdateFileFacadesCommand.cs | 365 - .../Bind/UpdateFromTextFilesCommand.cs | 77 - .../Bind/UpdateMediaSequencesCommand.cs | 109 - .../Bind/UpdateTransformsWithFileFacades.cs | 451 -- .../Bind/ValidateDatabaseCommand.cs | 187 - .../Decompile/DecompileMsiOrMsmCommand.cs | 96 - .../Decompile/Decompiler.cs | 7596 ----------------- .../Decompile/Names.cs | 160 - src/WixToolset.Core.WindowsInstaller/Differ.cs | 610 -- .../WindowsInstallerBackendHelper.cs | 121 - .../Inscribe/InscribeMsiPackageCommand.cs | 272 - src/WixToolset.Core.WindowsInstaller/Melter.cs | 399 - src/WixToolset.Core.WindowsInstaller/MelterCore.cs | 32 - src/WixToolset.Core.WindowsInstaller/MsiBackend.cs | 85 - src/WixToolset.Core.WindowsInstaller/MsmBackend.cs | 76 - src/WixToolset.Core.WindowsInstaller/MspBackend.cs | 162 - src/WixToolset.Core.WindowsInstaller/MstBackend.cs | 44 - .../RowDictionary.cs | 71 - .../Unbind/ExtractCabinetsCommand.cs | 147 - .../Unbind/UnbindDatabaseCommand.cs | 789 -- .../Unbind/UnbindMsiOrMsmCommand.cs | 55 - .../Unbind/UnbindTranformCommand.cs | 309 - .../WindowsInstallerBackendErrors.cs | 24 - .../WindowsInstallerBackendFactory.cs | 51 - .../WindowsInstallerBackendWarnings.cs | 24 - .../WindowsInstallerExtensionFactory.cs | 22 - .../WixToolset.Core.WindowsInstaller.csproj | 30 - .../WixToolsetCoreServiceProviderExtensions.cs | 42 - src/WixToolset.Core/Bind/DelayedField.cs | 35 - src/WixToolset.Core/Bind/ExpectedExtractFile.cs | 16 - src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | 92 - .../Bind/ExtractEmbeddedFilesCommand.cs | 53 - src/WixToolset.Core/Bind/FileResolver.cs | 207 - .../Bind/ResolveDelayedFieldsCommand.cs | 164 - src/WixToolset.Core/Bind/ResolveFieldsCommand.cs | 276 - src/WixToolset.Core/Bind/TransferFilesCommand.cs | 196 - src/WixToolset.Core/BindContext.cs | 65 - src/WixToolset.Core/BindFileWithPath.cs | 22 - src/WixToolset.Core/BindPath.cs | 20 - src/WixToolset.Core/BindResult.cs | 48 - src/WixToolset.Core/Binder.cs | 96 - src/WixToolset.Core/CommandLine/BuildCommand.cs | 912 --- src/WixToolset.Core/CommandLine/CommandLine.cs | 199 - .../CommandLine/CommandLineArguments.cs | 207 - .../CommandLine/CommandLineContext.cs | 22 - .../CommandLine/CommandLineParser.cs | 270 - src/WixToolset.Core/CommandLine/CompileCommand.cs | 94 - .../CommandLine/DecompileCommand.cs | 256 - src/WixToolset.Core/CommandLine/HelpCommand.cs | 66 - src/WixToolset.Core/CommandLine/VersionCommand.cs | 26 - src/WixToolset.Core/Common.cs | 832 -- src/WixToolset.Core/Compile/CompilerPayload.cs | 291 - src/WixToolset.Core/CompileContext.cs | 34 - src/WixToolset.Core/Compiler.cs | 8514 -------------------- src/WixToolset.Core/CompilerCore.cs | 1166 --- src/WixToolset.Core/CompilerErrors.cs | 43 - src/WixToolset.Core/CompilerWarnings.cs | 65 - src/WixToolset.Core/Compiler_Bundle.cs | 3266 -------- src/WixToolset.Core/Compiler_Dependency.cs | 384 - src/WixToolset.Core/Compiler_EmbeddedUI.cs | 417 - src/WixToolset.Core/Compiler_Module.cs | 662 -- src/WixToolset.Core/Compiler_Package.cs | 4996 ------------ src/WixToolset.Core/Compiler_Patch.cs | 657 -- src/WixToolset.Core/Compiler_PatchCreation.cs | 1265 --- src/WixToolset.Core/Compiler_Tag.cs | 315 - src/WixToolset.Core/Compiler_UI.cs | 1808 ----- src/WixToolset.Core/ComponentKeyPath.cs | 25 - src/WixToolset.Core/DecompileContext.cs | 49 - src/WixToolset.Core/DecompileResult.cs | 18 - src/WixToolset.Core/Decompiler.cs | 68 - .../ExtensibilityServices/BackendHelper.cs | 176 - .../ExtensibilityServices/ExtensionManager.cs | 233 - .../ExtensibilityServices/FileFacade.cs | 172 - .../ExtensibilityServices/FileTransfer.cs | 20 - .../ExtensibilityServices/Messaging.cs | 99 - .../ExtensibilityServices/ParseHelper.cs | 863 -- .../ExtensibilityServices/PathResolver.cs | 118 - .../ExtensibilityServices/PreprocessHelper.cs | 499 -- .../ExtensibilityServices/ResolvedDirectory.cs | 15 - .../SymbolDefinitionCreator.cs | 70 - .../ExtensibilityServices/TrackedFile.cs | 26 - src/WixToolset.Core/ExtensibilityServices/Uuid.cs | 81 - .../ExtensibilityServices/WixBranding.cs | 124 - src/WixToolset.Core/IBinder.cs | 12 - src/WixToolset.Core/ICompiler.cs | 13 - src/WixToolset.Core/IDecompiler.cs | 12 - src/WixToolset.Core/ILayoutCreator.cs | 12 - src/WixToolset.Core/ILibrarian.cs | 13 - src/WixToolset.Core/ILinker.cs | 13 - src/WixToolset.Core/ILocalizationParser.cs | 27 - src/WixToolset.Core/IPreprocessor.cs | 15 - src/WixToolset.Core/IResolver.cs | 19 - src/WixToolset.Core/IUnbinder.cs | 12 - src/WixToolset.Core/IncludedFile.cs | 14 - src/WixToolset.Core/IncribeContext.cs | 26 - src/WixToolset.Core/LayoutContext.cs | 40 - src/WixToolset.Core/LayoutCreator.cs | 223 - src/WixToolset.Core/Librarian.cs | 135 - src/WixToolset.Core/LibraryContext.cs | 38 - .../Link/CollateLocalizationsCommand.cs | 71 - src/WixToolset.Core/Link/ConnectToFeature.cs | 59 - .../Link/ConnectToFeatureCollection.cs | 92 - src/WixToolset.Core/Link/ConnectToModule.cs | 54 - .../Link/ConnectToModuleCollection.cs | 92 - .../Link/FindEntrySectionAndLoadSymbolsCommand.cs | 119 - .../Link/FlattenAndProcessBundleTablesCommand.cs | 194 - .../Link/IntermediateSymbolExtensions.cs | 26 - .../Link/ReportConflictingSymbolsCommand.cs | 54 - .../Link/ResolveReferencesCommand.cs | 183 - src/WixToolset.Core/Link/SymbolWithSection.cs | 92 - .../Link/WixComplexReferenceSymbolExtensions.cs | 75 - src/WixToolset.Core/Link/WixGroupingOrdering.cs | 683 -- src/WixToolset.Core/LinkContext.cs | 33 - src/WixToolset.Core/Linker.cs | 942 --- src/WixToolset.Core/LinkerErrors.cs | 48 - src/WixToolset.Core/LinkerWarnings.cs | 36 - src/WixToolset.Core/LocalizationParser.cs | 326 - src/WixToolset.Core/ParsedWixVariable.cs | 19 - src/WixToolset.Core/Preprocess/IfContext.cs | 74 - .../Preprocess/IfDefEventHandler.cs | 28 - src/WixToolset.Core/Preprocess/IfState.cs | 22 - .../Preprocess/IncludedFileEventHandler.cs | 43 - .../Preprocess/PreprocessorOperation.cs | 19 - .../Preprocess/ProcessedStreamEventHandler.cs | 43 - .../Preprocess/ResolvedVariableEventHandler.cs | 25 - src/WixToolset.Core/PreprocessContext.cs | 35 - src/WixToolset.Core/PreprocessResult.cs | 15 - src/WixToolset.Core/Preprocessor.cs | 1520 ---- src/WixToolset.Core/Properties/AssemblyInfo.cs | 9 - src/WixToolset.Core/ResolveContext.cs | 42 - src/WixToolset.Core/ResolveFileResult.cs | 14 - src/WixToolset.Core/ResolveResult.cs | 23 - src/WixToolset.Core/ResolvedCabinet.cs | 22 - src/WixToolset.Core/Resolver.cs | 304 - src/WixToolset.Core/SourceFile.cs | 17 - src/WixToolset.Core/UnbindContext.cs | 29 - src/WixToolset.Core/Unbinder.cs | 99 - src/WixToolset.Core/VariableResolution.cs | 29 - src/WixToolset.Core/VariableResolver.cs | 197 - src/WixToolset.Core/WixToolset.Core.csproj | 47 - .../WixToolset.Core.v3.ncrunchproject | 7 - src/WixToolset.Core/WixToolsetServiceProvider.cs | 117 - .../WixToolsetServiceProviderFactory.cs | 21 - .../CompileCoreTestExtensionWixlib.csproj | 32 - src/test/CompileCoreTestExtensionWixlib/Program.cs | 37 - src/test/Example.Extension/Data/example.txt | 1 - src/test/Example.Extension/Data/example.wxs | 15 - .../Example.Extension/Example.Extension.csproj | 24 - .../Example.Extension/ExampleCompilerExtension.cs | 195 - src/test/Example.Extension/ExampleExtensionData.cs | 23 - .../Example.Extension/ExampleExtensionFactory.cs | 54 - .../ExamplePreprocessorExtensionAndCommandLine.cs | 57 - src/test/Example.Extension/ExampleRow.cs | 32 - src/test/Example.Extension/ExampleSearchSymbol.cs | 30 - src/test/Example.Extension/ExampleSymbol.cs | 30 - .../Example.Extension/ExampleSymbolDefinitions.cs | 67 - .../Example.Extension/ExampleTableDefinitions.cs | 34 - .../ExampleWindowsInstallerBackendExtension.cs | 33 - .../WixToolsetTest.Core.Burn/BurnReaderFixture.cs | 44 - .../WixToolsetTest.Core.Burn.csproj | 28 - .../ApprovedExeFixture.cs | 64 - .../BadInputFixture.cs | 148 - .../BindVariablesFixture.cs | 96 - .../BootstrapperApplicationFixture.cs | 46 - .../BundleExtractionFixture.cs | 58 - .../BundleFixture.cs | 478 -- .../BundleManifestFixture.cs | 365 - .../WixToolsetTest.CoreIntegration/CabFixture.cs | 107 - .../ComponentFixture.cs | 45 - .../ContainerFixture.cs | 385 - .../CopyFileFixture.cs | 48 - .../CustomActionFixture.cs | 169 - .../CustomTableFixture.cs | 234 - .../DecompileFixture.cs | 86 - .../DependencyExtensionFixture.cs | 180 - .../DirectoryFixture.cs | 271 - .../ExePackageFixture.cs | 52 - .../ExtensionFixture.cs | 153 - .../LanguageFixture.cs | 174 - .../LinkerFixture.cs | 174 - .../WixToolsetTest.CoreIntegration/MediaFixture.cs | 62 - .../ModuleFixture.cs | 113 - .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 838 -- .../MsiQueryFixture.cs | 1040 --- .../MsiTransactionFixture.cs | 131 - .../MsuPackageFixture.cs | 36 - .../PackagePayloadFixture.cs | 211 - .../WixToolsetTest.CoreIntegration/ParseFixture.cs | 36 - .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 279 - .../PayloadFixture.cs | 212 - .../PreprocessorFixture.cs | 181 - .../RegistryFixture.cs | 173 - .../RollbackBoundaryFixture.cs | 41 - .../ShortcutFixture.cs | 78 - .../SoftwareTagFixture.cs | 100 - .../TestData/.Data/burn.exe | Bin 463360 -> 0 bytes .../TestData/AppId/Advertised.wxs | 11 - .../TestData/AppSearch/ComponentSearch.wxs | 12 - .../DecompiledNestedDirSearchUnderRegSearch.wxs | 42 - .../TestData/AppSearch/DirectorySearch.wxs | 12 - .../TestData/AppSearch/FileSearch.wxs | 14 - .../AppSearch/NestedDirSearchUnderRegSearch.msi | Bin 33045 -> 0 bytes .../TestData/AppSearch/RegistrySearch.wxs | 12 - .../TestData/AppSearch/RegistrySearch64.wxs | 12 - .../TestData/Assembly/Package.en-us.wxl | 11 - .../TestData/Assembly/Package.wxs | 17 - .../TestData/Assembly/PackageComponents.wxs | 10 - .../TestData/Assembly/Win32Assembly.wxs | 13 - .../TestData/Assembly/data/candle.exe | Bin 28672 -> 0 bytes .../TestData/Assembly/data/test.manifest | 76 - .../TestData/BadEnsureTable/BadEnsureTable.wxs | 11 - .../TestData/BadIf/Package.en-us.wxl | 11 - .../TestData/BadIf/Package.wxs | 24 - .../TestData/BadIf/PackageComponents.wxs | 12 - .../TestData/BadIf/data/test.txt | 1 - .../TestData/BadInput/BundleVariable.wxs | 6 - .../TestData/BadInput/DuplicateCacheIds.wxs | 12 - .../TestData/BadInput/DuplicatePayloadNames.wxs | 31 - .../BadInput/HiddenPersistedBundleVariable.wxs | 6 - .../TestData/BadInput/InvalidIds.wxs | 8 - .../TestData/BadInput/RegistryKey.wxs | 13 - .../TestData/BadInput/UnscheduledPackage.wxs | 16 - .../BadInput/UnscheduledRollbackBoundary.wxs | 16 - .../TestData/BindVariables/DefaultedVariable.wxs | 6 - .../TestData/BindVariables/data/test.txt | 1 - .../BootstrapperApplication/DpiAwareness.wxs | 7 - .../CacheIdFromPackageDescription.wxs | 8 - .../BundleCustomTable/BundleCustomTable.wxs | 53 - .../TestData/BundleExtension/BundleExtension.wxs | 6 - .../BundleExtension/BundleExtensionSearches.wxs | 8 - .../BundleExtension/BundleWithSearches.wxs | 11 - .../BundleExtension/SimpleBundleExtension.wxs | 10 - .../TestData/BundleTag/BundleWithTag.wxs | 15 - .../TestData/BundleTag/fakeba.dll | 1 - .../TestData/BundleWithApprovedExe/Bundle.wxs | 5 - .../TestData/BundleWithApprovedExe/Bundle64.wxs | 5 - .../BundleWithDetachedContainer/Bundle.wxs | 10 - .../TestData/BundleWithPackageGroupRef/Bundle.wxs | 10 - .../MinimalPackageGroup.wxs | 8 - .../TestData/Class/DecompiledOldClassTableDef.wxs | 22 - .../TestData/Class/IconIndex0.wxs | 11 - .../TestData/Class/OldClassTableDef.msi | Bin 36864 -> 0 bytes .../ComplexExampleExtension/OtherComponents.wxs | 12 - .../ComplexExampleExtension/Package.en-us.wxl | 11 - .../TestData/ComplexExampleExtension/Package.wxs | 22 - .../ComplexExampleExtension/PackageComponents.wxs | 12 - .../ComplexExampleExtension/data/example.txt | 1 - .../ComplexExampleExtension/data/other.txt | 1 - .../TestData/Component/GuidCollision.wxs | 14 - .../TestData/Components/Package.en-us.wxl | 11 - .../TestData/Components/Package.wxs | 17 - .../TestData/Components/PackageComponents.wxs | 10 - .../TestData/Components/data/test.txt | 1 - .../Container/HarvestIntoDetachedContainer.wxs | 15 - .../Container/MultipleAttachedContainers.wxs | 15 - .../TestData/CopyFile/CopyFile.wxs | 17 - .../TestData/CustomAction/CustomActionCycle.wxs | 18 - .../CustomAction/CustomActionCycleWithTail.wxs | 20 - .../TestData/CustomAction/SimpleCustomAction.wxs | 14 - .../CustomAction/UnscheduledCustomAction.wxs | 32 - .../CustomPackageDescription.wxs | 12 - .../TestData/CustomTable/CustomTable-Expected.wxs | 29 - .../TestData/CustomTable/CustomTable.wxs | 34 - .../TestData/CustomTable/CustomTableWithFile.wxs | 22 - .../CustomTable/LocalizedCustomTable.en-us.wxl | 7 - .../TestData/CustomTable/LocalizedCustomTable.wxs | 21 - .../TestData/CustomTable/data/file1.txt | 1 - .../TestData/CustomTable/data/file2.txt | 1 - .../TestData/CustomTable/data/test.txt | 1 - .../TestData/DecompileNullComponent/Expected.wxs | 16 - .../TestData/DecompileNullComponent/example.cab | Bin 137 -> 0 bytes .../TestData/DecompileNullComponent/example.msi | Bin 32768 -> 0 bytes .../DecompileSingleFileCompressed/Expected.wxs | 16 - .../DecompileSingleFileCompressed/example.cab | Bin 137 -> 0 bytes .../DecompileSingleFileCompressed/example.msi | Bin 32768 -> 0 bytes .../DecompileSingleFileCompressed64/Expected.wxs | 16 - .../DecompileSingleFileCompressed64/example.cab | Bin 137 -> 0 bytes .../DecompileSingleFileCompressed64/example.msi | Bin 32768 -> 0 bytes .../DecompileTargetDirMergeModule/Expected.wxs | 18 - .../DecompileTargetDirMergeModule/MergeModule1.msm | Bin 32768 -> 0 bytes .../TestData/DefaultDir/DefaultDir.wxs | 26 - .../Dependency/CustomProviderKeyBundle.wxs | 10 - .../Dependency/ExePackageProvidesBundle.wxs | 10 - .../TestData/Dependency/UsingProvidesBundle.wxs | 8 - .../PackageComponents.wxs | 36 - .../TestData/Directory/DefaultName.wxs | 13 - .../Directory/DuplicateTargetSourceName.wxs | 11 - .../TestData/Directory/Empty.wxs | 6 - .../TestData/Directory/Nested.wxs | 11 - .../TestData/DuplicateDir/DuplicateDir.wxs | 25 - .../TestData/EnsureTable/EnsureTable.wxs | 10 - .../TestData/Environment/Environment.wxs | 14 - .../TestData/ErrorsInUI/Package.en-us.wxl | 9 - .../TestData/ErrorsInUI/Package.wxs | 20 - .../TestData/ErrorsInUI/PackageComponents.wxs | 14 - .../TestData/ErrorsInUI/data/test.txt | 1 - .../TestData/ExampleExtension/Package.en-us.wxl | 11 - .../TestData/ExampleExtension/Package.wxs | 21 - .../ExampleExtension/PackageComponents.wxs | 12 - .../TestData/ExampleExtension/data/example.txt | 1 - .../TestData/ExePackage/MissingDetectCondition.wxs | 9 - .../TestData/ExePackage/RequireDetectCondition.wxs | 11 - .../TestData/FeatureGroup/FeatureGroup.wxs | 14 - .../TestData/Font/FontTitle.wxs | 10 - .../TestData/Font/TrueType.wxs | 10 - .../TestData/ForEach/Package.en-us.wxl | 11 - .../TestData/ForEach/Package.wxs | 19 - .../TestData/ForEach/PackageComponents.wxs | 12 - .../TestData/ForEach/data/test.txt | 1 - .../TestData/Icon/SampleIcon.wxs | 6 - .../TestData/IncludePath/Package.en-us.wxl | 11 - .../TestData/IncludePath/Package.wxs | 18 - .../TestData/IncludePath/PackageComponents.wxs | 8 - .../TestData/IncludePath/data/DontDoThis.wxi | 6 - .../TestData/IncludePath/data/Package.wxi | 4 - .../TestData/IncludePath/data/test.txt | 1 - .../TestData/InstanceTransform/Package.en-us.wxl | 11 - .../TestData/InstanceTransform/Package.wxs | 23 - .../InstanceTransform/PackageComponents.wxs | 10 - .../TestData/InstanceTransform/data/test.txt | 1 - .../TestData/Language/Package.en-us.wxl | 7 - .../TestData/Language/Package.ja-jp.wxl | 7 - .../TestData/Language/Package.wxl | 7 - .../TestData/Language/Package.wxs | 18 - .../Language/PackageWithEnSummaryInfo.ja-jp.wxl | 7 - .../TestData/Language/data/test.txt | 1 - .../TestData/LockPermissions/EmptyPermissions.wxs | 13 - .../TestData/ManualUpgrade/Package.en-us.wxl | 11 - .../TestData/ManualUpgrade/Package.wxs | 24 - .../TestData/ManualUpgrade/PackageComponents.wxs | 10 - .../TestData/ManualUpgrade/data/test.txt | 1 - .../TestData/Media/MultiMedia.wxs | 28 - .../TestData/Media/data/a1.txt | 1 - .../TestData/Media/data/a2.txt | 1 - .../TestData/Media/data/b1.txt | 1 - .../TestData/Media/data/b2.txt | 1 - .../TestData/MsiTransaction/FirstX64.wxs | 8 - .../TestData/MsiTransaction/FirstX86.wxs | 8 - .../TestData/MsiTransaction/SecondX64.wxs | 8 - .../TestData/MsiTransaction/SecondX86.wxs | 8 - .../TestData/MsiTransaction/X64AfterX86Bundle.wxs | 12 - .../TestData/MsiTransaction/X86AfterX64Bundle.wxs | 12 - .../TestData/MsuPackage/Bundle.wxs | 11 - .../TestData/MsuPackage/data/fakeba.dll | 1 - .../TestData/MsuPackage/data/test.msu | 1 - .../TestData/MultiFileCompressed/Package.en-us.wxl | 11 - .../TestData/MultiFileCompressed/Package.wxs | 26 - .../MultiFileCompressed/PackageComponents.wxs | 13 - .../TestData/MultiFileCompressed/data/test.txt | 1 - .../TestData/OverridableActions/Package.en-us.wxl | 11 - .../TestData/OverridableActions/Package.wxs | 48 - .../OverridableActions/PackageComponents.wxs | 10 - .../TestData/OverridableActions/data/test.txt | 1 - .../PackagePayload/MissingSourceFileAndHash.wxs | 10 - .../PackagePayload/MissingSourceFileAndName.wxs | 10 - .../PackagePayloadInPayloadGroup.wxs | 15 - .../TestData/PackagePayload/SpecifiedHash.wxs | 10 - .../SpecifiedHashAndMissingDownloadUrl.wxs | 10 - .../PackagePayload/SpecifiedSourceFileAndHash.wxs | 10 - .../WrongPackagePayloadInPayloadGroup.wxs | 15 - .../TestData/PatchFamilyFilter/.data/Av1.0.0.txt | 1 - .../TestData/PatchFamilyFilter/.data/Av1.0.1.txt | 1 - .../TestData/PatchFamilyFilter/.data/Bv1.0.0.txt | 1 - .../TestData/PatchFamilyFilter/.data/Bv1.0.1.txt | 1 - .../TestData/PatchFamilyFilter/Package.wxs | 28 - .../TestData/PatchFamilyFilter/Patch.wxs | 16 - .../TestData/PatchFromWixlib/Package.wxs | 30 - .../TestData/PatchFromWixlib/Patch.wxs | 16 - .../TestData/PatchNoFileChanges/.data/A.txt | 1 - .../TestData/PatchNoFileChanges/Package.wxs | 27 - .../TestData/PatchNoFileChanges/Patch.wxs | 16 - .../TestData/PatchNonSpecific/BundleA/Bundle.wxs | 7 - .../TestData/PatchNonSpecific/BundleB/Bundle.wxs | 8 - .../TestData/PatchNonSpecific/BundleC/Bundle.wxs | 9 - .../TestData/PatchNonSpecific/PackageA/Package.wxs | 44 - .../TestData/PatchNonSpecific/PatchA/Patch.wxs | 12 - .../TestData/PatchNonSpecific/PatchB/Patch.wxs | 14 - .../TestData/PatchNonSpecific/PatchC/Patch.wxs | 14 - .../TestData/PatchSingle/.data/Av1.0.0.txt | 1 - .../TestData/PatchSingle/.data/Av1.0.1.txt | 1 - .../TestData/PatchSingle/.data/Bv1.0.0.txt | 1 - .../TestData/PatchSingle/.data/Bv1.0.1.txt | 1 - .../TestData/PatchSingle/BundleA/Bundle.wxs | 10 - .../TestData/PatchSingle/Package.wxs | 27 - .../TestData/PatchSingle/Patch.wxs | 16 - .../TestData/Payload/AbsoluteName.wxs | 9 - .../TestData/Payload/CanonicalizeName.wxs | 7 - .../Payload/DownloadUrlPlaceholdersBundle.wxs | 30 - .../Payload/SharedBAAndPackagePayloadBundle.wxs | 16 - .../TestData/Payload/ValidName.wxs | 7 - .../TestData/Preprocessor/EnvParens.wxs | 4 - .../TestData/ProductTag/Package.en-us.wxl | 11 - .../TestData/ProductTag/PackageComponents.wxs | 10 - .../TestData/ProductTag/PackageWithTag.wxs | 18 - .../TestData/ProductTag/example.txt | 1 - .../MinimalComponentGroup.wxs | 10 - .../ProductWithComponentGroupRef/Product.wxs | 19 - .../TestData/ProgId/NestedUnderClass.wxs | 13 - .../TestData/ProgId/Package.en-us.wxl | 11 - .../TestData/ProgId/Package.wxs | 17 - .../TestData/ProgId/PackageComponents.wxs | 16 - .../TestData/ProgId/data/test.txt | 1 - .../TestData/PublishComponent/Package.en-us.wxl | 11 - .../TestData/PublishComponent/Package.wxs | 34 - .../TestData/PublishComponent/data/test.txt | 1 - .../Registry/DuplicateRegistryValueIds.wxs | 14 - .../Registry/RegistryKeyEndingWithBackslash.wxs | 12 - .../TestData/Registry/RegistryValue.wxs | 11 - .../TestData/Registry/RegistryValueMultiString.wxs | 17 - .../TestData/Registry/RemoveRegistryKey.wxs | 11 - .../TestData/ReserveCost/ReserveCost.wxs | 11 - .../TestData/RollbackBoundary/BeginningOfChain.wxs | 9 - .../TestData/SameFileFolders/TestComponents.wxs | 16 - .../TestData/SameFileFolders/data/a/test.txt | 1 - .../TestData/SameFileFolders/data/b/test.txt | 1 - .../TestData/SameFileFolders/data/c/test.txt | 1 - .../SequenceTables/DecompiledSequenceTables.wxs | 32 - .../TestData/SequenceTables/SequenceTables.msi | Bin 32768 -> 0 bytes .../TestData/ServiceInstall/OwnProcess.wxs | 12 - .../TestData/SetProperty/Package.en-us.wxl | 11 - .../TestData/SetProperty/Package.wxs | 19 - .../TestData/SetProperty/PackageComponents.wxs | 10 - .../TestData/SetProperty/data/test.txt | 1 - .../TestData/SetVariable/Simple.wxs | 15 - .../SharedPayloadsBetweenPackages.wxs | 18 - .../TestData/Shortcut/DecompiledShortcuts.wxs | 19 - .../TestData/Shortcut/ShortcutProperty.wxs | 14 - .../Shortcut/ShortcutSameNameShortName.wxs | 12 - .../TestData/Shortcut/shortcuts.msi | Bin 32768 -> 0 bytes .../TestData/SimpleBundle/Bundle.en-us.wxl | 10 - .../TestData/SimpleBundle/Bundle.wxs | 12 - .../MultiFileBootstrapperApplication.wxs | 7 - .../TestData/SimpleBundle/MultiFileBundle.wxs | 18 - .../SimpleBundle/data/MsiPackage/Shared.dll | 1 - .../TestData/SimpleBundle/data/MsiPackage/test.txt | 1 - .../TestData/SimpleBundle/data/fakeba.dll | 1 - .../TestData/SimpleBundle/data/test.msi | Bin 32768 -> 0 bytes .../TestData/SimpleMerge/.data/test.msm | Bin 24576 -> 0 bytes .../TestData/SimpleMerge/Package.en-us.wxl | 11 - .../TestData/SimpleMerge/Package.wxs | 20 - .../TestData/SimpleModule/Module.en-us.wxl | 10 - .../TestData/SimpleModule/Module.wixproj | 48 - .../TestData/SimpleModule/Module.wxs | 17 - .../TestData/SimpleModule/data/test.txt | 1 - .../SingleExeBundle/SingleExePackageGroup.wxs | 8 - .../SingleExeBundle/SingleExeRemotePayload.wxs | 27 - .../TestData/SingleFile/Package.en-us.wxl | 11 - .../TestData/SingleFile/Package.wxs | 17 - .../TestData/SingleFile/PackageComponents.wxs | 13 - .../TestData/SingleFile/data/test.txt | 1 - .../SingleFileCompressed/Package.en-us.wxl | 11 - .../TestData/SingleFileCompressed/Package.wxs | 25 - .../SingleFileCompressed/PackageComponents.wxs | 10 - .../TestData/SingleFileCompressed/data/test.txt | 1 - .../SuppressModularization/Module.en-us.wxl | 10 - .../TestData/SuppressModularization/Module.wxs | 10 - .../TestData/SuppressModularization/data/test.txt | 1 - .../TestData/TextStyle/ColorNull.wxs | 12 - .../TestData/TextStyle/SizeLocalized.en-us.wxl | 13 - .../TestData/TextStyle/SizeLocalized.wxs | 12 - .../TestData/TypeLib/Language0.wxs | 11 - .../TestData/Upgrade/DetectOnly.wxs | 12 - .../TestData/UsingProvides/Package.en-us.wxl | 11 - .../TestData/UsingProvides/Package.wxs | 16 - .../TestData/UsingProvides/PackageComponents.wxs | 10 - .../TestData/UsingProvides/example.txt | 1 - .../TestData/Variables/Package.en-us.wxl | 11 - .../TestData/Variables/Package.wxs | 31 - .../TestData/Variables/PackageComponents.wxs | 10 - .../TestData/Variables/data/test.txt | 1 - .../TestData/WixVariableOverride/Package.en-us.wxl | 11 - .../TestData/WixVariableOverride/Package.wxs | 19 - .../WixVariableOverride/PackageComponents.wxs | 14 - .../TestData/WixVariableOverride/data/test.txt | 1 - .../TestData/WixVariableOverride/data/test2.txt | 1 - .../TestData/Wixipl/Package.en-us.wxl | 11 - .../TestData/Wixipl/Package.wxs | 19 - .../TestData/Wixipl/PackageComponents.wxs | 10 - .../TestData/Wixipl/data/test.txt | 1 - .../TestData/WixlibWithBinaries/Package.en-us.wxl | 11 - .../TestData/WixlibWithBinaries/Package.wxs | 19 - .../WixlibWithBinaries/PackageComponents.wxs | 26 - .../TestData/WixlibWithBinaries/data/alpha/foo.dll | 1 - .../TestData/WixlibWithBinaries/data/mips/foo.dll | 1 - .../WixlibWithBinaries/data/powerpc/foo.dll | 1 - .../TestData/WixlibWithBinaries/data/test.txt | 1 - .../TestXmlFixture.cs | 62 - .../VariableResolverFixture.cs | 75 - .../WarningFixture.cs | 63 - .../WixToolsetTest.CoreIntegration.csproj | 32 - .../WixiplFixture.cs | 205 - .../WixlibFixture.cs | 316 - .../WixlibQueryFixture.cs | 81 - src/version.json | 11 + src/wix/Custom.Build.props | 6 + src/wix/Directory.Build.props | 27 + src/wix/Directory.Build.targets | 51 + src/wix/Directory.csproj.props | 13 + src/wix/Directory.csproj.targets | 26 + src/wix/README.md | 3 + .../WixToolset.Core.Burn/Bind/BaseSearchFacade.cs | 27 + .../WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 650 ++ .../Bind/ExtensionSearchFacade.cs | 24 + .../Bind/GenerateManifestDataFromIRCommand.cs | 237 + .../Bind/LegacySearchFacade.cs | 185 + .../Bind/ProcessBundleSoftwareTagsCommand.cs | 133 + .../Bind/ProcessDependencyProvidersCommand.cs | 147 + .../Bind/ResolveDownloadUrlsCommand.cs | 128 + .../Bind/SetVariableSearchFacade.cs | 48 + src/wix/WixToolset.Core.Burn/BundleBackend.cs | 77 + .../AutomaticallySlipstreamPatchesCommand.cs | 117 + .../Bundles/BundleHashAlgorithm.cs | 30 + src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs | 385 + src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs | 212 + src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs | 245 + ...CreateBootstrapperApplicationManifestCommand.cs | 290 + .../Bundles/CreateBundleExeCommand.cs | 325 + .../CreateBundleExtensionManifestCommand.cs | 99 + .../Bundles/CreateBurnManifestCommand.cs | 700 ++ .../Bundles/CreateContainerCommand.cs | 70 + .../Bundles/CreateNonUXContainers.cs | 151 + .../Bundles/DetectPayloadCollisionsCommand.cs | 137 + .../Bundles/GetPackageFacadesCommand.cs | 181 + .../OrderPackagesAndRollbackBoundariesCommand.cs | 171 + .../Bundles/OrderSearchesCommand.cs | 367 + .../WixToolset.Core.Burn/Bundles/PackageFacade.cs | 25 + .../Bundles/ProcessExePackageCommand.cs | 39 + .../Bundles/ProcessMsiPackageCommand.cs | 558 ++ .../Bundles/ProcessMspPackageCommand.cs | 183 + .../Bundles/ProcessMsuPackageCommand.cs | 37 + .../Bundles/ProcessPayloadsCommand.cs | 108 + src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs | 72 + src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs | 30 + .../WixToolset.Core.Burn/BurnBackendWarnings.cs | 36 + .../WixToolset.Core.Burn/BurnExtensionFactory.cs | 22 + .../ExtensibilityServices/BurnBackendHelper.cs | 214 + .../ExtensibilityServices/PayloadHarvester.cs | 68 + .../IInternalBurnBackendHelper.cs | 14 + src/wix/WixToolset.Core.Burn/ISearchFacade.cs | 15 + .../Inscribe/InscribeBundleCommand.cs | 54 + .../Inscribe/InscribeBundleEngineCommand.cs | 63 + .../Interfaces/IPayloadHarvester.cs | 23 + src/wix/WixToolset.Core.Burn/RowIndexedList.cs | 299 + .../WixToolset.Core.Burn.csproj | 39 + .../WixToolsetCoreServiceProviderExtensions.cs | 45 + .../CachedExtension.cs | 20 + .../ExtensionCacheManager.cs | 248 + .../ExtensionCacheManagerCommand.cs | 181 + .../ExtensionCacheManagerExtensionCommandLine.cs | 41 + .../ExtensionCacheManagerExtensionFactory.cs | 30 + .../WixToolset.Core.ExtensionCache.csproj | 29 + .../WixToolsetCoreServiceProviderExtensions.cs | 36 + .../WixToolset.Core.TestPackage/BundleExtractor.cs | 139 + .../ExtractBAContainerResult.cs | 116 + .../TestMessageListener.cs | 55 + src/wix/WixToolset.Core.TestPackage/WixRunner.cs | 88 + .../WixToolset.Core.TestPackage/WixRunnerResult.cs | 62 + .../WixToolset.Core.TestPackage.csproj | 34 + .../XmlNodeExtensions.cs | 90 + .../Bind/AddBackSuppressedSequenceTablesCommand.cs | 52 + .../Bind/AddCreateFoldersCommand.cs | 38 + .../Bind/AddRequiredStandardDirectories.cs | 95 + .../Bind/AssemblyName.cs | 60 + .../Bind/AssemblyNameReader.cs | 214 + .../Bind/AssignMediaCommand.cs | 302 + .../Bind/AttachPatchTransformsCommand.cs | 1305 +++ .../Bind/BindDatabaseCommand.cs | 646 ++ .../Bind/BindSummaryInfoCommand.cs | 211 + .../Bind/BindTransformCommand.cs | 445 + .../Bind/CabinetBuilder.cs | 171 + .../Bind/CabinetResolver.cs | 132 + .../Bind/CabinetWorkItem.cs | 68 + .../Bind/CreateCabinetsCommand.cs | 455 ++ .../Bind/CreateDeltaPatchesCommand.cs | 81 + .../Bind/CreateIdtFileCommand.cs | 250 + .../Bind/CreateInstanceTransformsCommand.cs | 260 + .../Bind/CreatePatchTransformsCommand.cs | 93 + .../Bind/CreateSpecialPropertiesCommand.cs | 83 + .../CreateWindowsInstallerDataFromIRCommand.cs | 1621 ++++ .../Bind/ExtractMergeModuleFilesCommand.cs | 221 + .../Bind/FileSystemManager.cs | 77 + .../Bind/FinalizeComponentGuids.cs | 262 + .../Bind/GenerateDatabaseCommand.cs | 408 + .../Bind/GenerateTransformCommand.cs | 582 ++ .../Bind/GetFileFacadesCommand.cs | 157 + .../Bind/GetFileFacadesFromTransforms.cs | 174 + .../Bind/LoadTableDefinitionsCommand.cs | 215 + .../Bind/MergeModulesCommand.cs | 331 + .../Bind/ModularizeCommand.cs | 236 + .../Bind/OptimizeFileFacadesOrderCommand.cs | 119 + .../Bind/PatchTransform.cs | 19 + .../Bind/ProcessDependencyReferencesCommand.cs | 114 + .../Bind/ProcessPackageSoftwareTagsCommand.cs | 131 + .../Bind/ProcessPropertiesCommand.cs | 101 + .../Bind/ProcessUncompressedFilesCommand.cs | 125 + .../Bind/SequenceActionsCommand.cs | 714 ++ .../Bind/UpdateFileFacadesCommand.cs | 365 + .../Bind/UpdateFromTextFilesCommand.cs | 77 + .../Bind/UpdateMediaSequencesCommand.cs | 109 + .../Bind/UpdateTransformsWithFileFacades.cs | 451 ++ .../Bind/ValidateDatabaseCommand.cs | 187 + .../Decompile/DecompileMsiOrMsmCommand.cs | 96 + .../Decompile/Decompiler.cs | 7596 +++++++++++++++++ .../Decompile/Names.cs | 160 + src/wix/WixToolset.Core.WindowsInstaller/Differ.cs | 610 ++ .../WindowsInstallerBackendHelper.cs | 121 + .../Inscribe/InscribeMsiPackageCommand.cs | 272 + src/wix/WixToolset.Core.WindowsInstaller/Melter.cs | 399 + .../WixToolset.Core.WindowsInstaller/MelterCore.cs | 32 + .../WixToolset.Core.WindowsInstaller/MsiBackend.cs | 85 + .../WixToolset.Core.WindowsInstaller/MsmBackend.cs | 76 + .../WixToolset.Core.WindowsInstaller/MspBackend.cs | 162 + .../WixToolset.Core.WindowsInstaller/MstBackend.cs | 44 + .../RowDictionary.cs | 71 + .../Unbind/ExtractCabinetsCommand.cs | 147 + .../Unbind/UnbindDatabaseCommand.cs | 789 ++ .../Unbind/UnbindMsiOrMsmCommand.cs | 55 + .../Unbind/UnbindTranformCommand.cs | 309 + .../WindowsInstallerBackendErrors.cs | 24 + .../WindowsInstallerBackendFactory.cs | 51 + .../WindowsInstallerBackendWarnings.cs | 24 + .../WindowsInstallerExtensionFactory.cs | 22 + .../WixToolset.Core.WindowsInstaller.csproj | 30 + .../WixToolsetCoreServiceProviderExtensions.cs | 42 + src/wix/WixToolset.Core.sln | 156 + src/wix/WixToolset.Core.v3.ncrunchsolution | 6 + src/wix/WixToolset.Core/Bind/DelayedField.cs | 35 + .../WixToolset.Core/Bind/ExpectedExtractFile.cs | 16 + .../WixToolset.Core/Bind/ExtractEmbeddedFiles.cs | 92 + .../Bind/ExtractEmbeddedFilesCommand.cs | 53 + src/wix/WixToolset.Core/Bind/FileResolver.cs | 207 + .../Bind/ResolveDelayedFieldsCommand.cs | 164 + .../WixToolset.Core/Bind/ResolveFieldsCommand.cs | 276 + .../WixToolset.Core/Bind/TransferFilesCommand.cs | 196 + src/wix/WixToolset.Core/BindContext.cs | 65 + src/wix/WixToolset.Core/BindFileWithPath.cs | 22 + src/wix/WixToolset.Core/BindPath.cs | 20 + src/wix/WixToolset.Core/BindResult.cs | 48 + src/wix/WixToolset.Core/Binder.cs | 96 + .../WixToolset.Core/CommandLine/BuildCommand.cs | 912 +++ src/wix/WixToolset.Core/CommandLine/CommandLine.cs | 199 + .../CommandLine/CommandLineArguments.cs | 207 + .../CommandLine/CommandLineContext.cs | 22 + .../CommandLine/CommandLineParser.cs | 270 + .../WixToolset.Core/CommandLine/CompileCommand.cs | 94 + .../CommandLine/DecompileCommand.cs | 256 + src/wix/WixToolset.Core/CommandLine/HelpCommand.cs | 66 + .../WixToolset.Core/CommandLine/VersionCommand.cs | 26 + src/wix/WixToolset.Core/Common.cs | 832 ++ src/wix/WixToolset.Core/Compile/CompilerPayload.cs | 291 + src/wix/WixToolset.Core/CompileContext.cs | 34 + src/wix/WixToolset.Core/Compiler.cs | 8514 ++++++++++++++++++++ src/wix/WixToolset.Core/CompilerCore.cs | 1166 +++ src/wix/WixToolset.Core/CompilerErrors.cs | 43 + src/wix/WixToolset.Core/CompilerWarnings.cs | 65 + src/wix/WixToolset.Core/Compiler_Bundle.cs | 3266 ++++++++ src/wix/WixToolset.Core/Compiler_Dependency.cs | 384 + src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs | 417 + src/wix/WixToolset.Core/Compiler_Module.cs | 662 ++ src/wix/WixToolset.Core/Compiler_Package.cs | 4996 ++++++++++++ src/wix/WixToolset.Core/Compiler_Patch.cs | 657 ++ src/wix/WixToolset.Core/Compiler_PatchCreation.cs | 1265 +++ src/wix/WixToolset.Core/Compiler_Tag.cs | 315 + src/wix/WixToolset.Core/Compiler_UI.cs | 1808 +++++ src/wix/WixToolset.Core/ComponentKeyPath.cs | 25 + src/wix/WixToolset.Core/DecompileContext.cs | 49 + src/wix/WixToolset.Core/DecompileResult.cs | 18 + src/wix/WixToolset.Core/Decompiler.cs | 68 + .../ExtensibilityServices/BackendHelper.cs | 176 + .../ExtensibilityServices/ExtensionManager.cs | 233 + .../ExtensibilityServices/FileFacade.cs | 172 + .../ExtensibilityServices/FileTransfer.cs | 20 + .../ExtensibilityServices/Messaging.cs | 99 + .../ExtensibilityServices/ParseHelper.cs | 863 ++ .../ExtensibilityServices/PathResolver.cs | 118 + .../ExtensibilityServices/PreprocessHelper.cs | 499 ++ .../ExtensibilityServices/ResolvedDirectory.cs | 15 + .../SymbolDefinitionCreator.cs | 70 + .../ExtensibilityServices/TrackedFile.cs | 26 + .../WixToolset.Core/ExtensibilityServices/Uuid.cs | 81 + .../ExtensibilityServices/WixBranding.cs | 124 + src/wix/WixToolset.Core/IBinder.cs | 12 + src/wix/WixToolset.Core/ICompiler.cs | 13 + src/wix/WixToolset.Core/IDecompiler.cs | 12 + src/wix/WixToolset.Core/ILayoutCreator.cs | 12 + src/wix/WixToolset.Core/ILibrarian.cs | 13 + src/wix/WixToolset.Core/ILinker.cs | 13 + src/wix/WixToolset.Core/ILocalizationParser.cs | 27 + src/wix/WixToolset.Core/IPreprocessor.cs | 15 + src/wix/WixToolset.Core/IResolver.cs | 19 + src/wix/WixToolset.Core/IUnbinder.cs | 12 + src/wix/WixToolset.Core/IncludedFile.cs | 14 + src/wix/WixToolset.Core/IncribeContext.cs | 26 + src/wix/WixToolset.Core/LayoutContext.cs | 40 + src/wix/WixToolset.Core/LayoutCreator.cs | 223 + src/wix/WixToolset.Core/Librarian.cs | 135 + src/wix/WixToolset.Core/LibraryContext.cs | 38 + .../Link/CollateLocalizationsCommand.cs | 71 + src/wix/WixToolset.Core/Link/ConnectToFeature.cs | 59 + .../Link/ConnectToFeatureCollection.cs | 92 + src/wix/WixToolset.Core/Link/ConnectToModule.cs | 54 + .../Link/ConnectToModuleCollection.cs | 92 + .../Link/FindEntrySectionAndLoadSymbolsCommand.cs | 119 + .../Link/FlattenAndProcessBundleTablesCommand.cs | 194 + .../Link/IntermediateSymbolExtensions.cs | 26 + .../Link/ReportConflictingSymbolsCommand.cs | 54 + .../Link/ResolveReferencesCommand.cs | 183 + src/wix/WixToolset.Core/Link/SymbolWithSection.cs | 92 + .../Link/WixComplexReferenceSymbolExtensions.cs | 75 + .../WixToolset.Core/Link/WixGroupingOrdering.cs | 683 ++ src/wix/WixToolset.Core/LinkContext.cs | 33 + src/wix/WixToolset.Core/Linker.cs | 942 +++ src/wix/WixToolset.Core/LinkerErrors.cs | 48 + src/wix/WixToolset.Core/LinkerWarnings.cs | 36 + src/wix/WixToolset.Core/LocalizationParser.cs | 326 + src/wix/WixToolset.Core/ParsedWixVariable.cs | 19 + src/wix/WixToolset.Core/Preprocess/IfContext.cs | 74 + .../Preprocess/IfDefEventHandler.cs | 28 + src/wix/WixToolset.Core/Preprocess/IfState.cs | 22 + .../Preprocess/IncludedFileEventHandler.cs | 43 + .../Preprocess/PreprocessorOperation.cs | 19 + .../Preprocess/ProcessedStreamEventHandler.cs | 43 + .../Preprocess/ResolvedVariableEventHandler.cs | 25 + src/wix/WixToolset.Core/PreprocessContext.cs | 35 + src/wix/WixToolset.Core/PreprocessResult.cs | 15 + src/wix/WixToolset.Core/Preprocessor.cs | 1520 ++++ src/wix/WixToolset.Core/Properties/AssemblyInfo.cs | 9 + src/wix/WixToolset.Core/ResolveContext.cs | 42 + src/wix/WixToolset.Core/ResolveFileResult.cs | 14 + src/wix/WixToolset.Core/ResolveResult.cs | 23 + src/wix/WixToolset.Core/ResolvedCabinet.cs | 22 + src/wix/WixToolset.Core/Resolver.cs | 304 + src/wix/WixToolset.Core/SourceFile.cs | 17 + src/wix/WixToolset.Core/UnbindContext.cs | 29 + src/wix/WixToolset.Core/Unbinder.cs | 99 + src/wix/WixToolset.Core/VariableResolution.cs | 29 + src/wix/WixToolset.Core/VariableResolver.cs | 197 + src/wix/WixToolset.Core/WixToolset.Core.csproj | 47 + .../WixToolset.Core.v3.ncrunchproject | 7 + .../WixToolset.Core/WixToolsetServiceProvider.cs | 117 + .../WixToolsetServiceProviderFactory.cs | 21 + src/wix/appveyor.cmd | 20 + src/wix/appveyor.yml | 44 + src/wix/nuget.config | 13 + .../CompileCoreTestExtensionWixlib.csproj | 32 + .../test/CompileCoreTestExtensionWixlib/Program.cs | 37 + src/wix/test/Example.Extension/Data/example.txt | 1 + src/wix/test/Example.Extension/Data/example.wxs | 15 + .../Example.Extension/Example.Extension.csproj | 24 + .../Example.Extension/ExampleCompilerExtension.cs | 195 + .../test/Example.Extension/ExampleExtensionData.cs | 23 + .../Example.Extension/ExampleExtensionFactory.cs | 54 + .../ExamplePreprocessorExtensionAndCommandLine.cs | 57 + src/wix/test/Example.Extension/ExampleRow.cs | 32 + .../test/Example.Extension/ExampleSearchSymbol.cs | 30 + src/wix/test/Example.Extension/ExampleSymbol.cs | 30 + .../Example.Extension/ExampleSymbolDefinitions.cs | 67 + .../Example.Extension/ExampleTableDefinitions.cs | 34 + .../ExampleWindowsInstallerBackendExtension.cs | 33 + .../WixToolsetTest.Core.Burn/BurnReaderFixture.cs | 44 + .../WixToolsetTest.Core.Burn.csproj | 28 + .../ApprovedExeFixture.cs | 64 + .../BadInputFixture.cs | 148 + .../BindVariablesFixture.cs | 96 + .../BootstrapperApplicationFixture.cs | 46 + .../BundleExtractionFixture.cs | 58 + .../BundleFixture.cs | 478 ++ .../BundleManifestFixture.cs | 365 + .../WixToolsetTest.CoreIntegration/CabFixture.cs | 107 + .../ComponentFixture.cs | 45 + .../ContainerFixture.cs | 385 + .../CopyFileFixture.cs | 48 + .../CustomActionFixture.cs | 169 + .../CustomTableFixture.cs | 234 + .../DecompileFixture.cs | 86 + .../DependencyExtensionFixture.cs | 180 + .../DirectoryFixture.cs | 271 + .../ExePackageFixture.cs | 52 + .../ExtensionFixture.cs | 153 + .../LanguageFixture.cs | 174 + .../LinkerFixture.cs | 174 + .../WixToolsetTest.CoreIntegration/MediaFixture.cs | 62 + .../ModuleFixture.cs | 113 + .../WixToolsetTest.CoreIntegration/MsiFixture.cs | 838 ++ .../MsiQueryFixture.cs | 1040 +++ .../MsiTransactionFixture.cs | 131 + .../MsuPackageFixture.cs | 36 + .../PackagePayloadFixture.cs | 211 + .../WixToolsetTest.CoreIntegration/ParseFixture.cs | 36 + .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 279 + .../PayloadFixture.cs | 212 + .../PreprocessorFixture.cs | 181 + .../RegistryFixture.cs | 173 + .../RollbackBoundaryFixture.cs | 41 + .../ShortcutFixture.cs | 78 + .../SoftwareTagFixture.cs | 100 + .../TestData/.Data/burn.exe | Bin 0 -> 463360 bytes .../TestData/AppId/Advertised.wxs | 11 + .../TestData/AppSearch/ComponentSearch.wxs | 12 + .../DecompiledNestedDirSearchUnderRegSearch.wxs | 42 + .../TestData/AppSearch/DirectorySearch.wxs | 12 + .../TestData/AppSearch/FileSearch.wxs | 14 + .../AppSearch/NestedDirSearchUnderRegSearch.msi | Bin 0 -> 33045 bytes .../TestData/AppSearch/RegistrySearch.wxs | 12 + .../TestData/AppSearch/RegistrySearch64.wxs | 12 + .../TestData/Assembly/Package.en-us.wxl | 11 + .../TestData/Assembly/Package.wxs | 17 + .../TestData/Assembly/PackageComponents.wxs | 10 + .../TestData/Assembly/Win32Assembly.wxs | 13 + .../TestData/Assembly/data/candle.exe | Bin 0 -> 28672 bytes .../TestData/Assembly/data/test.manifest | 76 + .../TestData/BadEnsureTable/BadEnsureTable.wxs | 11 + .../TestData/BadIf/Package.en-us.wxl | 11 + .../TestData/BadIf/Package.wxs | 24 + .../TestData/BadIf/PackageComponents.wxs | 12 + .../TestData/BadIf/data/test.txt | 1 + .../TestData/BadInput/BundleVariable.wxs | 6 + .../TestData/BadInput/DuplicateCacheIds.wxs | 12 + .../TestData/BadInput/DuplicatePayloadNames.wxs | 31 + .../BadInput/HiddenPersistedBundleVariable.wxs | 6 + .../TestData/BadInput/InvalidIds.wxs | 8 + .../TestData/BadInput/OrphanPayload.wxs | 11 + .../BadInput/PackageInMultipleContainers.wxs | 14 + .../TestData/BadInput/RegistryKey.wxs | 13 + .../TestData/BadInput/UnscheduledPackage.wxs | 16 + .../BadInput/UnscheduledRollbackBoundary.wxs | 16 + .../TestData/BindVariables/DefaultedVariable.wxs | 6 + .../TestData/BindVariables/data/test.txt | 1 + .../BootstrapperApplication/DpiAwareness.wxs | 7 + .../CacheIdFromPackageDescription.wxs | 8 + .../BundleCustomTable/BundleCustomTable.wxs | 53 + .../TestData/BundleExtension/BundleExtension.wxs | 6 + .../BundleExtension/BundleExtensionSearches.wxs | 8 + .../BundleExtension/BundleWithSearches.wxs | 11 + .../BundleExtension/SimpleBundleExtension.wxs | 10 + .../TestData/BundleTag/BundleWithTag.wxs | 15 + .../TestData/BundleTag/fakeba.dll | 1 + .../TestData/BundleWithApprovedExe/Bundle.wxs | 5 + .../TestData/BundleWithApprovedExe/Bundle64.wxs | 5 + .../BundleWithDetachedContainer/Bundle.wxs | 10 + .../TestData/BundleWithPackageGroupRef/Bundle.wxs | 10 + .../MinimalPackageGroup.wxs | 8 + .../TestData/Class/DecompiledOldClassTableDef.wxs | 22 + .../TestData/Class/IconIndex0.wxs | 11 + .../TestData/Class/OldClassTableDef.msi | Bin 0 -> 36864 bytes .../ComplexExampleExtension/OtherComponents.wxs | 12 + .../ComplexExampleExtension/Package.en-us.wxl | 11 + .../TestData/ComplexExampleExtension/Package.wxs | 22 + .../ComplexExampleExtension/PackageComponents.wxs | 12 + .../ComplexExampleExtension/data/example.txt | 1 + .../ComplexExampleExtension/data/other.txt | 1 + .../TestData/Component/GuidCollision.wxs | 14 + .../TestData/Components/Package.en-us.wxl | 11 + .../TestData/Components/Package.wxs | 17 + .../TestData/Components/PackageComponents.wxs | 10 + .../TestData/Components/data/test.txt | 1 + .../Container/HarvestIntoAttachedContainer.wxs | 17 + .../Container/HarvestIntoDetachedContainer.wxs | 15 + .../Container/LayoutPayloadInContainer.wxs | 28 + .../Container/MultipleAttachedContainers.wxs | 15 + .../Container/PayloadInMultipleContainers.wxs | 28 + .../TestData/CopyFile/CopyFile.wxs | 17 + .../TestData/CustomAction/CustomActionCycle.wxs | 18 + .../CustomAction/CustomActionCycleWithTail.wxs | 20 + .../TestData/CustomAction/SimpleCustomAction.wxs | 14 + .../CustomAction/UnscheduledCustomAction.wxs | 32 + .../CustomPackageDescription.wxs | 12 + .../TestData/CustomTable/CustomTable-Expected.wxs | 29 + .../TestData/CustomTable/CustomTable.wxs | 34 + .../TestData/CustomTable/CustomTableWithFile.wxs | 22 + .../CustomTable/LocalizedCustomTable.en-us.wxl | 7 + .../TestData/CustomTable/LocalizedCustomTable.wxs | 21 + .../TestData/CustomTable/data/file1.txt | 1 + .../TestData/CustomTable/data/file2.txt | 1 + .../TestData/CustomTable/data/test.txt | 1 + .../TestData/DecompileNullComponent/Expected.wxs | 16 + .../TestData/DecompileNullComponent/example.cab | Bin 0 -> 137 bytes .../TestData/DecompileNullComponent/example.msi | Bin 0 -> 32768 bytes .../DecompileSingleFileCompressed/Expected.wxs | 16 + .../DecompileSingleFileCompressed/example.cab | Bin 0 -> 137 bytes .../DecompileSingleFileCompressed/example.msi | Bin 0 -> 32768 bytes .../DecompileSingleFileCompressed64/Expected.wxs | 16 + .../DecompileSingleFileCompressed64/example.cab | Bin 0 -> 137 bytes .../DecompileSingleFileCompressed64/example.msi | Bin 0 -> 32768 bytes .../DecompileTargetDirMergeModule/Expected.wxs | 18 + .../DecompileTargetDirMergeModule/MergeModule1.msm | Bin 0 -> 32768 bytes .../TestData/DefaultDir/DefaultDir.wxs | 26 + .../Dependency/CustomProviderKeyBundle.wxs | 10 + .../Dependency/ExePackageProvidesBundle.wxs | 10 + .../TestData/Dependency/UsingProvidesBundle.wxs | 8 + .../PackageComponents.wxs | 36 + .../TestData/Directory/DefaultName.wxs | 13 + .../Directory/DuplicateTargetSourceName.wxs | 11 + .../TestData/Directory/Empty.wxs | 6 + .../TestData/Directory/Nested.wxs | 11 + .../TestData/DuplicateDir/DuplicateDir.wxs | 25 + .../TestData/EnsureTable/EnsureTable.wxs | 10 + .../TestData/Environment/Environment.wxs | 14 + .../TestData/ErrorsInUI/Package.en-us.wxl | 9 + .../TestData/ErrorsInUI/Package.wxs | 20 + .../TestData/ErrorsInUI/PackageComponents.wxs | 14 + .../TestData/ErrorsInUI/data/test.txt | 1 + .../TestData/ExampleExtension/Package.en-us.wxl | 11 + .../TestData/ExampleExtension/Package.wxs | 21 + .../ExampleExtension/PackageComponents.wxs | 12 + .../TestData/ExampleExtension/data/example.txt | 1 + .../TestData/ExePackage/MissingDetectCondition.wxs | 9 + .../TestData/ExePackage/RequireDetectCondition.wxs | 11 + .../TestData/FeatureGroup/FeatureGroup.wxs | 14 + .../TestData/Font/FontTitle.wxs | 10 + .../TestData/Font/TrueType.wxs | 10 + .../TestData/ForEach/Package.en-us.wxl | 11 + .../TestData/ForEach/Package.wxs | 19 + .../TestData/ForEach/PackageComponents.wxs | 12 + .../TestData/ForEach/data/test.txt | 1 + .../TestData/Icon/SampleIcon.wxs | 6 + .../TestData/IncludePath/Package.en-us.wxl | 11 + .../TestData/IncludePath/Package.wxs | 18 + .../TestData/IncludePath/PackageComponents.wxs | 8 + .../TestData/IncludePath/data/DontDoThis.wxi | 6 + .../TestData/IncludePath/data/Package.wxi | 4 + .../TestData/IncludePath/data/test.txt | 1 + .../TestData/InstanceTransform/Package.en-us.wxl | 11 + .../TestData/InstanceTransform/Package.wxs | 23 + .../InstanceTransform/PackageComponents.wxs | 10 + .../TestData/InstanceTransform/data/test.txt | 1 + .../TestData/Language/Package.en-us.wxl | 7 + .../TestData/Language/Package.ja-jp.wxl | 7 + .../TestData/Language/Package.wxl | 7 + .../TestData/Language/Package.wxs | 18 + .../Language/PackageWithEnSummaryInfo.ja-jp.wxl | 7 + .../TestData/Language/data/test.txt | 1 + .../TestData/LockPermissions/EmptyPermissions.wxs | 13 + .../TestData/ManualUpgrade/Package.en-us.wxl | 11 + .../TestData/ManualUpgrade/Package.wxs | 24 + .../TestData/ManualUpgrade/PackageComponents.wxs | 10 + .../TestData/ManualUpgrade/data/test.txt | 1 + .../TestData/Media/MultiMedia.wxs | 28 + .../TestData/Media/data/a1.txt | 1 + .../TestData/Media/data/a2.txt | 1 + .../TestData/Media/data/b1.txt | 1 + .../TestData/Media/data/b2.txt | 1 + .../TestData/MsiTransaction/FirstX64.wxs | 8 + .../TestData/MsiTransaction/FirstX86.wxs | 8 + .../TestData/MsiTransaction/SecondX64.wxs | 8 + .../TestData/MsiTransaction/SecondX86.wxs | 8 + .../TestData/MsiTransaction/X64AfterX86Bundle.wxs | 12 + .../TestData/MsiTransaction/X86AfterX64Bundle.wxs | 12 + .../TestData/MsuPackage/Bundle.wxs | 11 + .../TestData/MsuPackage/data/fakeba.dll | 1 + .../TestData/MsuPackage/data/test.msu | 1 + .../TestData/MultiFileCompressed/Package.en-us.wxl | 11 + .../TestData/MultiFileCompressed/Package.wxs | 26 + .../MultiFileCompressed/PackageComponents.wxs | 13 + .../TestData/MultiFileCompressed/data/test.txt | 1 + .../TestData/OverridableActions/Package.en-us.wxl | 11 + .../TestData/OverridableActions/Package.wxs | 48 + .../OverridableActions/PackageComponents.wxs | 10 + .../TestData/OverridableActions/data/test.txt | 1 + .../PackagePayload/MissingSourceFileAndHash.wxs | 10 + .../PackagePayload/MissingSourceFileAndName.wxs | 10 + .../PackagePayloadInPayloadGroup.wxs | 15 + .../TestData/PackagePayload/SpecifiedHash.wxs | 10 + .../SpecifiedHashAndMissingDownloadUrl.wxs | 10 + .../PackagePayload/SpecifiedSourceFileAndHash.wxs | 10 + .../WrongPackagePayloadInPayloadGroup.wxs | 15 + .../TestData/PatchFamilyFilter/.data/Av1.0.0.txt | 1 + .../TestData/PatchFamilyFilter/.data/Av1.0.1.txt | 1 + .../TestData/PatchFamilyFilter/.data/Bv1.0.0.txt | 1 + .../TestData/PatchFamilyFilter/.data/Bv1.0.1.txt | 1 + .../TestData/PatchFamilyFilter/Package.wxs | 28 + .../TestData/PatchFamilyFilter/Patch.wxs | 16 + .../TestData/PatchFromWixlib/Package.wxs | 30 + .../TestData/PatchFromWixlib/Patch.wxs | 16 + .../TestData/PatchNoFileChanges/.data/A.txt | 1 + .../TestData/PatchNoFileChanges/Package.wxs | 27 + .../TestData/PatchNoFileChanges/Patch.wxs | 16 + .../TestData/PatchNonSpecific/BundleA/Bundle.wxs | 7 + .../TestData/PatchNonSpecific/BundleB/Bundle.wxs | 8 + .../TestData/PatchNonSpecific/BundleC/Bundle.wxs | 9 + .../TestData/PatchNonSpecific/PackageA/Package.wxs | 44 + .../TestData/PatchNonSpecific/PatchA/Patch.wxs | 12 + .../TestData/PatchNonSpecific/PatchB/Patch.wxs | 14 + .../TestData/PatchNonSpecific/PatchC/Patch.wxs | 14 + .../TestData/PatchSingle/.data/Av1.0.0.txt | 1 + .../TestData/PatchSingle/.data/Av1.0.1.txt | 1 + .../TestData/PatchSingle/.data/Bv1.0.0.txt | 1 + .../TestData/PatchSingle/.data/Bv1.0.1.txt | 1 + .../TestData/PatchSingle/BundleA/Bundle.wxs | 10 + .../TestData/PatchSingle/Package.wxs | 27 + .../TestData/PatchSingle/Patch.wxs | 16 + .../TestData/Payload/AbsoluteName.wxs | 9 + .../TestData/Payload/CanonicalizeName.wxs | 7 + .../Payload/DownloadUrlPlaceholdersBundle.wxs | 30 + .../Payload/SharedBAAndPackagePayloadBundle.wxs | 16 + .../TestData/Payload/ValidName.wxs | 7 + .../TestData/Preprocessor/EnvParens.wxs | 4 + .../TestData/ProductTag/Package.en-us.wxl | 11 + .../TestData/ProductTag/PackageComponents.wxs | 10 + .../TestData/ProductTag/PackageWithTag.wxs | 18 + .../TestData/ProductTag/example.txt | 1 + .../MinimalComponentGroup.wxs | 10 + .../ProductWithComponentGroupRef/Product.wxs | 19 + .../TestData/ProgId/NestedUnderClass.wxs | 13 + .../TestData/ProgId/Package.en-us.wxl | 11 + .../TestData/ProgId/Package.wxs | 17 + .../TestData/ProgId/PackageComponents.wxs | 16 + .../TestData/ProgId/data/test.txt | 1 + .../TestData/PublishComponent/Package.en-us.wxl | 11 + .../TestData/PublishComponent/Package.wxs | 34 + .../TestData/PublishComponent/data/test.txt | 1 + .../Registry/DuplicateRegistryValueIds.wxs | 14 + .../Registry/RegistryKeyEndingWithBackslash.wxs | 12 + .../TestData/Registry/RegistryValue.wxs | 11 + .../TestData/Registry/RegistryValueMultiString.wxs | 17 + .../TestData/Registry/RemoveRegistryKey.wxs | 11 + .../TestData/ReserveCost/ReserveCost.wxs | 11 + .../TestData/RollbackBoundary/BeginningOfChain.wxs | 9 + .../TestData/SameFileFolders/TestComponents.wxs | 16 + .../TestData/SameFileFolders/data/a/test.txt | 1 + .../TestData/SameFileFolders/data/b/test.txt | 1 + .../TestData/SameFileFolders/data/c/test.txt | 1 + .../SequenceTables/DecompiledSequenceTables.wxs | 32 + .../TestData/SequenceTables/SequenceTables.msi | Bin 0 -> 32768 bytes .../TestData/ServiceInstall/OwnProcess.wxs | 12 + .../TestData/SetProperty/Package.en-us.wxl | 11 + .../TestData/SetProperty/Package.wxs | 19 + .../TestData/SetProperty/PackageComponents.wxs | 10 + .../TestData/SetProperty/data/test.txt | 1 + .../TestData/SetVariable/Simple.wxs | 15 + .../SharedPayloadsBetweenPackages.wxs | 18 + .../TestData/Shortcut/DecompiledShortcuts.wxs | 19 + .../TestData/Shortcut/ShortcutProperty.wxs | 14 + .../Shortcut/ShortcutSameNameShortName.wxs | 12 + .../TestData/Shortcut/shortcuts.msi | Bin 0 -> 32768 bytes .../TestData/SimpleBundle/Bundle.en-us.wxl | 10 + .../TestData/SimpleBundle/Bundle.wxs | 12 + .../MultiFileBootstrapperApplication.wxs | 7 + .../TestData/SimpleBundle/MultiFileBundle.wxs | 18 + .../SimpleBundle/data/MsiPackage/Shared.dll | 1 + .../TestData/SimpleBundle/data/MsiPackage/test.txt | 1 + .../TestData/SimpleBundle/data/fakeba.dll | 1 + .../TestData/SimpleBundle/data/test.msi | Bin 0 -> 32768 bytes .../TestData/SimpleMerge/.data/test.msm | Bin 0 -> 24576 bytes .../TestData/SimpleMerge/Package.en-us.wxl | 11 + .../TestData/SimpleMerge/Package.wxs | 20 + .../TestData/SimpleModule/Module.en-us.wxl | 10 + .../TestData/SimpleModule/Module.wixproj | 48 + .../TestData/SimpleModule/Module.wxs | 17 + .../TestData/SimpleModule/data/test.txt | 1 + .../SingleExeBundle/SingleExePackageGroup.wxs | 8 + .../SingleExeBundle/SingleExeRemotePayload.wxs | 27 + .../TestData/SingleFile/Package.en-us.wxl | 11 + .../TestData/SingleFile/Package.wxs | 17 + .../TestData/SingleFile/PackageComponents.wxs | 13 + .../TestData/SingleFile/data/test.txt | 1 + .../SingleFileCompressed/Package.en-us.wxl | 11 + .../TestData/SingleFileCompressed/Package.wxs | 25 + .../SingleFileCompressed/PackageComponents.wxs | 10 + .../TestData/SingleFileCompressed/data/test.txt | 1 + .../SuppressModularization/Module.en-us.wxl | 10 + .../TestData/SuppressModularization/Module.wxs | 10 + .../TestData/SuppressModularization/data/test.txt | 1 + .../TestData/TextStyle/ColorNull.wxs | 12 + .../TestData/TextStyle/SizeLocalized.en-us.wxl | 13 + .../TestData/TextStyle/SizeLocalized.wxs | 12 + .../TestData/TypeLib/Language0.wxs | 11 + .../TestData/Upgrade/DetectOnly.wxs | 12 + .../TestData/UsingProvides/Package.en-us.wxl | 11 + .../TestData/UsingProvides/Package.wxs | 16 + .../TestData/UsingProvides/PackageComponents.wxs | 10 + .../TestData/UsingProvides/example.txt | 1 + .../TestData/Variables/Package.en-us.wxl | 11 + .../TestData/Variables/Package.wxs | 31 + .../TestData/Variables/PackageComponents.wxs | 10 + .../TestData/Variables/data/test.txt | 1 + .../TestData/WixVariableOverride/Package.en-us.wxl | 11 + .../TestData/WixVariableOverride/Package.wxs | 19 + .../WixVariableOverride/PackageComponents.wxs | 14 + .../TestData/WixVariableOverride/data/test.txt | 1 + .../TestData/WixVariableOverride/data/test2.txt | 1 + .../TestData/Wixipl/Package.en-us.wxl | 11 + .../TestData/Wixipl/Package.wxs | 19 + .../TestData/Wixipl/PackageComponents.wxs | 10 + .../TestData/Wixipl/data/test.txt | 1 + .../TestData/WixlibWithBinaries/Package.en-us.wxl | 11 + .../TestData/WixlibWithBinaries/Package.wxs | 19 + .../WixlibWithBinaries/PackageComponents.wxs | 26 + .../TestData/WixlibWithBinaries/data/alpha/foo.dll | 1 + .../TestData/WixlibWithBinaries/data/mips/foo.dll | 1 + .../WixlibWithBinaries/data/powerpc/foo.dll | 1 + .../TestData/WixlibWithBinaries/data/test.txt | 1 + .../TestXmlFixture.cs | 62 + .../VariableResolverFixture.cs | 75 + .../WarningFixture.cs | 63 + .../WixToolsetTest.CoreIntegration.csproj | 32 + .../WixiplFixture.cs | 205 + .../WixlibFixture.cs | 316 + .../WixlibQueryFixture.cs | 81 + version.json | 11 - 1209 files changed, 80836 insertions(+), 80738 deletions(-) delete mode 100644 .editorconfig delete mode 100644 README.md delete mode 100644 WixToolset.Core.sln delete mode 100644 WixToolset.Core.v3.ncrunchsolution delete mode 100644 appveyor.cmd delete mode 100644 appveyor.yml delete mode 100644 nuget.config create mode 100644 src/.editorconfig delete mode 100644 src/Custom.Build.props delete mode 100644 src/Directory.Build.props delete mode 100644 src/Directory.Build.targets delete mode 100644 src/Directory.csproj.props delete mode 100644 src/Directory.csproj.targets delete mode 100644 src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/BundleBackend.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/BurnCommon.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/BurnReader.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/BurnWriter.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/PackageFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs delete mode 100644 src/WixToolset.Core.Burn/BurnBackendErrors.cs delete mode 100644 src/WixToolset.Core.Burn/BurnBackendFactory.cs delete mode 100644 src/WixToolset.Core.Burn/BurnBackendWarnings.cs delete mode 100644 src/WixToolset.Core.Burn/BurnExtensionFactory.cs delete mode 100644 src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs delete mode 100644 src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs delete mode 100644 src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs delete mode 100644 src/WixToolset.Core.Burn/ISearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs delete mode 100644 src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs delete mode 100644 src/WixToolset.Core.Burn/RowIndexedList.cs delete mode 100644 src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj delete mode 100644 src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/CachedExtension.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs delete mode 100644 src/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj delete mode 100644 src/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs delete mode 100644 src/WixToolset.Core.TestPackage/BundleExtractor.cs delete mode 100644 src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs delete mode 100644 src/WixToolset.Core.TestPackage/TestMessageListener.cs delete mode 100644 src/WixToolset.Core.TestPackage/WixRunner.cs delete mode 100644 src/WixToolset.Core.TestPackage/WixRunnerResult.cs delete mode 100644 src/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj delete mode 100644 src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Differ.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Melter.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/MelterCore.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/MsiBackend.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/MsmBackend.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/MspBackend.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/MstBackend.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/RowDictionary.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj delete mode 100644 src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs delete mode 100644 src/WixToolset.Core/Bind/DelayedField.cs delete mode 100644 src/WixToolset.Core/Bind/ExpectedExtractFile.cs delete mode 100644 src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs delete mode 100644 src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs delete mode 100644 src/WixToolset.Core/Bind/FileResolver.cs delete mode 100644 src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs delete mode 100644 src/WixToolset.Core/Bind/ResolveFieldsCommand.cs delete mode 100644 src/WixToolset.Core/Bind/TransferFilesCommand.cs delete mode 100644 src/WixToolset.Core/BindContext.cs delete mode 100644 src/WixToolset.Core/BindFileWithPath.cs delete mode 100644 src/WixToolset.Core/BindPath.cs delete mode 100644 src/WixToolset.Core/BindResult.cs delete mode 100644 src/WixToolset.Core/Binder.cs delete mode 100644 src/WixToolset.Core/CommandLine/BuildCommand.cs delete mode 100644 src/WixToolset.Core/CommandLine/CommandLine.cs delete mode 100644 src/WixToolset.Core/CommandLine/CommandLineArguments.cs delete mode 100644 src/WixToolset.Core/CommandLine/CommandLineContext.cs delete mode 100644 src/WixToolset.Core/CommandLine/CommandLineParser.cs delete mode 100644 src/WixToolset.Core/CommandLine/CompileCommand.cs delete mode 100644 src/WixToolset.Core/CommandLine/DecompileCommand.cs delete mode 100644 src/WixToolset.Core/CommandLine/HelpCommand.cs delete mode 100644 src/WixToolset.Core/CommandLine/VersionCommand.cs delete mode 100644 src/WixToolset.Core/Common.cs delete mode 100644 src/WixToolset.Core/Compile/CompilerPayload.cs delete mode 100644 src/WixToolset.Core/CompileContext.cs delete mode 100644 src/WixToolset.Core/Compiler.cs delete mode 100644 src/WixToolset.Core/CompilerCore.cs delete mode 100644 src/WixToolset.Core/CompilerErrors.cs delete mode 100644 src/WixToolset.Core/CompilerWarnings.cs delete mode 100644 src/WixToolset.Core/Compiler_Bundle.cs delete mode 100644 src/WixToolset.Core/Compiler_Dependency.cs delete mode 100644 src/WixToolset.Core/Compiler_EmbeddedUI.cs delete mode 100644 src/WixToolset.Core/Compiler_Module.cs delete mode 100644 src/WixToolset.Core/Compiler_Package.cs delete mode 100644 src/WixToolset.Core/Compiler_Patch.cs delete mode 100644 src/WixToolset.Core/Compiler_PatchCreation.cs delete mode 100644 src/WixToolset.Core/Compiler_Tag.cs delete mode 100644 src/WixToolset.Core/Compiler_UI.cs delete mode 100644 src/WixToolset.Core/ComponentKeyPath.cs delete mode 100644 src/WixToolset.Core/DecompileContext.cs delete mode 100644 src/WixToolset.Core/DecompileResult.cs delete mode 100644 src/WixToolset.Core/Decompiler.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/FileFacade.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/FileTransfer.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/Messaging.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/PathResolver.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/TrackedFile.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/Uuid.cs delete mode 100644 src/WixToolset.Core/ExtensibilityServices/WixBranding.cs delete mode 100644 src/WixToolset.Core/IBinder.cs delete mode 100644 src/WixToolset.Core/ICompiler.cs delete mode 100644 src/WixToolset.Core/IDecompiler.cs delete mode 100644 src/WixToolset.Core/ILayoutCreator.cs delete mode 100644 src/WixToolset.Core/ILibrarian.cs delete mode 100644 src/WixToolset.Core/ILinker.cs delete mode 100644 src/WixToolset.Core/ILocalizationParser.cs delete mode 100644 src/WixToolset.Core/IPreprocessor.cs delete mode 100644 src/WixToolset.Core/IResolver.cs delete mode 100644 src/WixToolset.Core/IUnbinder.cs delete mode 100644 src/WixToolset.Core/IncludedFile.cs delete mode 100644 src/WixToolset.Core/IncribeContext.cs delete mode 100644 src/WixToolset.Core/LayoutContext.cs delete mode 100644 src/WixToolset.Core/LayoutCreator.cs delete mode 100644 src/WixToolset.Core/Librarian.cs delete mode 100644 src/WixToolset.Core/LibraryContext.cs delete mode 100644 src/WixToolset.Core/Link/CollateLocalizationsCommand.cs delete mode 100644 src/WixToolset.Core/Link/ConnectToFeature.cs delete mode 100644 src/WixToolset.Core/Link/ConnectToFeatureCollection.cs delete mode 100644 src/WixToolset.Core/Link/ConnectToModule.cs delete mode 100644 src/WixToolset.Core/Link/ConnectToModuleCollection.cs delete mode 100644 src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs delete mode 100644 src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs delete mode 100644 src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs delete mode 100644 src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs delete mode 100644 src/WixToolset.Core/Link/ResolveReferencesCommand.cs delete mode 100644 src/WixToolset.Core/Link/SymbolWithSection.cs delete mode 100644 src/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs delete mode 100644 src/WixToolset.Core/Link/WixGroupingOrdering.cs delete mode 100644 src/WixToolset.Core/LinkContext.cs delete mode 100644 src/WixToolset.Core/Linker.cs delete mode 100644 src/WixToolset.Core/LinkerErrors.cs delete mode 100644 src/WixToolset.Core/LinkerWarnings.cs delete mode 100644 src/WixToolset.Core/LocalizationParser.cs delete mode 100644 src/WixToolset.Core/ParsedWixVariable.cs delete mode 100644 src/WixToolset.Core/Preprocess/IfContext.cs delete mode 100644 src/WixToolset.Core/Preprocess/IfDefEventHandler.cs delete mode 100644 src/WixToolset.Core/Preprocess/IfState.cs delete mode 100644 src/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs delete mode 100644 src/WixToolset.Core/Preprocess/PreprocessorOperation.cs delete mode 100644 src/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs delete mode 100644 src/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs delete mode 100644 src/WixToolset.Core/PreprocessContext.cs delete mode 100644 src/WixToolset.Core/PreprocessResult.cs delete mode 100644 src/WixToolset.Core/Preprocessor.cs delete mode 100644 src/WixToolset.Core/Properties/AssemblyInfo.cs delete mode 100644 src/WixToolset.Core/ResolveContext.cs delete mode 100644 src/WixToolset.Core/ResolveFileResult.cs delete mode 100644 src/WixToolset.Core/ResolveResult.cs delete mode 100644 src/WixToolset.Core/ResolvedCabinet.cs delete mode 100644 src/WixToolset.Core/Resolver.cs delete mode 100644 src/WixToolset.Core/SourceFile.cs delete mode 100644 src/WixToolset.Core/UnbindContext.cs delete mode 100644 src/WixToolset.Core/Unbinder.cs delete mode 100644 src/WixToolset.Core/VariableResolution.cs delete mode 100644 src/WixToolset.Core/VariableResolver.cs delete mode 100644 src/WixToolset.Core/WixToolset.Core.csproj delete mode 100644 src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject delete mode 100644 src/WixToolset.Core/WixToolsetServiceProvider.cs delete mode 100644 src/WixToolset.Core/WixToolsetServiceProviderFactory.cs delete mode 100644 src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj delete mode 100644 src/test/CompileCoreTestExtensionWixlib/Program.cs delete mode 100644 src/test/Example.Extension/Data/example.txt delete mode 100644 src/test/Example.Extension/Data/example.wxs delete mode 100644 src/test/Example.Extension/Example.Extension.csproj delete mode 100644 src/test/Example.Extension/ExampleCompilerExtension.cs delete mode 100644 src/test/Example.Extension/ExampleExtensionData.cs delete mode 100644 src/test/Example.Extension/ExampleExtensionFactory.cs delete mode 100644 src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs delete mode 100644 src/test/Example.Extension/ExampleRow.cs delete mode 100644 src/test/Example.Extension/ExampleSearchSymbol.cs delete mode 100644 src/test/Example.Extension/ExampleSymbol.cs delete mode 100644 src/test/Example.Extension/ExampleSymbolDefinitions.cs delete mode 100644 src/test/Example.Extension/ExampleTableDefinitions.cs delete mode 100644 src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs delete mode 100644 src/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs delete mode 100644 src/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/CabFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt delete mode 100644 src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/WarningFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj delete mode 100644 src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs delete mode 100644 src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs create mode 100644 src/version.json create mode 100644 src/wix/Custom.Build.props create mode 100644 src/wix/Directory.Build.props create mode 100644 src/wix/Directory.Build.targets create mode 100644 src/wix/Directory.csproj.props create mode 100644 src/wix/Directory.csproj.targets create mode 100644 src/wix/README.md create mode 100644 src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/BundleBackend.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs create mode 100644 src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs create mode 100644 src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs create mode 100644 src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs create mode 100644 src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs create mode 100644 src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs create mode 100644 src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs create mode 100644 src/wix/WixToolset.Core.Burn/ISearchFacade.cs create mode 100644 src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs create mode 100644 src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs create mode 100644 src/wix/WixToolset.Core.Burn/RowIndexedList.cs create mode 100644 src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj create mode 100644 src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs create mode 100644 src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj create mode 100644 src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/WixRunner.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs create mode 100644 src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj create mode 100644 src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Differ.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Melter.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs create mode 100644 src/wix/WixToolset.Core.sln create mode 100644 src/wix/WixToolset.Core.v3.ncrunchsolution create mode 100644 src/wix/WixToolset.Core/Bind/DelayedField.cs create mode 100644 src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs create mode 100644 src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs create mode 100644 src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs create mode 100644 src/wix/WixToolset.Core/Bind/FileResolver.cs create mode 100644 src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs create mode 100644 src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs create mode 100644 src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs create mode 100644 src/wix/WixToolset.Core/BindContext.cs create mode 100644 src/wix/WixToolset.Core/BindFileWithPath.cs create mode 100644 src/wix/WixToolset.Core/BindPath.cs create mode 100644 src/wix/WixToolset.Core/BindResult.cs create mode 100644 src/wix/WixToolset.Core/Binder.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/BuildCommand.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/CommandLine.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/CompileCommand.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/HelpCommand.cs create mode 100644 src/wix/WixToolset.Core/CommandLine/VersionCommand.cs create mode 100644 src/wix/WixToolset.Core/Common.cs create mode 100644 src/wix/WixToolset.Core/Compile/CompilerPayload.cs create mode 100644 src/wix/WixToolset.Core/CompileContext.cs create mode 100644 src/wix/WixToolset.Core/Compiler.cs create mode 100644 src/wix/WixToolset.Core/CompilerCore.cs create mode 100644 src/wix/WixToolset.Core/CompilerErrors.cs create mode 100644 src/wix/WixToolset.Core/CompilerWarnings.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Bundle.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Dependency.cs create mode 100644 src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Module.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Package.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Patch.cs create mode 100644 src/wix/WixToolset.Core/Compiler_PatchCreation.cs create mode 100644 src/wix/WixToolset.Core/Compiler_Tag.cs create mode 100644 src/wix/WixToolset.Core/Compiler_UI.cs create mode 100644 src/wix/WixToolset.Core/ComponentKeyPath.cs create mode 100644 src/wix/WixToolset.Core/DecompileContext.cs create mode 100644 src/wix/WixToolset.Core/DecompileResult.cs create mode 100644 src/wix/WixToolset.Core/Decompiler.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs create mode 100644 src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs create mode 100644 src/wix/WixToolset.Core/IBinder.cs create mode 100644 src/wix/WixToolset.Core/ICompiler.cs create mode 100644 src/wix/WixToolset.Core/IDecompiler.cs create mode 100644 src/wix/WixToolset.Core/ILayoutCreator.cs create mode 100644 src/wix/WixToolset.Core/ILibrarian.cs create mode 100644 src/wix/WixToolset.Core/ILinker.cs create mode 100644 src/wix/WixToolset.Core/ILocalizationParser.cs create mode 100644 src/wix/WixToolset.Core/IPreprocessor.cs create mode 100644 src/wix/WixToolset.Core/IResolver.cs create mode 100644 src/wix/WixToolset.Core/IUnbinder.cs create mode 100644 src/wix/WixToolset.Core/IncludedFile.cs create mode 100644 src/wix/WixToolset.Core/IncribeContext.cs create mode 100644 src/wix/WixToolset.Core/LayoutContext.cs create mode 100644 src/wix/WixToolset.Core/LayoutCreator.cs create mode 100644 src/wix/WixToolset.Core/Librarian.cs create mode 100644 src/wix/WixToolset.Core/LibraryContext.cs create mode 100644 src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs create mode 100644 src/wix/WixToolset.Core/Link/ConnectToFeature.cs create mode 100644 src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs create mode 100644 src/wix/WixToolset.Core/Link/ConnectToModule.cs create mode 100644 src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs create mode 100644 src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs create mode 100644 src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs create mode 100644 src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs create mode 100644 src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs create mode 100644 src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs create mode 100644 src/wix/WixToolset.Core/Link/SymbolWithSection.cs create mode 100644 src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs create mode 100644 src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs create mode 100644 src/wix/WixToolset.Core/LinkContext.cs create mode 100644 src/wix/WixToolset.Core/Linker.cs create mode 100644 src/wix/WixToolset.Core/LinkerErrors.cs create mode 100644 src/wix/WixToolset.Core/LinkerWarnings.cs create mode 100644 src/wix/WixToolset.Core/LocalizationParser.cs create mode 100644 src/wix/WixToolset.Core/ParsedWixVariable.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/IfContext.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/IfState.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs create mode 100644 src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs create mode 100644 src/wix/WixToolset.Core/PreprocessContext.cs create mode 100644 src/wix/WixToolset.Core/PreprocessResult.cs create mode 100644 src/wix/WixToolset.Core/Preprocessor.cs create mode 100644 src/wix/WixToolset.Core/Properties/AssemblyInfo.cs create mode 100644 src/wix/WixToolset.Core/ResolveContext.cs create mode 100644 src/wix/WixToolset.Core/ResolveFileResult.cs create mode 100644 src/wix/WixToolset.Core/ResolveResult.cs create mode 100644 src/wix/WixToolset.Core/ResolvedCabinet.cs create mode 100644 src/wix/WixToolset.Core/Resolver.cs create mode 100644 src/wix/WixToolset.Core/SourceFile.cs create mode 100644 src/wix/WixToolset.Core/UnbindContext.cs create mode 100644 src/wix/WixToolset.Core/Unbinder.cs create mode 100644 src/wix/WixToolset.Core/VariableResolution.cs create mode 100644 src/wix/WixToolset.Core/VariableResolver.cs create mode 100644 src/wix/WixToolset.Core/WixToolset.Core.csproj create mode 100644 src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject create mode 100644 src/wix/WixToolset.Core/WixToolsetServiceProvider.cs create mode 100644 src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs create mode 100644 src/wix/appveyor.cmd create mode 100644 src/wix/appveyor.yml create mode 100644 src/wix/nuget.config create mode 100644 src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj create mode 100644 src/wix/test/CompileCoreTestExtensionWixlib/Program.cs create mode 100644 src/wix/test/Example.Extension/Data/example.txt create mode 100644 src/wix/test/Example.Extension/Data/example.wxs create mode 100644 src/wix/test/Example.Extension/Example.Extension.csproj create mode 100644 src/wix/test/Example.Extension/ExampleCompilerExtension.cs create mode 100644 src/wix/test/Example.Extension/ExampleExtensionData.cs create mode 100644 src/wix/test/Example.Extension/ExampleExtensionFactory.cs create mode 100644 src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs create mode 100644 src/wix/test/Example.Extension/ExampleRow.cs create mode 100644 src/wix/test/Example.Extension/ExampleSearchSymbol.cs create mode 100644 src/wix/test/Example.Extension/ExampleSymbol.cs create mode 100644 src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs create mode 100644 src/wix/test/Example.Extension/ExampleTableDefinitions.cs create mode 100644 src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs create mode 100644 src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs create mode 100644 src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs delete mode 100644 version.json (limited to 'src/test/WixToolsetTest.CoreIntegration/TestData') diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 1d72e683..00000000 --- a/.editorconfig +++ /dev/null @@ -1,37 +0,0 @@ -# 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. -# -# Do NOT modify this file. Update the canonical version in Home\repo-template\src\.editorconfig -# then update all of the repos. - -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true - -[*.{cs,vb}] -dotnet_sort_system_directives_first = true - -[*.cs] -csharp_indent_case_contents = true : error -csharp_indent_switch_labels = true : error -csharp_new_line_before_open_brace = all -csharp_prefer_braces = true : error -csharp_style_expression_bodied_methods = when_on_single_line : suggestion -csharp_style_expression_bodied_constructors = when_on_single_line : suggestion -csharp_style_expression_bodied_operators = when_on_single_line : suggestion -csharp_style_expression_bodied_properties = when_on_single_line : suggestion -csharp_style_expression_bodied_indexers = when_on_single_line : suggestion -csharp_style_expression_bodied_accessors = when_on_single_line : suggestion -csharp_style_var_elsewhere = true : suggestion -csharp_style_var_for_built_in_types = true : suggestion -csharp_style_var_when_type_is_apparent = true : suggestion -dotnet_style_qualification_for_event = true : error -dotnet_style_qualification_for_field = true : error -dotnet_style_qualification_for_method = true : error -dotnet_style_qualification_for_property = true : error - -[*.targets] -indent_size = 2 diff --git a/README.md b/README.md deleted file mode 100644 index 622cd3f9..00000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Core -WixToolset.Core - preprocessor, compiler, linker and binder for Windows Installer and Burn - diff --git a/WixToolset.Core.sln b/WixToolset.Core.sln deleted file mode 100644 index 523c960e..00000000 --- a/WixToolset.Core.sln +++ /dev/null @@ -1,156 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2009 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core", "src\WixToolset.Core\WixToolset.Core.csproj", "{0B524850-5B9A-472B-85CC-D25920A1DFE1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.WindowsInstaller", "src\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj", "{5617F2A7-46A0-4D07-B9E0-E982D15641E4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.Burn", "src\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj", "{BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.ExtensionCache", "src\WixToolset.Core.ExtensionCache\WixToolset.Core.ExtensionCache.csproj", "{A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{1284331E-BC6C-426D-AAAF-140C0174F875}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.Extension", "src\test\Example.Extension\Example.Extension.csproj", "{C66C2503-C671-4230-8B48-1D93A8532A28}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.CoreIntegration", "src\test\WixToolsetTest.CoreIntegration\WixToolsetTest.CoreIntegration.csproj", "{E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.Core.Burn", "src\test\WixToolsetTest.Core.Burn\WixToolsetTest.Core.Burn.csproj", "{DF63F589-028E-45A1-A212-948FACF1FDCD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.TestPackage", "src\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj", "{853716DB-C02C-41BD-91BC-79CDC0C17D10}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompileCoreTestExtensionWixlib", "src\test\CompileCoreTestExtensionWixlib\CompileCoreTestExtensionWixlib.csproj", "{23FC60D7-B101-42F8-9786-DB7A9CD964A2}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x64.ActiveCfg = Debug|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x64.Build.0 = Debug|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x86.ActiveCfg = Debug|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x86.Build.0 = Debug|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.Build.0 = Release|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x64.ActiveCfg = Release|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x64.Build.0 = Release|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x86.ActiveCfg = Release|Any CPU - {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x86.Build.0 = Release|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x64.ActiveCfg = Debug|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x64.Build.0 = Debug|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x86.ActiveCfg = Debug|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x86.Build.0 = Debug|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|Any CPU.Build.0 = Release|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x64.ActiveCfg = Release|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x64.Build.0 = Release|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x86.ActiveCfg = Release|Any CPU - {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x86.Build.0 = Release|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x64.ActiveCfg = Debug|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x64.Build.0 = Debug|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x86.ActiveCfg = Debug|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x86.Build.0 = Debug|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|Any CPU.Build.0 = Release|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x64.ActiveCfg = Release|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x64.Build.0 = Release|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x86.ActiveCfg = Release|Any CPU - {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x86.Build.0 = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.ActiveCfg = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.Build.0 = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.ActiveCfg = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.Build.0 = Debug|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.Build.0 = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.ActiveCfg = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.Build.0 = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.ActiveCfg = Release|Any CPU - {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.Build.0 = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.ActiveCfg = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.Build.0 = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.ActiveCfg = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.Build.0 = Debug|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.Build.0 = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.ActiveCfg = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.Build.0 = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.ActiveCfg = Release|Any CPU - {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.Build.0 = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.ActiveCfg = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.Build.0 = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.ActiveCfg = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.Build.0 = Debug|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.Build.0 = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.ActiveCfg = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.Build.0 = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.ActiveCfg = Release|Any CPU - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.Build.0 = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.ActiveCfg = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.Build.0 = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.ActiveCfg = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.Build.0 = Debug|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.Build.0 = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.ActiveCfg = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.Build.0 = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.ActiveCfg = Release|Any CPU - {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.Build.0 = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.Build.0 = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.ActiveCfg = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.Build.0 = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.ActiveCfg = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.Build.0 = Debug|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.ActiveCfg = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.Build.0 = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.ActiveCfg = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.Build.0 = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.ActiveCfg = Release|Any CPU - {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.Build.0 = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.ActiveCfg = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.Build.0 = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.ActiveCfg = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.Build.0 = Debug|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.Build.0 = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.ActiveCfg = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.Build.0 = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.ActiveCfg = Release|Any CPU - {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {C66C2503-C671-4230-8B48-1D93A8532A28} = {1284331E-BC6C-426D-AAAF-140C0174F875} - {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B} = {1284331E-BC6C-426D-AAAF-140C0174F875} - {DF63F589-028E-45A1-A212-948FACF1FDCD} = {1284331E-BC6C-426D-AAAF-140C0174F875} - {23FC60D7-B101-42F8-9786-DB7A9CD964A2} = {1284331E-BC6C-426D-AAAF-140C0174F875} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {BB8820D5-723D-426D-B4A0-4D221603C5FA} - EndGlobalSection -EndGlobal diff --git a/WixToolset.Core.v3.ncrunchsolution b/WixToolset.Core.v3.ncrunchsolution deleted file mode 100644 index 10420ac9..00000000 --- a/WixToolset.Core.v3.ncrunchsolution +++ /dev/null @@ -1,6 +0,0 @@ - - - True - True - - \ No newline at end of file diff --git a/appveyor.cmd b/appveyor.cmd deleted file mode 100644 index 02db695b..00000000 --- a/appveyor.cmd +++ /dev/null @@ -1,20 +0,0 @@ -@setlocal -@pushd %~dp0 -@set _P=%~dp0build\Release\publish -@set _C=Release -@if /i "%1"=="debug" set _C=Debug - -:: Restore -msbuild -p:Configuration=%_C% -t:Restore || exit /b - -:: Build -msbuild -p:Configuration=%_C% || exit /b - -:: Test -dotnet test -c %_C% --no-build || exit /b - -:: Pack -msbuild -p:Configuration=%_C% -p:NoBuild=true -t:Pack || exit /b - -@popd -@endlocal diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 364569cf..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,44 +0,0 @@ -# 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. -# -# Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml -# then update all of the repos. - -branches: - only: - - master - - develop - -image: Visual Studio 2019 - -version: 0.0.0.{build} -configuration: Release - -environment: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - NUGET_XMLDOC_MODE: skip - -build_script: - - appveyor.cmd - -test: off - -pull_requests: - do_not_increment_build_number: true - -nuget: - disable_publish_on_pr: true - -skip_branch_with_pr: true -skip_tags: true - -artifacts: -- path: build\Release\**\*.nupkg - name: nuget -- path: build\Release\**\*.snupkg - name: snupkg - -notifications: -- provider: Slack - incoming_webhook: - secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA= diff --git a/nuget.config b/nuget.config deleted file mode 100644 index 022f9240..00000000 --- a/nuget.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 00000000..1d72e683 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,37 @@ +# 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. +# +# Do NOT modify this file. Update the canonical version in Home\repo-template\src\.editorconfig +# then update all of the repos. + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.{cs,vb}] +dotnet_sort_system_directives_first = true + +[*.cs] +csharp_indent_case_contents = true : error +csharp_indent_switch_labels = true : error +csharp_new_line_before_open_brace = all +csharp_prefer_braces = true : error +csharp_style_expression_bodied_methods = when_on_single_line : suggestion +csharp_style_expression_bodied_constructors = when_on_single_line : suggestion +csharp_style_expression_bodied_operators = when_on_single_line : suggestion +csharp_style_expression_bodied_properties = when_on_single_line : suggestion +csharp_style_expression_bodied_indexers = when_on_single_line : suggestion +csharp_style_expression_bodied_accessors = when_on_single_line : suggestion +csharp_style_var_elsewhere = true : suggestion +csharp_style_var_for_built_in_types = true : suggestion +csharp_style_var_when_type_is_apparent = true : suggestion +dotnet_style_qualification_for_event = true : error +dotnet_style_qualification_for_field = true : error +dotnet_style_qualification_for_method = true : error +dotnet_style_qualification_for_property = true : error + +[*.targets] +indent_size = 2 diff --git a/src/Custom.Build.props b/src/Custom.Build.props deleted file mode 100644 index 889fb62e..00000000 --- a/src/Custom.Build.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - true - - diff --git a/src/Directory.Build.props b/src/Directory.Build.props deleted file mode 100644 index b3c6287c..00000000 --- a/src/Directory.Build.props +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - Debug - false - MSB3246 - - $(MSBuildProjectName) - $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\)) - $(BaseOutputPath)obj\$(ProjectName)\ - $(BaseOutputPath)$(Configuration)\ - - WiX Toolset Team - WiX Toolset - Copyright (c) .NET Foundation and contributors. All rights reserved. - MS-RL - WiX Toolset - - - - - diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets deleted file mode 100644 index 2fcc765a..00000000 --- a/src/Directory.Build.targets +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - true - $(SolutionPath) - $(NCrunchOriginalSolutionPath) - - - - - - - $([System.IO.File]::ReadAllText($(TheSolutionPath))) - $([System.IO.Path]::GetDirectoryName( $(TheSolutionPath) )) - (?<="[PackageName]", ")(.*)(?=", ") - - - - - - %(Identity) - $(SolutionFileContent.Contains('\%(Identity).csproj')) - - - - - $(RegexPattern.Replace('[PackageName]','%(PackageName)') ) - $([System.Text.RegularExpressions.Regex]::Match('$(SolutionFileContent)', '%(Pattern)')) - - - - - - - - - - - - - - diff --git a/src/Directory.csproj.props b/src/Directory.csproj.props deleted file mode 100644 index 81d24ad1..00000000 --- a/src/Directory.csproj.props +++ /dev/null @@ -1,13 +0,0 @@ - - - - - true - true - $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk)) - false - - diff --git a/src/Directory.csproj.targets b/src/Directory.csproj.targets deleted file mode 100644 index c3270426..00000000 --- a/src/Directory.csproj.targets +++ /dev/null @@ -1,26 +0,0 @@ - - - - - false - $(OutputPath)\$(AssemblyName).xml - - - - - $(PrivateRepositoryUrl.Replace('.git','')) - - $(MSBuildProjectName).nuspec - $(OutputPath)..\ - $(NuspecProperties);Id=$(PackageId);Authors=$(Authors);Copyright=$(Copyright);Description=$(Description);Title=$(Title) - $(NuspecProperties);Version=$(PackageVersion);RepositoryCommit=$(SourceRevisionId);RepositoryType=$(RepositoryType);RepositoryUrl=$(PrivateRepositoryUrl);ProjectFolder=$(MSBuildProjectDirectory)\;ProjectUrl=$(ProjectUrl) - true - snupkg - - - - diff --git a/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs deleted file mode 100644 index 0da78797..00000000 --- a/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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.Burn -{ - using System; - using System.Xml; - using WixToolset.Data.Symbols; - - internal abstract class BaseSearchFacade : ISearchFacade - { - protected WixSearchSymbol SearchSymbol { get; set; } - - public virtual void WriteXml(XmlTextWriter writer) - { - writer.WriteAttributeString("Id", this.SearchSymbol.Id.Id); - writer.WriteAttributeString("Variable", this.SearchSymbol.Variable); - if (!String.IsNullOrEmpty(this.SearchSymbol.Condition)) - { - writer.WriteAttributeString("Condition", this.SearchSymbol.Condition); - } - if (!String.IsNullOrEmpty(this.SearchSymbol.BundleExtensionRef)) - { - writer.WriteAttributeString("ExtensionId", this.SearchSymbol.BundleExtensionRef); - } - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs deleted file mode 100644 index 4a4f06f3..00000000 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ /dev/null @@ -1,650 +0,0 @@ -// 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.Burn -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Core.Burn.Bind; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Burn.Interfaces; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Binds a this.bundle. - /// - internal class BindBundleCommand - { - public BindBundleCommand(IBindContext context, IEnumerable backedExtensions) - { - this.ServiceProvider = context.ServiceProvider; - - this.Messaging = context.ServiceProvider.GetService(); - - this.BackendHelper = context.ServiceProvider.GetService(); - this.InternalBurnBackendHelper = context.ServiceProvider.GetService(); - this.PayloadHarvester = context.ServiceProvider.GetService(); - - this.DefaultCompressionLevel = context.DefaultCompressionLevel; - this.DelayedFields = context.DelayedFields; - this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; - this.IntermediateFolder = context.IntermediateFolder; - this.Output = context.IntermediateRepresentation; - this.OutputPath = context.OutputPath; - this.OutputPdbPath = context.PdbPath; - //this.VariableResolver = context.VariableResolver; - - this.BackendExtensions = backedExtensions; - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } - - private IPayloadHarvester PayloadHarvester { get; } - - private CompressionLevel? DefaultCompressionLevel { get; } - - public IEnumerable DelayedFields { get; } - - public IEnumerable ExpectedEmbeddedFiles { get; } - - private IEnumerable BackendExtensions { get; } - - private Intermediate Output { get; } - - private string OutputPath { get; } - - private string OutputPdbPath { get; } - - private string IntermediateFolder { get; } - - private IVariableResolver VariableResolver { get; } - - public IReadOnlyCollection FileTransfers { get; private set; } - - public IReadOnlyCollection TrackedFiles { get; private set; } - - public WixOutput Wixout { get; private set; } - - public void Execute() - { - var section = this.Output.Sections.Single(); - - var fileTransfers = new List(); - var trackedFiles = new List(); - - // First look for data we expect to find... Chain, WixGroups, etc. - - // We shouldn't really get past the linker phase if there are - // no group items... that means that there's no UX, no Chain, - // *and* no Containers! - var chainPackageSymbols = this.GetRequiredSymbols(); - - var wixGroupSymbols = this.GetRequiredSymbols(); - - // Ensure there is one and only one WixBundleSymbol. - // The compiler and linker behavior should have colluded to get - // this behavior. - var bundleSymbol = this.GetSingleSymbol(); - - bundleSymbol.ProviderKey = bundleSymbol.BundleId = Guid.NewGuid().ToString("B").ToUpperInvariant(); - - bundleSymbol.Attributes |= WixBundleAttributes.PerMachine; // default to per-machine but the first-per user package wil flip the bundle per-user. - - // Ensure there is one and only one WixBootstrapperApplicationDllSymbol. - // The compiler and linker behavior should have colluded to get - // this behavior. - var bundleApplicationDllSymbol = this.GetSingleSymbol(); - - // Ensure there is one and only one WixChainSymbol. - // The compiler and linker behavior should have colluded to get - // this behavior. - var chainSymbol = this.GetSingleSymbol(); - - if (this.Messaging.EncounteredError) - { - return; - } - - // If there are any fields to resolve later, create the cache to populate during bind. - var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; - - IEnumerable orderedSearches; - IDictionary> extensionSearchSymbolsById; - { - var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section); - orderSearchesCommand.Execute(); - - orderedSearches = orderSearchesCommand.OrderedSearchFacades; - extensionSearchSymbolsById = orderSearchesCommand.ExtensionSearchSymbolsByExtensionId; - } - - // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). - { - var extractedFiles = this.BackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); - - trackedFiles.AddRange(extractedFiles); - } - - // Get the explicit payloads. - var payloadSymbols = section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); - - var layoutDirectory = Path.GetDirectoryName(this.OutputPath); - - // Process the explicitly authored payloads. - ISet processedPayloads; - { - var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory); - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - - processedPayloads = new HashSet(payloadSymbols.Keys); - } - - IDictionary facades; - { - var command = new GetPackageFacadesCommand(this.Messaging, chainPackageSymbols, section); - command.Execute(); - - facades = command.PackageFacades; - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Process each package facade. Note this is likely to add payloads and other symbols so - // note that any indexes created above may be out of date now. - foreach (var facade in facades.Values) - { - switch (facade.PackageSymbol.Type) - { - case WixBundlePackageType.Exe: - { - var command = new ProcessExePackageCommand(facade, payloadSymbols); - command.Execute(); - } - break; - - case WixBundlePackageType.Msi: - { - var command = new ProcessMsiPackageCommand(this.ServiceProvider, this.BackendExtensions, section, facade, packagesPayloads[facade.PackageId]); - command.Execute(); - } - break; - - case WixBundlePackageType.Msp: - { - var command = new ProcessMspPackageCommand(this.Messaging, section, facade, payloadSymbols); - command.Execute(); - } - break; - - case WixBundlePackageType.Msu: - { - var command = new ProcessMsuPackageCommand(facade, payloadSymbols); - command.Execute(); - } - break; - } - - if (null != variableCache) - { - BindBundleCommand.PopulatePackageVariableCache(facade, variableCache); - } - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) - // are present. - payloadSymbols = section.Symbols.OfType().ToDictionary(t => t.Id.Id); - wixGroupSymbols = this.GetRequiredSymbols(); - packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); - - // Process the payloads that were added by processing the packages. - { - var toProcess = payloadSymbols.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList(); - - var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory); - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - - processedPayloads = null; - } - - // Set the package metadata from the payloads now that we have the complete payload information. - { - foreach (var facade in facades.Values) - { - facade.PackageSymbol.Size = 0; - - var packagePayloads = packagesPayloads[facade.PackageId]; - - foreach (var payload in packagePayloads.Values) - { - facade.PackageSymbol.Size += payload.FileSize.Value; - } - - if (!facade.PackageSymbol.InstallSize.HasValue) - { - facade.PackageSymbol.InstallSize = facade.PackageSymbol.Size; - } - - var packagePayload = payloadSymbols[facade.PackageSymbol.PayloadRef]; - - if (String.IsNullOrEmpty(facade.PackageSymbol.Description)) - { - facade.PackageSymbol.Description = packagePayload.Description; - } - - if (String.IsNullOrEmpty(facade.PackageSymbol.DisplayName)) - { - facade.PackageSymbol.DisplayName = packagePayload.DisplayName; - } - } - } - - // Give the UX payloads their embedded IDs... - var uxPayloadIndex = 0; - { - foreach (var payload in payloadSymbols.Values.Where(p => BurnConstants.BurnUXContainerName == p.ContainerRef)) - { - // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even - // downloaded. The current engine requires the UX to be fully present before any downloading starts, - // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads - // into the temporary UX directory correctly, so we don't allow external either. - if (PackagingType.Embedded != payload.Packaging) - { - this.Messaging.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.SourceFile.Path)); - payload.Packaging = PackagingType.Embedded; - } - - payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex); - ++uxPayloadIndex; - } - - if (0 == uxPayloadIndex) - { - // If we didn't get any UX payloads, it's an error! - throw new WixException(ErrorMessages.MissingBundleInformation("BootstrapperApplication")); - } - - // Give the embedded payloads without an embedded id yet an embedded id. - var payloadIndex = 0; - foreach (var payload in payloadSymbols.Values) - { - Debug.Assert(PackagingType.Unknown != payload.Packaging); - - if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) - { - payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAuthoredContainerEmbeddedIdFormat, payloadIndex); - ++payloadIndex; - } - } - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Determine patches to automatically slipstream. - { - var command = new AutomaticallySlipstreamPatchesCommand(section, facades.Values); - command.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - IEnumerable orderedFacades; - IEnumerable boundaries; - { - var command = new OrderPackagesAndRollbackBoundariesCommand(this.Messaging, section, facades); - command.Execute(); - - orderedFacades = command.OrderedPackageFacades; - boundaries = command.UsedRollbackBoundaries; - } - - // Resolve any delayed fields before generating the manifest. - if (this.DelayedFields.Any()) - { - this.BackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); - } - - { - var command = new ProcessDependencyProvidersCommand(this.Messaging, section, facades); - command.Execute(); - - if (!String.IsNullOrEmpty(command.BundleProviderKey)) - { - bundleSymbol.ProviderKey = command.BundleProviderKey; // set the overridable bundle provider key. - } - } - - // Update the bundle per-machine/per-user scope based on the chained packages. - this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades); - - var softwareTags = section.Symbols.OfType().ToList(); - if (softwareTags.Any()) - { - var command = new ProcessBundleSoftwareTagsCommand(section, softwareTags); - command.Execute(); - } - - this.DetectDuplicateCacheIds(facades); - - if (this.Messaging.EncounteredError) - { - return; - } - - // Give the extension one last hook before generating the output files. - foreach (var extension in this.BackendExtensions) - { - extension.SymbolsFinalized(section); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Generate data for all manifests. - { - var command = new GenerateManifestDataFromIRCommand(this.Messaging, section, this.BackendExtensions, this.InternalBurnBackendHelper, extensionSearchSymbolsById); - command.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Generate the core-defined BA manifest tables... - string baManifestPath; - { - var command = new CreateBootstrapperApplicationManifestCommand(section, bundleSymbol, orderedFacades, uxPayloadIndex, payloadSymbols, packagesPayloads, this.IntermediateFolder, this.InternalBurnBackendHelper); - command.Execute(); - - var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; - baManifestPath = command.OutputPath; - payloadSymbols.Add(baManifestPayload.Id.Id, baManifestPayload); - ++uxPayloadIndex; - - trackedFiles.Add(this.BackendHelper.TrackFile(baManifestPath, TrackedFileType.Temporary)); - } - - // Generate the bundle extension manifest... - string bextManifestPath; - { - var command = new CreateBundleExtensionManifestCommand(section, bundleSymbol, uxPayloadIndex, this.IntermediateFolder, this.InternalBurnBackendHelper); - command.Execute(); - - var bextManifestPayload = command.BundleExtensionManifestPayloadRow; - bextManifestPath = command.OutputPath; - payloadSymbols.Add(bextManifestPayload.Id.Id, bextManifestPayload); - ++uxPayloadIndex; - - trackedFiles.Add(this.BackendHelper.TrackFile(bextManifestPath, TrackedFileType.Temporary)); - } - - var containers = section.Symbols.OfType().ToDictionary(t => t.Id.Id); - { - var command = new DetectPayloadCollisionsCommand(this.Messaging, containers, facades.Values, payloadSymbols, packagesPayloads); - command.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Create all the containers except the UX container first so the manifest (that goes in the UX container) - // can contain all size and hash information about the non-UX containers. - WixBundleContainerSymbol uxContainer; - IEnumerable uxPayloads; - { - var command = new CreateNonUXContainers(this.BackendHelper, this.Messaging, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - - uxContainer = command.UXContainer; - uxPayloads = command.UXContainerPayloads; - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Resolve the download URLs now that we have all of the containers and payloads calculated. - { - var command = new ResolveDownloadUrlsCommand(this.Messaging, this.BackendExtensions, containers.Values, payloadSymbols); - command.Execute(); - } - - // Create the bundle manifest. - string manifestPath; - { - var executableName = Path.GetFileName(this.OutputPath); - - var command = new CreateBurnManifestCommand(executableName, section, bundleSymbol, containers.Values, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, packagesPayloads, orderedSearches, this.IntermediateFolder); - command.Execute(); - - manifestPath = command.OutputPath; - trackedFiles.Add(this.BackendHelper.TrackFile(manifestPath, TrackedFileType.Temporary)); - } - - // Create the UX container. - { - var command = new CreateContainerCommand(manifestPath, uxPayloads, uxContainer.WorkingPath, this.DefaultCompressionLevel); - command.Execute(); - - uxContainer.Hash = command.Hash; - uxContainer.Size = command.Size; - - trackedFiles.Add(this.BackendHelper.TrackFile(uxContainer.WorkingPath, TrackedFileType.Temporary)); - } - - { - var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers.Values); - command.Execute(); - - fileTransfers.Add(command.Transfer); - trackedFiles.Add(this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final)); - } - -#if TODO // does this need to come back, or do they only need to be in TrackedFiles? - this.ContentFilePaths = payloadSymbols.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); -#endif - this.FileTransfers = fileTransfers; - this.TrackedFiles = trackedFiles; - this.Wixout = this.CreateWixout(trackedFiles, this.Output, manifestPath, baManifestPath, bextManifestPath); - } - - private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, string manifestPath, string baDataPath, string bextDataPath) - { - WixOutput wixout; - - if (String.IsNullOrEmpty(this.OutputPdbPath)) - { - wixout = WixOutput.Create(); - } - else - { - var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); - trackedFiles.Add(trackPdb); - - wixout = WixOutput.Create(trackPdb.Path); - } - - intermediate.Save(wixout); - - wixout.ImportDataStream(BurnConstants.BurnManifestWixOutputStreamName, manifestPath); - wixout.ImportDataStream(BurnConstants.BootstrapperApplicationDataWixOutputStreamName, baDataPath); - wixout.ImportDataStream(BurnConstants.BundleExtensionDataWixOutputStreamName, bextDataPath); - - wixout.Reopen(); - - return wixout; - } - - /// - /// Populates the variable cache with specific package properties. - /// - /// The package facade with properties to cache. - /// The property cache. - private static void PopulatePackageVariableCache(PackageFacade facade, IDictionary variableCache) - { - var package = facade.PackageSymbol; - var id = package.Id.Id; - - variableCache.Add(String.Concat("packageDescription.", id), package.Description ?? String.Empty); - variableCache.Add(String.Concat("packageName.", id), package.DisplayName ?? String.Empty); - variableCache.Add(String.Concat("packageVersion.", id), package.Version); - - if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) - { - variableCache.Add(String.Concat("packageLanguage.", id), msiPackage.ProductLanguage.ToString()); - variableCache.Add(String.Concat("packageManufacturer.", id), msiPackage.Manufacturer ?? String.Empty); - } - else - { - variableCache.Add(String.Concat("packageLanguage.", id), String.Empty); - variableCache.Add(String.Concat("packageManufacturer.", id), String.Empty); - } - } - - private void ResolveBundleInstallScope(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable facades) - { - var dependencySymbolsById = section.Symbols.OfType().ToDictionary(t => t.Id.Id); - - foreach (var facade in facades) - { - if (bundleSymbol.PerMachine && YesNoDefaultType.No == facade.PackageSymbol.PerMachine) - { - this.Messaging.Write(VerboseMessages.SwitchingToPerUserPackage(facade.PackageSymbol.SourceLineNumbers, facade.PackageId)); - - bundleSymbol.Attributes &= ~WixBundleAttributes.PerMachine; - break; - } - } - - foreach (var facade in facades) - { - // Update package scope from bundle scope if default. - if (YesNoDefaultType.Default == facade.PackageSymbol.PerMachine) - { - facade.PackageSymbol.PerMachine = bundleSymbol.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; - } - - // We will only register packages in the same scope as the bundle. Warn if any packages with providers - // are in a different scope and not permanent (permanents typically don't need a ref-count). - if (!bundleSymbol.PerMachine && - YesNoDefaultType.Yes == facade.PackageSymbol.PerMachine && - !facade.PackageSymbol.Permanent && - dependencySymbolsById.ContainsKey(facade.PackageId)) - { - this.Messaging.Write(WarningMessages.NoPerMachineDependencies(facade.PackageSymbol.SourceLineNumbers, facade.PackageId)); - } - } - } - - private void DetectDuplicateCacheIds(IDictionary facades) - { - var duplicateCacheIdDetector = new Dictionary(); - - foreach (var facade in facades.Values) - { - if (duplicateCacheIdDetector.TryGetValue(facade.PackageSymbol.CacheId, out var collisionPackage)) - { - this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.CacheId, facade.PackageId)); - this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds2(collisionPackage.SourceLineNumbers)); - } - else - { - duplicateCacheIdDetector.Add(facade.PackageSymbol.CacheId, facade.PackageSymbol); - } - } - } - - private IEnumerable GetRequiredSymbols() where T : IntermediateSymbol - { - var symbols = this.Output.Sections.Single().Symbols.OfType().ToList(); - - if (0 == symbols.Count) - { - throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); - } - - return symbols; - } - - private T GetSingleSymbol() where T : IntermediateSymbol - { - var symbols = this.Output.Sections.Single().Symbols.OfType().ToList(); - - if (1 != symbols.Count) - { - throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); - } - - return symbols[0]; - } - - private static Dictionary> RecalculatePackagesPayloads(Dictionary payloadSymbols, IEnumerable wixGroupSymbols) - { - var packagesPayloads = new Dictionary>(); - - foreach (var groupSymbol in wixGroupSymbols) - { - if (ComplexReferenceChildType.Payload == groupSymbol.ChildType) - { - var payloadSymbol = payloadSymbols[groupSymbol.ChildId]; - - if (ComplexReferenceParentType.Package == groupSymbol.ParentType) - { - if (!packagesPayloads.TryGetValue(groupSymbol.ParentId, out var packagePayloadsById)) - { - packagePayloadsById = new Dictionary(); - packagesPayloads.Add(groupSymbol.ParentId, packagePayloadsById); - } - - packagePayloadsById.Add(payloadSymbol.Id.Id, payloadSymbol); - } - } - } - - return packagesPayloads; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs deleted file mode 100644 index 773250d7..00000000 --- a/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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.Burn -{ - using System.Xml; - using WixToolset.Data.Symbols; - - internal class ExtensionSearchFacade : BaseSearchFacade - { - public ExtensionSearchFacade(WixSearchSymbol searchSymbol) - { - this.SearchSymbol = searchSymbol; - } - - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("ExtensionSearch"); - - base.WriteXml(writer); - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs deleted file mode 100644 index a76f84ec..00000000 --- a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs +++ /dev/null @@ -1,237 +0,0 @@ -// 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.Burn.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Xml; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Burn.ExtensibilityServices; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class GenerateManifestDataFromIRCommand - { - public GenerateManifestDataFromIRCommand(IMessaging messaging, IntermediateSection section, IEnumerable backendExtensions, IBurnBackendHelper backendHelper, IDictionary> extensionSearchSymbolsById) - { - this.Messaging = messaging; - this.Section = section; - this.BackendExtensions = backendExtensions; - this.BackendHelper = backendHelper; - this.ExtensionSearchSymbolsById = extensionSearchSymbolsById; - } - - private IEnumerable BackendExtensions { get; } - - private IBurnBackendHelper BackendHelper { get; } - - private IDictionary> ExtensionSearchSymbolsById { get; } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - public void Execute() - { - var symbols = this.Section.Symbols.ToList(); - var cellsByCustomDataAndElementId = new Dictionary>(); - var customDataById = new Dictionary(); - - foreach (var kvp in this.ExtensionSearchSymbolsById) - { - var extensionId = kvp.Key; - var extensionSearchSymbols = kvp.Value; - foreach (var extensionSearchSymbol in extensionSearchSymbols) - { - this.BackendHelper.AddBundleExtensionData(extensionId, extensionSearchSymbol, symbolIdIsIdAttribute: true); - symbols.Remove(extensionSearchSymbol); - } - } - - foreach (var symbol in symbols) - { - var unknownSymbol = false; - switch (symbol.Definition.Type) - { - // Symbols used internally and are not added to a data manifest. - case SymbolDefinitionType.ProvidesDependency: - case SymbolDefinitionType.WixApprovedExeForElevation: - case SymbolDefinitionType.WixBootstrapperApplication: - case SymbolDefinitionType.WixBootstrapperApplicationDll: - case SymbolDefinitionType.WixBundle: - case SymbolDefinitionType.WixBundleContainer: - case SymbolDefinitionType.WixBundleCustomDataAttribute: - case SymbolDefinitionType.WixBundleExePackage: - case SymbolDefinitionType.WixBundleExePackagePayload: - case SymbolDefinitionType.WixBundleExtension: - case SymbolDefinitionType.WixBundleMsiFeature: - case SymbolDefinitionType.WixBundleMsiPackage: - case SymbolDefinitionType.WixBundleMsiPackagePayload: - case SymbolDefinitionType.WixBundleMsiProperty: - case SymbolDefinitionType.WixBundleMspPackage: - case SymbolDefinitionType.WixBundleMspPackagePayload: - case SymbolDefinitionType.WixBundleMsuPackage: - case SymbolDefinitionType.WixBundleMsuPackagePayload: - case SymbolDefinitionType.WixBundlePackage: - case SymbolDefinitionType.WixBundlePackageCommandLine: - case SymbolDefinitionType.WixBundlePackageExitCode: - case SymbolDefinitionType.WixBundlePackageGroup: - case SymbolDefinitionType.WixBundlePatchTargetCode: - case SymbolDefinitionType.WixBundlePayload: - case SymbolDefinitionType.WixBundlePayloadGroup: - case SymbolDefinitionType.WixBundleRelatedPackage: - case SymbolDefinitionType.WixBundleRollbackBoundary: - case SymbolDefinitionType.WixBundleSlipstreamMsp: - case SymbolDefinitionType.WixBundleTag: - case SymbolDefinitionType.WixBundleUpdate: - case SymbolDefinitionType.WixBundleVariable: - case SymbolDefinitionType.WixBuildInfo: - case SymbolDefinitionType.WixChain: - case SymbolDefinitionType.WixComponentSearch: - case SymbolDefinitionType.WixDependencyProvider: - case SymbolDefinitionType.WixFileSearch: - case SymbolDefinitionType.WixGroup: - case SymbolDefinitionType.WixProductSearch: - case SymbolDefinitionType.WixRegistrySearch: - case SymbolDefinitionType.WixRelatedBundle: - case SymbolDefinitionType.WixSearch: - case SymbolDefinitionType.WixSearchRelation: - case SymbolDefinitionType.WixSetVariable: - case SymbolDefinitionType.WixUpdateRegistration: - break; - - // Symbols used before binding. - case SymbolDefinitionType.WixComplexReference: - case SymbolDefinitionType.WixOrdering: - case SymbolDefinitionType.WixSimpleReference: - case SymbolDefinitionType.WixVariable: - break; - - // Symbols to investigate: - case SymbolDefinitionType.WixChainItem: - break; - - case SymbolDefinitionType.WixBundleCustomData: - unknownSymbol = !this.IndexBundleCustomDataSymbol((WixBundleCustomDataSymbol)symbol, customDataById); - break; - - case SymbolDefinitionType.WixBundleCustomDataCell: - this.IndexBundleCustomDataCellSymbol((WixBundleCustomDataCellSymbol)symbol, cellsByCustomDataAndElementId); - break; - - case SymbolDefinitionType.MustBeFromAnExtension: - unknownSymbol = !this.AddSymbolFromExtension(symbol); - break; - - default: - unknownSymbol = true; - break; - } - - if (unknownSymbol) - { - this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol)); - } - } - - this.AddIndexedCellSymbols(customDataById, cellsByCustomDataAndElementId); - } - - private bool IndexBundleCustomDataSymbol(WixBundleCustomDataSymbol wixBundleCustomDataSymbol, Dictionary customDataById) - { - switch (wixBundleCustomDataSymbol.Type) - { - case WixBundleCustomDataType.BootstrapperApplication: - case WixBundleCustomDataType.BundleExtension: - break; - default: - return false; - } - - var customDataId = wixBundleCustomDataSymbol.Id.Id; - customDataById.Add(customDataId, wixBundleCustomDataSymbol); - return true; - } - - private void IndexBundleCustomDataCellSymbol(WixBundleCustomDataCellSymbol wixBundleCustomDataCellSymbol, Dictionary> cellsByCustomDataAndElementId) - { - var tableAndRowId = wixBundleCustomDataCellSymbol.CustomDataRef + "/" + wixBundleCustomDataCellSymbol.ElementId; - if (!cellsByCustomDataAndElementId.TryGetValue(tableAndRowId, out var cells)) - { - cells = new List(); - cellsByCustomDataAndElementId.Add(tableAndRowId, cells); - } - - cells.Add(wixBundleCustomDataCellSymbol); - } - - private void AddIndexedCellSymbols(Dictionary customDataById, Dictionary> cellsByCustomDataAndElementId) - { - foreach (var elementValues in cellsByCustomDataAndElementId.Values) - { - var elementName = elementValues[0].CustomDataRef; - var customDataSymbol = customDataById[elementName]; - - var attributeNames = customDataSymbol.AttributeNamesSeparated; - - var elementValuesByAttribute = elementValues.ToDictionary(t => t.AttributeRef, t => t.Value); - - var sb = new StringBuilder(); - using (var writer = XmlWriter.Create(sb, BurnBackendHelper.WriterSettings)) - { - switch (customDataSymbol.Type) - { - case WixBundleCustomDataType.BootstrapperApplication: - writer.WriteStartElement(elementName, BurnCommon.BADataNamespace); - break; - case WixBundleCustomDataType.BundleExtension: - writer.WriteStartElement(elementName, BurnCommon.BundleExtensionDataNamespace); - break; - default: - throw new NotImplementedException(); - } - - // Write all row data as attributes in table column order. - foreach (var attributeName in attributeNames) - { - if (elementValuesByAttribute.TryGetValue(attributeName, out var value)) - { - writer.WriteAttributeString(attributeName, value); - } - } - - writer.WriteEndElement(); - } - - switch (customDataSymbol.Type) - { - case WixBundleCustomDataType.BootstrapperApplication: - this.BackendHelper.AddBootstrapperApplicationData(sb.ToString()); - break; - case WixBundleCustomDataType.BundleExtension: - this.BackendHelper.AddBundleExtensionData(customDataSymbol.BundleExtensionRef, sb.ToString()); - break; - default: - throw new NotImplementedException(); - } - } - } - - private bool AddSymbolFromExtension(IntermediateSymbol symbol) - { - foreach (var extension in this.BackendExtensions) - { - if (extension.TryProcessSymbol(this.Section, symbol)) - { - return true; - } - } - - return false; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs b/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs deleted file mode 100644 index 24d6f542..00000000 --- a/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs +++ /dev/null @@ -1,185 +0,0 @@ -// 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.Burn -{ - using System; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class LegacySearchFacade : BaseSearchFacade - { - public LegacySearchFacade(WixSearchSymbol searchSymbol, IntermediateSymbol searchSpecificSymbol) - { - this.SearchSymbol = searchSymbol; - this.SearchSpecificSymbol = searchSpecificSymbol; - } - - public IntermediateSymbol SearchSpecificSymbol { get; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup a search. - /// - /// - public override void WriteXml(XmlTextWriter writer) - { - switch (this.SearchSpecificSymbol) - { - case WixComponentSearchSymbol symbol: - this.WriteComponentSearchXml(writer, symbol); - break; - case WixFileSearchSymbol symbol: - this.WriteFileSearchXml(writer, symbol); - break; - case WixProductSearchSymbol symbol: - this.WriteProductSearchXml(writer, symbol); - break; - case WixRegistrySearchSymbol symbol: - this.WriteRegistrySearchXml(writer, symbol); - break; - } - } - - private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchSymbol searchSymbol) - { - writer.WriteStartElement("MsiComponentSearch"); - - base.WriteXml(writer); - - writer.WriteAttributeString("ComponentId", searchSymbol.Guid); - - if (!String.IsNullOrEmpty(searchSymbol.ProductCode)) - { - writer.WriteAttributeString("ProductCode", searchSymbol.ProductCode); - } - - if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.KeyPath)) - { - writer.WriteAttributeString("Type", "keyPath"); - } - else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.WantDirectory)) - { - writer.WriteAttributeString("Type", "directory"); - } - - writer.WriteEndElement(); - } - - private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchSymbol searchSymbol) - { - writer.WriteStartElement((0 == (searchSymbol.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); - - base.WriteXml(writer); - - writer.WriteAttributeString("Path", searchSymbol.Path); - if (WixFileSearchAttributes.WantExists == (searchSymbol.Attributes & WixFileSearchAttributes.WantExists)) - { - writer.WriteAttributeString("Type", "exists"); - } - else if (WixFileSearchAttributes.WantVersion == (searchSymbol.Attributes & WixFileSearchAttributes.WantVersion)) - { - // Can never get here for DirectorySearch. - writer.WriteAttributeString("Type", "version"); - } - else - { - writer.WriteAttributeString("Type", "path"); - } - writer.WriteEndElement(); - } - - private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchSymbol symbol) - { - writer.WriteStartElement("MsiProductSearch"); - - base.WriteXml(writer); - - if (0 != (symbol.Attributes & WixProductSearchAttributes.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", symbol.Guid); - } - else - { - writer.WriteAttributeString("ProductCode", symbol.Guid); - } - - if (0 != (symbol.Attributes & WixProductSearchAttributes.Version)) - { - writer.WriteAttributeString("Type", "version"); - } - else if (0 != (symbol.Attributes & WixProductSearchAttributes.Language)) - { - writer.WriteAttributeString("Type", "language"); - } - else if (0 != (symbol.Attributes & WixProductSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (symbol.Attributes & WixProductSearchAttributes.Assignment)) - { - writer.WriteAttributeString("Type", "assignment"); - } - - writer.WriteEndElement(); - } - - private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchSymbol symbol) - { - writer.WriteStartElement("RegistrySearch"); - - base.WriteXml(writer); - - switch (symbol.Root) - { - case RegistryRootType.ClassesRoot: - writer.WriteAttributeString("Root", "HKCR"); - break; - case RegistryRootType.CurrentUser: - writer.WriteAttributeString("Root", "HKCU"); - break; - case RegistryRootType.LocalMachine: - writer.WriteAttributeString("Root", "HKLM"); - break; - case RegistryRootType.Users: - writer.WriteAttributeString("Root", "HKU"); - break; - } - - writer.WriteAttributeString("Key", symbol.Key); - - if (!String.IsNullOrEmpty(symbol.Value)) - { - writer.WriteAttributeString("Value", symbol.Value); - } - - var existenceOnly = 0 != (symbol.Attributes & WixRegistrySearchAttributes.WantExists); - - writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); - - if (0 != (symbol.Attributes & WixRegistrySearchAttributes.Win64)) - { - writer.WriteAttributeString("Win64", "yes"); - } - - if (!existenceOnly) - { - if (0 != (symbol.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) - { - writer.WriteAttributeString("ExpandEnvironment", "yes"); - } - - // We *always* say this is VariableType="string". If we end up - // needing to be more specific, we will have to expand the "Format" - // attribute to allow "number" and "version". - - writer.WriteAttributeString("VariableType", "string"); - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs b/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs deleted file mode 100644 index f9ff23cb..00000000 --- a/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs +++ /dev/null @@ -1,133 +0,0 @@ -// 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.Burn.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class ProcessBundleSoftwareTagsCommand - { - public ProcessBundleSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags) - { - this.Section = section; - this.SoftwareTags = softwareTags; - } - - private IntermediateSection Section { get; } - - private IEnumerable SoftwareTags { get; } - - public void Execute() - { - var bundleInfo = this.Section.Symbols.OfType().FirstOrDefault(); - var bundleId = NormalizeGuid(bundleInfo.BundleId); - var upgradeCode = NormalizeGuid(bundleInfo.UpgradeCode); - - var uniqueId = String.Concat("wix:bundle/", bundleId); - var persistentId = String.Concat("wix:bundle.upgrade/", upgradeCode); - - // Try to collect all the software id tags from all the child packages. - var containedTags = CollectPackageTags(this.Section); - - foreach (var bundleTag in this.SoftwareTags) - { - using (var ms = new MemoryStream()) - { - CreateTagFile(ms, uniqueId, bundleInfo.Name, bundleInfo.Version, bundleTag.Regid, bundleInfo.Manufacturer, persistentId, containedTags); - bundleTag.Xml = Encoding.UTF8.GetString(ms.ToArray()); - } - } - } - - private static string NormalizeGuid(string guidString) - { - if (Guid.TryParse(guidString, out var guid)) - { - return guid.ToString("D").ToUpperInvariant(); - } - - return guidString; - } - - private static IEnumerable CollectPackageTags(IntermediateSection section) - { - var tags = new List(); - - var msiPackages = section.Symbols.OfType().Where(s => s.Type == WixBundlePackageType.Msi).ToList(); - if (msiPackages.Any()) - { - var payloadSymbolsById = section.Symbols.OfType().ToDictionary(s => s.Id.Id); - - foreach (var msiPackage in msiPackages) - { - var payload = payloadSymbolsById[msiPackage.PayloadRef]; - - using (var db = new Database(payload.SourceFile.Path, OpenDatabase.ReadOnly)) - { - using (var view = db.OpenExecuteView("SELECT `Regid`, `TagId` FROM `SoftwareIdentificationTag`")) - { - foreach (var record in view.Records) - { - tags.Add(new SoftwareTag { Regid = record.GetString(1), Id = record.GetString(2) }); - } - } - } - } - } - - return tags; - } - - private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId, IEnumerable containedTags) - { - var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; - - using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) - { - writer.WriteStartDocument(); - writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); - writer.WriteAttributeString("tagId", uniqueId); - writer.WriteAttributeString("name", name); - writer.WriteAttributeString("version", version); - writer.WriteAttributeString("versionScheme", versionScheme); - - writer.WriteStartElement("Entity"); - writer.WriteAttributeString("name", manufacturer); - writer.WriteAttributeString("regid", regid); - writer.WriteAttributeString("role", "softwareCreator tagCreator"); - writer.WriteEndElement(); // - - if (!String.IsNullOrEmpty(persistendId)) - { - writer.WriteStartElement("Meta"); - writer.WriteAttributeString("persistentId", persistendId); - writer.WriteEndElement(); // - } - - foreach (var containedTag in containedTags) - { - writer.WriteStartElement("Link"); - writer.WriteAttributeString("rel", "component"); - writer.WriteAttributeString("href", String.Concat("swid:", containedTag.Id)); - writer.WriteEndElement(); // - } - - writer.WriteEndElement(); // - } - } - - private class SoftwareTag - { - public string Regid { get; set; } - - public string Id { get; set; } - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs b/src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs deleted file mode 100644 index 99effbc7..00000000 --- a/src/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs +++ /dev/null @@ -1,147 +0,0 @@ -// 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.Burn.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Extensibility.Services; - using WixToolset.Data.Symbols; - - internal class ProcessDependencyProvidersCommand - { - public ProcessDependencyProvidersCommand(IMessaging messaging, IntermediateSection section, IDictionary facades) - { - this.Messaging = messaging; - this.Section = section; - - this.Facades = facades; - } - - public string BundleProviderKey { get; private set; } - - public Dictionary DependencySymbolsByKey { get; private set; } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - private IDictionary Facades { get; } - - /// - /// Sets the explicitly provided bundle provider key, if provided. And... - /// Imports authored dependency providers for each package in the manifest, - /// and generates dependency providers for certain package types that do not - /// have a provider defined. - /// - public void Execute() - { - var dependencySymbols = this.Section.Symbols.OfType(); - - foreach (var dependency in dependencySymbols) - { - // Sets the provider key for the bundle, if it is not set already. - if (String.IsNullOrEmpty(this.BundleProviderKey)) - { - if (dependency.Bundle) - { - this.BundleProviderKey = dependency.ProviderKey; - } - } - - // Import any authored dependencies. These may merge with imported provides from MSI packages. - var packageId = dependency.ParentRef; - - if (this.Facades.TryGetValue(packageId, out var facade)) - { - if (String.IsNullOrEmpty(dependency.ProviderKey)) - { - switch (facade.SpecificPackageSymbol) - { - // The WixDependencyExtension allows an empty Key for MSIs and MSPs. - case WixBundleMsiPackageSymbol msiPackage: - dependency.ProviderKey = msiPackage.ProductCode; - break; - case WixBundleMspPackageSymbol mspPackage: - dependency.ProviderKey = mspPackage.PatchCode; - break; - } - } - - if (String.IsNullOrEmpty(dependency.Version)) - { - dependency.Version = facade.PackageSymbol.Version; - } - - // If the version is still missing, a version could not be gathered from the package and was not authored. - if (String.IsNullOrEmpty(dependency.Version)) - { - this.Messaging.Write(ErrorMessages.MissingDependencyVersion(facade.PackageId)); - } - - if (String.IsNullOrEmpty(dependency.DisplayName)) - { - dependency.DisplayName = facade.PackageSymbol.DisplayName; - } - } - } - - this.DependencySymbolsByKey = this.GetDependencySymbolsByKey(dependencySymbols); - - // Generate providers for MSI and MSP packages that still do not have providers. - foreach (var facade in this.Facades.Values) - { - string key = null; - - if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) - { - key = msiPackage.ProductCode; - } - else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) - { - key = mspPackage.PatchCode; - } - - if (!String.IsNullOrEmpty(key) && !this.DependencySymbolsByKey.ContainsKey(key)) - { - var dependency = this.Section.AddSymbol(new WixDependencyProviderSymbol(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.Id) - { - ParentRef = facade.PackageId, - ProviderKey = key, - Version = facade.PackageSymbol.Version, - DisplayName = facade.PackageSymbol.DisplayName - }); - - this.DependencySymbolsByKey.Add(dependency.ProviderKey, dependency); - } - } - } - - private Dictionary GetDependencySymbolsByKey(IEnumerable dependencySymbols) - { - var dependencySymbolsByKey = new Dictionary(); - - foreach (var dependency in dependencySymbols) - { - if (dependencySymbolsByKey.TryGetValue(dependency.ProviderKey, out var collision)) - { - // If not a perfect dependency collision, display an error. - if (dependency.ProviderKey != collision.ProviderKey || - dependency.Version != collision.Version || - dependency.DisplayName != collision.DisplayName) - { - this.Messaging.Write(ErrorMessages.DuplicateProviderDependencyKey(dependency.ProviderKey, dependency.ParentRef)); - } - } - else - { - dependencySymbolsByKey.Add(dependency.ProviderKey, dependency); - } - } - - return dependencySymbolsByKey; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs b/src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs deleted file mode 100644 index c678b114..00000000 --- a/src/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs +++ /dev/null @@ -1,128 +0,0 @@ -// 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.Burn.Bind -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class ResolveDownloadUrlsCommand - { - public ResolveDownloadUrlsCommand(IMessaging messaging, IEnumerable backendExtensions, IEnumerable containers, Dictionary payloadsById) - { - this.Messaging = messaging; - this.BackendExtensions = backendExtensions; - this.Containers = containers; - this.PayloadsById = payloadsById; - } - - private IMessaging Messaging { get; } - - private IEnumerable BackendExtensions { get; } - - private IEnumerable Containers { get; } - - private Dictionary PayloadsById { get; } - - public void Execute() - { - this.ResolveContainerUrls(); - - this.ResolvePayloadUrls(); - } - - private void ResolveContainerUrls() - { - foreach (var container in this.Containers) - { - if (container.Type == ContainerType.Detached) - { - var resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id.Id, container.Name); - if (!String.IsNullOrEmpty(resolvedUrl)) - { - container.DownloadUrl = resolvedUrl; - } - } - else if (container.Type == ContainerType.Attached) - { - if (!String.IsNullOrEmpty(container.DownloadUrl)) - { - this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id.Id)); - } - } - } - } - - private void ResolvePayloadUrls() - { - foreach (var payload in this.PayloadsById.Values) - { - if (payload.Packaging == PackagingType.Embedded && payload.ContainerRef == BurnConstants.BurnUXContainerName) - { - if (!String.IsNullOrEmpty(payload.DownloadUrl)) - { - this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(payload.SourceLineNumbers, payload.Id.Id)); - } - } - else - { - var packageId = payload.ParentPackagePayloadRef; - var parentUrl = payload.ParentPackagePayloadRef == null ? null : this.PayloadsById[payload.ParentPackagePayloadRef].DownloadUrl; - var resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id.Id, payload.Name); - if (!String.IsNullOrEmpty(resolvedUrl)) - { - payload.DownloadUrl = resolvedUrl; - } - } - } - } - - private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName) - { - string resolvedUrl = null; - - foreach (var extension in this.BackendExtensions) - { - resolvedUrl = extension.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName); - if (!String.IsNullOrEmpty(resolvedUrl)) - { - break; - } - } - - if (String.IsNullOrEmpty(resolvedUrl)) - { - // If a URL was not specified but there is a fallback URL that has a format specifier in it - // then use the fallback URL formatter for this URL. - if (String.IsNullOrEmpty(url) && !String.IsNullOrEmpty(fallbackUrl)) - { - var formattedFallbackUrl = String.Format(fallbackUrl, packageId, payloadId, fileName); - if (!String.Equals(fallbackUrl, formattedFallbackUrl, StringComparison.OrdinalIgnoreCase)) - { - url = fallbackUrl; - } - } - - if (!String.IsNullOrEmpty(url)) - { - var formattedUrl = String.Format(url, packageId, payloadId, fileName); - - if (Uri.TryCreate(formattedUrl, UriKind.Absolute, out var canonicalUri)) - { - resolvedUrl = canonicalUri.AbsoluteUri; - } - else - { - resolvedUrl = null; - } - } - } - - return resolvedUrl; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs deleted file mode 100644 index e88f26ef..00000000 --- a/src/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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.Burn -{ - using System.Xml; - using WixToolset.Data.Symbols; - - internal class SetVariableSearchFacade : BaseSearchFacade - { - public SetVariableSearchFacade(WixSearchSymbol searchSymbol, WixSetVariableSymbol setVariableSymbol) - { - this.SearchSymbol = searchSymbol; - this.SetVariableSymbol = setVariableSymbol; - } - - private WixSetVariableSymbol SetVariableSymbol { get; } - - public override void WriteXml(XmlTextWriter writer) - { - writer.WriteStartElement("SetVariable"); - - base.WriteXml(writer); - - if (this.SetVariableSymbol.Type != WixBundleVariableType.Unknown) - { - writer.WriteAttributeString("Value", this.SetVariableSymbol.Value); - - switch (this.SetVariableSymbol.Type) - { - case WixBundleVariableType.Formatted: - writer.WriteAttributeString("Type", "formatted"); - break; - case WixBundleVariableType.Numeric: - writer.WriteAttributeString("Type", "numeric"); - break; - case WixBundleVariableType.String: - writer.WriteAttributeString("Type", "string"); - break; - case WixBundleVariableType.Version: - writer.WriteAttributeString("Type", "version"); - break; - } - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/BundleBackend.cs b/src/WixToolset.Core.Burn/BundleBackend.cs deleted file mode 100644 index 60e9ea60..00000000 --- a/src/WixToolset.Core.Burn/BundleBackend.cs +++ /dev/null @@ -1,77 +0,0 @@ -// 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.Burn -{ - using System; - using System.IO; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Burn.Inscribe; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class BundleBackend : IBackend - { - public IBindResult Bind(IBindContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendBind(context); - } - - var command = new BindBundleCommand(context, backendExtensions); - command.Execute(); - - var result = context.ServiceProvider.GetService(); - result.FileTransfers = command.FileTransfers; - result.TrackedFiles = command.TrackedFiles; - result.Wixout = command.Wixout; - - foreach (var extension in backendExtensions) - { - extension.PostBackendBind(result); - } - - return result; - } - - public IDecompileResult Decompile(IDecompileContext context) - { - throw new NotImplementedException(); - } - - public bool Inscribe(IInscribeContext context) - { - if (String.IsNullOrEmpty(context.SignedEngineFile)) - { - var command = new InscribeBundleCommand(context); - return command.Execute(); - } - else - { - var command = new InscribeBundleEngineCommand(context); - return command.Execute(); - } - } - - public Intermediate Unbind(IUnbindContext context) - { - var uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); - var acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); - var messaging = context.ServiceProvider.GetService(); - - using (var reader = BurnReader.Open(messaging, context.InputFilePath)) - { - reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder); - reader.ExtractAttachedContainer(acExtractPath, context.IntermediateFolder); - } - - return null; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs deleted file mode 100644 index 75c60e56..00000000 --- a/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs +++ /dev/null @@ -1,117 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class AutomaticallySlipstreamPatchesCommand - { - public AutomaticallySlipstreamPatchesCommand(IntermediateSection section, ICollection packageFacades) - { - this.Section = section; - this.PackageFacades = packageFacades; - } - - private IntermediateSection Section { get; } - - private IEnumerable PackageFacades { get; } - - public void Execute() - { - var msiPackages = new List(); - var targetsProductCode = new Dictionary>(); - var targetsUpgradeCode = new Dictionary>(); - - foreach (var facade in this.PackageFacades) - { - // Keep track of all MSI packages. - if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) - { - msiPackages.Add(msiPackage); - } - else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage && mspPackage.Slipstream) - { - var patchTargetCodeSymbols = this.Section.Symbols - .OfType() - .Where(r => r.PackageRef == facade.PackageId); - - // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. - foreach (var symbol in patchTargetCodeSymbols) - { - if (symbol.TargetsProductCode) - { - if (!targetsProductCode.TryGetValue(symbol.TargetCode, out var symbols)) - { - symbols = new List(); - targetsProductCode.Add(symbol.TargetCode, symbols); - } - - symbols.Add(symbol); - } - else if (symbol.TargetsUpgradeCode) - { - if (!targetsUpgradeCode.TryGetValue(symbol.TargetCode, out var symbols)) - { - symbols = new List(); - targetsUpgradeCode.Add(symbol.TargetCode, symbols); - } - } - } - } - } - - var slipstreamMspIds = new HashSet(); - - // Loop through the MSI and slipstream patches targeting it. - foreach (var msi in msiPackages) - { - if (targetsProductCode.TryGetValue(msi.ProductCode, out var symbols)) - { - foreach (var symbol in symbols) - { - Debug.Assert(symbol.TargetsProductCode); - Debug.Assert(!symbol.TargetsUpgradeCode); - - this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol); - } - } - - if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out symbols)) - { - foreach (var symbol in symbols) - { - Debug.Assert(!symbol.TargetsProductCode); - Debug.Assert(symbol.TargetsUpgradeCode); - - this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol); - } - - symbols = null; - } - } - } - - private bool TryAddSlipstreamSymbol(HashSet slipstreamMspIds, WixBundleMsiPackageSymbol msiPackage, WixBundlePatchTargetCodeSymbol patchTargetCode) - { - var id = new Identifier(AccessModifier.Section, msiPackage.Id.Id, patchTargetCode.PackageRef); - - if (slipstreamMspIds.Add(id.Id)) - { - this.Section.AddSymbol(new WixBundleSlipstreamMspSymbol(patchTargetCode.SourceLineNumbers) - { - TargetPackageRef = msiPackage.Id.Id, - MspPackageRef = patchTargetCode.PackageRef, - }); - - return true; - } - - return false; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs b/src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs deleted file mode 100644 index 3b4a4156..00000000 --- a/src/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Burn.Bundles -{ - using System.IO; - using System.Security.Cryptography; - using System.Text; - - internal static class BundleHashAlgorithm - { - public static string Hash(FileInfo fileInfo) - { - byte[] hashBytes; - - using (var managed = new SHA512CryptoServiceProvider()) - using (var stream = fileInfo.OpenRead()) - { - hashBytes = managed.ComputeHash(stream); - } - - var sb = new StringBuilder(hashBytes.Length * 2); - for (var i = 0; i < hashBytes.Length; i++) - { - sb.AppendFormat("{0:X2}", hashBytes[i]); - } - - return sb.ToString(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs deleted file mode 100644 index 1eb3563a..00000000 --- a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs +++ /dev/null @@ -1,385 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Diagnostics; - using System.IO; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - /// - /// Common functionality for Burn PE Writer & Reader for the WiX toolset. - /// - /// This class encapsulates common functionality related to - /// bundled/chained setup packages. - /// - /// - internal abstract class BurnCommon : IDisposable - { - public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; - public const string BurnUXContainerEmbeddedIdFormat = "u{0}"; - public const string BurnAuthoredContainerEmbeddedIdFormat = "a{0}"; - - public const string BADataFileName = "BootstrapperApplicationData.xml"; - public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; - - public const string BundleExtensionDataFileName = "BundleExtensionData.xml"; - public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData"; - - // See WinNT.h for details about the PE format, including the - // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, - // IMAGE_FILE_HEADER, etc. - protected const UInt32 IMAGE_DOS_HEADER_SIZE = 64; - protected const UInt32 IMAGE_DOS_HEADER_OFFSET_MAGIC = 0; - protected const UInt32 IMAGE_DOS_HEADER_OFFSET_NTHEADER = 60; - - protected const UInt32 IMAGE_NT_HEADER_SIZE = 24; // signature DWORD (4) + IMAGE_FILE_HEADER (20) - protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIGNATURE = 0; - protected const UInt32 IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS = 6; - protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER = 20; - - protected const UInt32 IMAGE_OPTIONAL_OFFSET_CHECKSUM = 4 * 16; // checksum is 16 DWORDs into IMAGE_OPTIONAL_HEADER which is right after the IMAGE_NT_HEADER. - protected const UInt32 IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE = (IMAGE_DATA_DIRECTORY_SIZE * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY)); - - protected const UInt32 IMAGE_SECTION_HEADER_SIZE = 40; - protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_NAME = 0; - protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_VIRTUALSIZE = 8; - protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA = 16; - protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA = 20; - - protected const UInt32 IMAGE_DATA_DIRECTORY_SIZE = 8; // struct of two DWORDs. - protected const UInt32 IMAGE_DIRECTORY_ENTRY_SECURITY = 4; - protected const UInt32 IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; - - protected const UInt16 IMAGE_DOS_SIGNATURE = 0x5A4D; - protected const UInt32 IMAGE_NT_SIGNATURE = 0x00004550; - protected const UInt64 IMAGE_SECTION_WIXBURN_NAME = 0x6E7275627869772E; // ".wixburn", as a qword. - - // The ".wixburn" section contains: - // 0- 3: magic number - // 4- 7: version - // 8-23: bundle GUID - // 24-27: engine (stub) size - // 28-31: original checksum - // 32-35: original signature offset - // 36-39: original signature size - // 40-43: container type (1 = CAB) - // 44-47: container count - // 48-51: byte count of manifest + UX container - // 52-55: byte count of attached container - protected const UInt32 BURN_SECTION_OFFSET_MAGIC = 0; - protected const UInt32 BURN_SECTION_OFFSET_VERSION = 4; - protected const UInt32 BURN_SECTION_OFFSET_BUNDLEGUID = 8; - protected const UInt32 BURN_SECTION_OFFSET_STUBSIZE = 24; - protected const UInt32 BURN_SECTION_OFFSET_ORIGINALCHECKSUM = 28; - protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET = 32; - protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE = 36; - protected const UInt32 BURN_SECTION_OFFSET_FORMAT = 40; - protected const UInt32 BURN_SECTION_OFFSET_COUNT = 44; - protected const UInt32 BURN_SECTION_OFFSET_UXSIZE = 48; - protected const UInt32 BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE = 52; - protected const UInt32 BURN_SECTION_SIZE = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE + 4; // last field + sizeof(DWORD) - - protected const UInt32 BURN_SECTION_MAGIC = 0x00f14300; - protected const UInt32 BURN_SECTION_VERSION = 0x00000002; - protected string fileExe; - protected UInt32 peOffset = UInt32.MaxValue; - protected UInt16 sections = UInt16.MaxValue; - protected UInt32 firstSectionOffset = UInt32.MaxValue; - protected UInt32 checksumOffset; - protected UInt32 certificateTableSignatureOffset; - protected UInt32 certificateTableSignatureSize; - protected UInt32 wixburnDataOffset = UInt32.MaxValue; - - // TODO: does this enum exist in another form somewhere? - /// - /// The types of attached containers that BurnWriter supports. - /// - public enum Container - { - Nothing = 0, - UX, - Attached - } - - /// - /// Creates a BurnCommon for re-writing a PE file. - /// - /// - /// File to modify in-place. - public BurnCommon(IMessaging messaging, string fileExe) - { - this.Messaging = messaging; - this.fileExe = fileExe; - } - - public UInt32 Checksum { get; protected set; } - public UInt32 SignatureOffset { get; protected set; } - public UInt32 SignatureSize { get; protected set; } - public UInt32 Version { get; protected set; } - public UInt32 StubSize { get; protected set; } - public UInt32 OriginalChecksum { get; protected set; } - public UInt32 OriginalSignatureOffset { get; protected set; } - public UInt32 OriginalSignatureSize { get; protected set; } - public UInt32 EngineSize { get; protected set; } - public UInt32 ContainerCount { get; protected set; } - public UInt32 UXAddress { get; protected set; } - public UInt32 UXSize { get; protected set; } - public UInt32 AttachedContainerAddress { get; protected set; } - public UInt32 AttachedContainerSize { get; protected set; } - - protected IMessaging Messaging { get; } - - public void Dispose() - { - this.Dispose(true); - - GC.SuppressFinalize(this); - } - - /// - /// Copies one stream to another. - /// - /// Input stream. - /// Output stream. - /// Optional count of bytes to copy. 0 indicates whole input stream from current should be copied. - protected static int CopyStream(Stream input, Stream output, int size) - { - var bytes = new byte[4096]; - var total = 0; - do - { - var read = Math.Min(bytes.Length, size - total); - read = input.Read(bytes, 0, read); - if (0 == read) - { - break; - } - - output.Write(bytes, 0, read); - total += read; - } while (0 == size || total < size); - - return total; - } - - /// - /// Initialize the common information about a Burn engine. - /// - /// Binary reader open against a Burn engine. - /// True if initialized. - protected bool Initialize(BinaryReader reader) - { - if (!this.GetWixburnSectionInfo(reader)) - { - return false; - } - - reader.BaseStream.Seek(this.wixburnDataOffset, SeekOrigin.Begin); - byte[] bytes = reader.ReadBytes((int)BURN_SECTION_SIZE); - UInt32 uint32 = 0; - - uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_MAGIC); - if (BURN_SECTION_MAGIC != uint32) - { - this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe)); - return false; - } - - this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION); - if (BURN_SECTION_VERSION != this.Version) - { - this.Messaging.Write(ErrorMessages.BundleTooNew(this.fileExe, this.Version)); - return false; - } - - uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_FORMAT); // We only know how to deal with CABs right now - if (1 != uint32) - { - this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe)); - return false; - } - - this.StubSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_STUBSIZE); - this.OriginalChecksum = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALCHECKSUM); - this.OriginalSignatureOffset = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET); - this.OriginalSignatureSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE); - - this.ContainerCount = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_COUNT); - this.UXAddress = this.StubSize; - this.UXSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_UXSIZE); - - // If there is an original signature use that to determine the engine size. - if (0 < this.OriginalSignatureOffset) - { - this.EngineSize = this.OriginalSignatureOffset + this.OriginalSignatureSize; - } - else if (0 < this.SignatureOffset && 2 > this.ContainerCount) // if there is a signature and no attached containers, use the current signature. - { - this.EngineSize = this.SignatureOffset + this.SignatureSize; - } - else // just use the stub and UX container as the size of the engine. - { - this.EngineSize = this.StubSize + this.UXSize; - } - - this.AttachedContainerAddress = this.ContainerCount > 1 ? this.EngineSize : 0; - this.AttachedContainerSize = this.ContainerCount > 1 ? BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE) : 0; - - return true; - } - - protected virtual void Dispose(bool disposing) - { - } - - /// - /// Finds the ".wixburn" section in the current exe. - /// - /// true if the ".wixburn" section is successfully found; false otherwise - private bool GetWixburnSectionInfo(BinaryReader reader) - { - if (UInt32.MaxValue == this.wixburnDataOffset) - { - if (!this.EnsureNTHeader(reader)) - { - return false; - } - - UInt32 wixburnSectionOffset = UInt32.MaxValue; - byte[] bytes = new byte[IMAGE_SECTION_HEADER_SIZE]; - - reader.BaseStream.Seek(this.firstSectionOffset, SeekOrigin.Begin); - for (UInt16 sectionIndex = 0; sectionIndex < this.sections; ++sectionIndex) - { - reader.Read(bytes, 0, bytes.Length); - - if (IMAGE_SECTION_WIXBURN_NAME == BurnCommon.ReadUInt64(bytes, IMAGE_SECTION_HEADER_OFFSET_NAME)) - { - wixburnSectionOffset = this.firstSectionOffset + (IMAGE_SECTION_HEADER_SIZE * sectionIndex); - break; - } - } - - if (UInt32.MaxValue == wixburnSectionOffset) - { - this.Messaging.Write(ErrorMessages.StubMissingWixburnSection(this.fileExe)); - return false; - } - - // we need 56 bytes for the manifest header, which is always going to fit in - // the smallest alignment (512 bytes), but just to be paranoid... - if (BURN_SECTION_SIZE > BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA)) - { - this.Messaging.Write(ErrorMessages.StubWixburnSectionTooSmall(this.fileExe)); - return false; - } - - this.wixburnDataOffset = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA); - } - - return true; - } - - /// - /// Checks for a valid Windows PE signature (IMAGE_NT_SIGNATURE) in the current exe. - /// - /// true if the exe is a Windows executable; false otherwise - private bool EnsureNTHeader(BinaryReader reader) - { - if (UInt32.MaxValue == this.firstSectionOffset) - { - if (!this.EnsureDosHeader(reader)) - { - return false; - } - - reader.BaseStream.Seek(this.peOffset, SeekOrigin.Begin); - byte[] bytes = reader.ReadBytes((int)IMAGE_NT_HEADER_SIZE); - - // Verify the NT signature... - if (IMAGE_NT_SIGNATURE != BurnCommon.ReadUInt32(bytes, IMAGE_NT_HEADER_OFFSET_SIGNATURE)) - { - this.Messaging.Write(ErrorMessages.InvalidStubExe(this.fileExe)); - return false; - } - - ushort sizeOptionalHeader = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER); - - this.sections = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS); - this.firstSectionOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader; - - this.checksumOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + IMAGE_OPTIONAL_OFFSET_CHECKSUM; - this.certificateTableSignatureOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE; - this.certificateTableSignatureSize = this.certificateTableSignatureOffset + 4; // size is in the DWORD after the offset. - - bytes = reader.ReadBytes(sizeOptionalHeader); - this.Checksum = BurnCommon.ReadUInt32(bytes, IMAGE_OPTIONAL_OFFSET_CHECKSUM); - this.SignatureOffset = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE); - this.SignatureSize = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE + 4); - } - - return true; - } - - /// - /// Checks for a valid DOS header in the current exe. - /// - /// true if the exe starts with a DOS stub; false otherwise - private bool EnsureDosHeader(BinaryReader reader) - { - if (UInt32.MaxValue == this.peOffset) - { - byte[] bytes = reader.ReadBytes((int)IMAGE_DOS_HEADER_SIZE); - - // Verify the DOS 'MZ' signature. - if (IMAGE_DOS_SIGNATURE != BurnCommon.ReadUInt16(bytes, IMAGE_DOS_HEADER_OFFSET_MAGIC)) - { - this.Messaging.Write(ErrorMessages.InvalidStubExe(this.fileExe)); - return false; - } - - this.peOffset = BurnCommon.ReadUInt32(bytes, IMAGE_DOS_HEADER_OFFSET_NTHEADER); - } - - return true; - } - - /// - /// Reads a UInt16 value in little-endian format from an offset in an array of bytes. - /// - /// Array from which to read. - /// Beginning offset from which to read. - /// value at offset - internal static UInt16 ReadUInt16(byte[] bytes, UInt32 offset) - { - Debug.Assert(offset + 2 <= bytes.Length); - return (UInt16)(bytes[offset] + (bytes[offset + 1] << 8)); - } - - /// - /// Reads a UInt32 value in little-endian format from an offset in an array of bytes. - /// - /// Array from which to read. - /// Beginning offset from which to read. - /// value at offset - internal static UInt32 ReadUInt32(byte[] bytes, UInt32 offset) - { - Debug.Assert(offset + 4 <= bytes.Length); - return BurnCommon.ReadUInt16(bytes, offset) + ((UInt32)BurnCommon.ReadUInt16(bytes, offset + 2) << 16); - } - - /// - /// Reads a UInt64 value in little-endian format from an offset in an array of bytes. - /// - /// Array from which to read. - /// Beginning offset from which to read. - /// value at offset - internal static UInt64 ReadUInt64(byte[] bytes, UInt32 offset) - { - Debug.Assert(offset + 8 <= bytes.Length); - return BurnCommon.ReadUInt32(bytes, offset) + ((UInt64)BurnCommon.ReadUInt32(bytes, offset + 4) << 32); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/BurnReader.cs b/src/WixToolset.Core.Burn/Bundles/BurnReader.cs deleted file mode 100644 index 5b06b31e..00000000 --- a/src/WixToolset.Core.Burn/Bundles/BurnReader.cs +++ /dev/null @@ -1,212 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Xml; - using WixToolset.Core.Native; - using WixToolset.Extensibility.Services; - - /// - /// Burn PE reader for the WiX toolset. - /// - /// This class encapsulates reading from a stub EXE with containers attached - /// for dissecting bundled/chained setup packages. - /// - /// using (BurnReader reader = BurnReader.Open(fileExe, this.core, guid)) - /// { - /// reader.ExtractUXContainer(file1, tempFolder); - /// } - /// - internal class BurnReader : BurnCommon - { - private bool disposed; - - private bool invalidBundle; - private BinaryReader binaryReader; - private readonly List attachedContainerPayloadNames; - - /// - /// Creates a BurnReader for reading a PE file. - /// - /// - /// File to read. - private BurnReader(IMessaging messaging, string fileExe) - : base(messaging, fileExe) - { - this.attachedContainerPayloadNames = new List(); - } - - /// - /// Gets the underlying stream. - /// - public Stream Stream => this.binaryReader?.BaseStream; - - internal static BurnReader Open(object inputFilePath) - { - throw new NotImplementedException(); - } - - /// - /// Opens a Burn reader. - /// - /// - /// Path to file. - /// Burn reader. - public static BurnReader Open(IMessaging messaging, string fileExe) - { - var reader = new BurnReader(messaging, fileExe); - - reader.binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)); - if (!reader.Initialize(reader.binaryReader)) - { - reader.invalidBundle = true; - } - - return reader; - } - - /// - /// Gets the UX container from the exe and extracts its contents to the output directory. - /// - /// Directory to write extracted files to. - /// Scratch directory. - /// True if successful, false otherwise - public bool ExtractUXContainer(string outputDirectory, string tempDirectory) - { - // No UX container to extract - if (this.UXAddress == 0 || this.UXSize == 0) - { - return false; - } - - if (this.invalidBundle) - { - return false; - } - - Directory.CreateDirectory(outputDirectory); - string tempCabPath = Path.Combine(tempDirectory, "ux.cab"); - string manifestOriginalPath = Path.Combine(outputDirectory, "0"); - string manifestPath = Path.Combine(outputDirectory, "manifest.xml"); - - this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin); - using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) - { - BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.UXSize); - } - - var cabinet = new Cabinet(tempCabPath); - cabinet.Extract(outputDirectory); - - Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); - FileSystem.MoveFile(manifestOriginalPath, manifestPath); - - XmlDocument document = new XmlDocument(); - document.Load(manifestPath); - XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace("burn", BurnCommon.BurnNamespace); - XmlNodeList uxPayloads = document.SelectNodes("/burn:BurnManifest/burn:UX/burn:Payload", namespaceManager); - XmlNodeList payloads = document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager); - - foreach (XmlNode uxPayload in uxPayloads) - { - XmlNode sourcePathNode = uxPayload.Attributes.GetNamedItem("SourcePath"); - XmlNode filePathNode = uxPayload.Attributes.GetNamedItem("FilePath"); - - string sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value); - string destinationPath = Path.Combine(outputDirectory, filePathNode.Value); - - Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); - FileSystem.MoveFile(sourcePath, destinationPath); - } - - foreach (XmlNode payload in payloads) - { - XmlNode sourcePathNode = payload.Attributes.GetNamedItem("SourcePath"); - XmlNode filePathNode = payload.Attributes.GetNamedItem("FilePath"); - XmlNode packagingNode = payload.Attributes.GetNamedItem("Packaging"); - - string sourcePath = sourcePathNode.Value; - string destinationPath = filePathNode.Value; - string packaging = packagingNode.Value; - - if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase)) - { - this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath)); - } - } - - return true; - } - - internal void ExtractUXContainer(string uxExtractPath, object intermediateFolder) - { - throw new NotImplementedException(); - } - - /// - /// Gets the attached container from the exe and extracts its contents to the output directory. - /// - /// Directory to write extracted files to. - /// Scratch directory. - /// True if successful, false otherwise - public bool ExtractAttachedContainer(string outputDirectory, string tempDirectory) - { - // No attached container to extract - if (this.AttachedContainerAddress == 0 || this.AttachedContainerSize == 0) - { - return false; - } - - if (this.invalidBundle) - { - return false; - } - - Directory.CreateDirectory(outputDirectory); - string tempCabPath = Path.Combine(tempDirectory, "attached.cab"); - - this.binaryReader.BaseStream.Seek(this.AttachedContainerAddress, SeekOrigin.Begin); - using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) - { - BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.AttachedContainerSize); - } - - var cabinet = new Cabinet(tempCabPath); - cabinet.Extract(outputDirectory); - - foreach (DictionaryEntry entry in this.attachedContainerPayloadNames) - { - string sourcePath = Path.Combine(outputDirectory, (string)entry.Key); - string destinationPath = Path.Combine(outputDirectory, (string)entry.Value); - - Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); - FileSystem.MoveFile(sourcePath, destinationPath); - } - - return true; - } - - /// - /// Dispose object. - /// - /// True when releasing managed objects. - protected override void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing && this.binaryReader != null) - { - this.binaryReader.Close(); - this.binaryReader = null; - } - - this.disposed = true; - } - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs b/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs deleted file mode 100644 index 2d16d11c..00000000 --- a/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs +++ /dev/null @@ -1,245 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Diagnostics; - using System.IO; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - /// - /// Burn PE writer for the WiX toolset. - /// - /// This class encapsulates reading/writing to a stub EXE for - /// creating bundled/chained setup packages. - /// - /// using (BurnWriter writer = new BurnWriter(fileExe, this.core, guid)) - /// { - /// writer.AppendContainer(file1, BurnWriter.Container.UX); - /// writer.AppendContainer(file2, BurnWriter.Container.Attached); - /// } - /// - internal class BurnWriter : BurnCommon - { - private bool disposed; - private bool invalidBundle; - private BinaryWriter binaryWriter; - - /// - /// Creates a BurnWriter for re-writing a PE file. - /// - /// - /// File to modify in-place. - private BurnWriter(IMessaging messaging, string fileExe) - : base(messaging, fileExe) - { - } - - /// - /// Opens a Burn writer. - /// - /// - /// Path to file. - /// Burn writer. - public static BurnWriter Open(IMessaging messaging, string fileExe) - { - BurnWriter writer = new BurnWriter(messaging, fileExe); - - using (BinaryReader binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))) - { - if (!writer.Initialize(binaryReader)) - { - writer.invalidBundle = true; - } - } - - if (!writer.invalidBundle) - { - writer.binaryWriter = new BinaryWriter(File.Open(fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete)); - } - - return writer; - } - - /// - /// Update the ".wixburn" section data. - /// - /// Size of the stub engine "burn.exe". - /// Unique identifier for this bundle. - /// - public bool InitializeBundleSectionData(long stubSize, string bundleId) - { - if (this.invalidBundle) - { - return false; - } - - var bundleGuid = Guid.Parse(bundleId); - - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); - - this.Messaging.Write(VerboseMessages.BundleGuid(bundleId)); - this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); - this.binaryWriter.Write(bundleGuid.ToByteArray()); - - this.StubSize = (uint)stubSize; - - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_STUBSIZE, this.StubSize); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, 0); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, 0); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, 0); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_FORMAT, 1); // Hard-coded to CAB for now. - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, 0); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_UXSIZE, 0); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE, 0); - this.binaryWriter.BaseStream.Flush(); - - this.EngineSize = this.StubSize; - - return true; - } - - /// - /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. - /// - /// File path to append to the current exe. - /// Container section represented by the fileContainer. - /// true if the container data is successfully appended; false otherwise - public bool AppendContainer(string fileContainer, BurnCommon.Container container) - { - using (FileStream reader = File.OpenRead(fileContainer)) - { - return this.AppendContainer(reader, reader.Length, container); - } - } - - /// - /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. - /// - /// File stream to append to the current exe. - /// Size of container to append. - /// Container section represented by the fileContainer. - /// true if the container data is successfully appended; false otherwise - public bool AppendContainer(Stream containerStream, long containerSize, BurnCommon.Container container) - { - UInt32 burnSectionCount = 0; - UInt32 burnSectionOffsetSize = 0; - - switch (container) - { - case Container.UX: - burnSectionCount = 1; - burnSectionOffsetSize = BURN_SECTION_OFFSET_UXSIZE; - // TODO: verify that the size in the section data is 0 or the same size. - this.EngineSize += (uint)containerSize; - this.UXSize = (uint)containerSize; - break; - - case Container.Attached: - burnSectionCount = 2; - burnSectionOffsetSize = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE; - // TODO: verify that the size in the section data is 0 or the same size. - this.AttachedContainerSize = (uint)containerSize; - break; - - default: - Debug.Assert(false); - return false; - } - - return this.AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount); - } - - public void RememberThenResetSignature() - { - if (this.invalidBundle) - { - return; - } - - this.OriginalChecksum = this.Checksum; - this.OriginalSignatureOffset = this.SignatureOffset; - this.OriginalSignatureSize = this.SignatureSize; - - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, this.OriginalChecksum); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, this.OriginalSignatureOffset); - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, this.OriginalSignatureSize); - - this.Checksum = 0; - this.SignatureOffset = 0; - this.SignatureSize = 0; - - this.WriteToOffset(this.checksumOffset, this.Checksum); - this.WriteToOffset(this.certificateTableSignatureOffset, this.SignatureOffset); - this.WriteToOffset(this.certificateTableSignatureSize, this.SignatureSize); - } - - /// - /// Dispose object. - /// - /// True when releasing managed objects. - protected override void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing && this.binaryWriter != null) - { - this.binaryWriter.Close(); - this.binaryWriter = null; - } - - this.disposed = true; - } - } - - /// - /// Appends a container to the exe and updates the ".wixburn" section data to point to it. - /// - /// File stream to append to the current exe. - /// Size of the container. - /// Offset of size field for this container in ".wixburn" section data. - /// Number of Burn sections. - /// true if the container data is successfully appended; false otherwise - private bool AppendContainer(Stream containerStream, UInt32 containerSize, UInt32 burnSectionOffsetSize, UInt32 burnSectionCount) - { - if (this.invalidBundle) - { - return false; - } - - // Update the ".wixburn" section data - this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, burnSectionCount); - this.WriteToBurnSectionOffset(burnSectionOffsetSize, containerSize); - - // Append the container to the end of the existing bits. - this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End); - BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize); - this.binaryWriter.BaseStream.Flush(); - - return true; - } - - /// - /// Writes the value to an offset in the Burn section data. - /// - /// Offset in to the Burn section data. - /// Value to write. - private void WriteToBurnSectionOffset(uint offset, uint value) - { - this.WriteToOffset(this.wixburnDataOffset + offset, value); - } - - /// - /// Writes the value to an offset in the Burn stub. - /// - /// Offset in to the Burn stub. - /// Value to write. - private void WriteToOffset(uint offset, uint value) - { - this.binaryWriter.BaseStream.Seek((int)offset, SeekOrigin.Begin); - this.binaryWriter.Write(value); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs deleted file mode 100644 index a0ee606d..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ /dev/null @@ -1,290 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - - internal class CreateBootstrapperApplicationManifestCommand - { - public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable chainPackages, int lastUXPayloadIndex, Dictionary payloadSymbols, Dictionary> packagesPayloads, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) - { - this.Section = section; - this.BundleSymbol = bundleSymbol; - this.ChainPackages = chainPackages; - this.LastUXPayloadIndex = lastUXPayloadIndex; - this.Payloads = payloadSymbols; - this.PackagesPayloads = packagesPayloads; - this.IntermediateFolder = intermediateFolder; - this.InternalBurnBackendHelper = internalBurnBackendHelper; - } - - private IntermediateSection Section { get; } - - private WixBundleSymbol BundleSymbol { get; } - - private IEnumerable ChainPackages { get; } - - private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } - - private int LastUXPayloadIndex { get; } - - private Dictionary Payloads { get; } - - private Dictionary> PackagesPayloads { get; } - - private string IntermediateFolder { get; } - - public WixBundlePayloadSymbol BootstrapperApplicationManifestPayloadRow { get; private set; } - - public string OutputPath { get; private set; } - - public void Execute() - { - this.OutputPath = this.CreateBootstrapperApplicationManifest(); - - this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(this.OutputPath); - } - - private string CreateBootstrapperApplicationManifest() - { - var path = Path.Combine(this.IntermediateFolder, "wix-badata.xml"); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - using (var writer = new XmlTextWriter(path, Encoding.Unicode)) - { - writer.Formatting = Formatting.Indented; - writer.WriteStartDocument(); - writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace); - - this.WriteBundleInfo(writer); - - this.WritePackageInfo(writer); - - this.WriteFeatureInfo(writer); - - this.WritePayloadInfo(writer); - - this.InternalBurnBackendHelper.WriteBootstrapperApplicationData(writer); - - writer.WriteEndElement(); - writer.WriteEndDocument(); - } - - return path; - } - - private void WriteBundleInfo(XmlTextWriter writer) - { - writer.WriteStartElement("WixBundleProperties"); - - writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); - writer.WriteAttributeString("LogPathVariable", this.BundleSymbol.LogPathVariable); - writer.WriteAttributeString("Compressed", this.BundleSymbol.Compressed == true ? "yes" : "no"); - writer.WriteAttributeString("Id", this.BundleSymbol.BundleId.ToUpperInvariant()); - writer.WriteAttributeString("UpgradeCode", this.BundleSymbol.UpgradeCode); - writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); - - writer.WriteEndElement(); - } - - private void WritePackageInfo(XmlTextWriter writer) - { - foreach (var package in this.ChainPackages) - { - if (!this.PackagesPayloads.TryGetValue(package.PackageId, out var payloads)) - { - continue; - } - - var packagePayload = payloads[package.PackageSymbol.PayloadRef]; - - var size = package.PackageSymbol.Size.ToString(CultureInfo.InvariantCulture); - - writer.WriteStartElement("WixPackageProperties"); - - writer.WriteAttributeString("Package", package.PackageId); - writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == true ? "yes" : "no"); - - if (!String.IsNullOrEmpty(package.PackageSymbol.DisplayName)) - { - writer.WriteAttributeString("DisplayName", package.PackageSymbol.DisplayName); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.Description)) - { - writer.WriteAttributeString("Description", package.PackageSymbol.Description); - } - - writer.WriteAttributeString("DownloadSize", size); - writer.WriteAttributeString("PackageSize", size); - writer.WriteAttributeString("InstalledSize", package.PackageSymbol.InstallSize?.ToString(CultureInfo.InvariantCulture) ?? size); - writer.WriteAttributeString("PackageType", package.PackageSymbol.Type.ToString()); - writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); - writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); - writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); - writer.WriteAttributeString("Compressed", packagePayload.Packaging == PackagingType.Embedded ? "yes" : "no"); - - if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) - { - if (!String.IsNullOrEmpty(msiPackage.ProductCode)) - { - writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); - } - - if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); - } - } - else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) - { - if (!String.IsNullOrEmpty(mspPackage.PatchCode)) - { - writer.WriteAttributeString("ProductCode", mspPackage.PatchCode); - } - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.Version)) - { - writer.WriteAttributeString("Version", package.PackageSymbol.Version); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) - { - writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); - } - - switch (package.PackageSymbol.Cache) - { - case YesNoAlwaysType.No: - writer.WriteAttributeString("Cache", "remove"); - break; - case YesNoAlwaysType.Yes: - writer.WriteAttributeString("Cache", "keep"); - break; - case YesNoAlwaysType.Always: - writer.WriteAttributeString("Cache", "force"); - break; - } - - writer.WriteEndElement(); - } - } - - private void WriteFeatureInfo(XmlTextWriter writer) - { - var featureSymbols = this.Section.Symbols.OfType(); - - foreach (var featureSymbol in featureSymbols) - { - writer.WriteStartElement("WixPackageFeatureInfo"); - - writer.WriteAttributeString("Package", featureSymbol.PackageRef); - writer.WriteAttributeString("Feature", featureSymbol.Name); - writer.WriteAttributeString("Size", featureSymbol.Size.ToString(CultureInfo.InvariantCulture)); - - if (!String.IsNullOrEmpty(featureSymbol.Parent)) - { - writer.WriteAttributeString("Parent", featureSymbol.Parent); - } - - if (!String.IsNullOrEmpty(featureSymbol.Title)) - { - writer.WriteAttributeString("Title", featureSymbol.Title); - } - - if (!String.IsNullOrEmpty(featureSymbol.Description)) - { - writer.WriteAttributeString("Description", featureSymbol.Description); - } - - writer.WriteAttributeString("Display", featureSymbol.Display.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Level", featureSymbol.Level.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Directory", featureSymbol.Directory); - writer.WriteAttributeString("Attributes", featureSymbol.Attributes.ToString(CultureInfo.InvariantCulture)); - - writer.WriteEndElement(); - } - } - - private void WritePayloadInfo(XmlTextWriter writer) - { - foreach (var kvp in this.PackagesPayloads.OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) - { - var packageId = kvp.Key; - var payloadsById = kvp.Value; - - foreach (var payloadSymbol in payloadsById.Values.OrderBy(p => p.Id.Id, StringComparer.Ordinal)) - { - this.WritePayloadInfo(writer, payloadSymbol, packageId); - } - } - - foreach (var payloadSymbol in this.Payloads.Values.Where(p => p.LayoutOnly).OrderBy(p => p.Id.Id, StringComparer.Ordinal)) - { - this.WritePayloadInfo(writer, payloadSymbol, null); - } - } - - private void WritePayloadInfo(XmlTextWriter writer, WixBundlePayloadSymbol payloadSymbol, string packageId) - { - writer.WriteStartElement("WixPayloadProperties"); - - if (!String.IsNullOrEmpty(packageId)) - { - writer.WriteAttributeString("Package", packageId); - } - - writer.WriteAttributeString("Payload", payloadSymbol.Id.Id); - - if (!String.IsNullOrEmpty(payloadSymbol.ContainerRef)) - { - writer.WriteAttributeString("Container", payloadSymbol.ContainerRef); - } - - writer.WriteAttributeString("Name", payloadSymbol.Name); - writer.WriteAttributeString("Size", payloadSymbol.FileSize.Value.ToString(CultureInfo.InvariantCulture)); - - if (!String.IsNullOrEmpty(payloadSymbol.DownloadUrl)) - { - writer.WriteAttributeString("DownloadUrl", payloadSymbol.DownloadUrl); - } - - writer.WriteEndElement(); - } - - private WixBundlePayloadSymbol CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) - { - var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BADataFileName); - - var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) - { - Name = BurnCommon.BADataFileName, - SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, - Compressed = true, - UnresolvedSourceFile = baManifestPath, - ContainerRef = BurnConstants.BurnUXContainerName, - EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), - Packaging = PackagingType.Embedded, - }); - - var fileInfo = new FileInfo(baManifestPath); - - symbol.FileSize = (int)fileInfo.Length; - - symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); - - return symbol; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs deleted file mode 100644 index b802f556..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs +++ /dev/null @@ -1,325 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Reflection; - using System.Text; - using System.Xml; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Dtf.Resources; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CreateBundleExeCommand - { - public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable containers) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.IntermediateFolder = intermediateFolder; - this.OutputPath = outputPath; - this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; - this.BundleSymbol = bundleSymbol; - this.UXContainer = uxContainer; - this.Containers = containers; - } - - public IFileTransfer Transfer { get; private set; } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private string IntermediateFolder { get; } - - private string OutputPath { get; } - - private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } - - private WixBundleSymbol BundleSymbol { get; } - - private WixBundleContainerSymbol UXContainer { get; } - - private IEnumerable Containers { get; } - - public void Execute() - { - var bundleFilename = Path.GetFileName(this.OutputPath); - - // Copy the burn.exe to a writable location then mark it to be moved to its final build location. - - var stubPlatform = this.BundleSymbol.Platform.ToString(); - var stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); - - if (stubPlatform != "X86") - { - this.Messaging.Write(WarningMessages.ExperimentalBundlePlatform(stubPlatform)); - } - - var bundleTempPath = Path.Combine(this.IntermediateFolder, bundleFilename); - - this.Messaging.Write(VerboseMessages.GeneratingBundle(bundleTempPath, stubFile)); - - if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) - { - this.Messaging.Write(ErrorMessages.InsecureBundleFilename(bundleFilename)); - } - - this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleSymbol.SourceLineNumbers); - - FileSystem.CopyFile(stubFile, bundleTempPath, allowHardlink: false); - File.SetAttributes(bundleTempPath, FileAttributes.Normal); - - var windowsAssemblyVersion = GetWindowsAssemblyVersion(this.BundleSymbol); - - var applicationManifestData = GenerateApplicationManifest(this.BundleSymbol, this.BootstrapperApplicationDllSymbol, this.OutputPath, windowsAssemblyVersion); - - UpdateBurnResources(bundleTempPath, this.OutputPath, this.BundleSymbol, windowsAssemblyVersion, applicationManifestData); - - // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers - // if they should be attached. - using (var writer = BurnWriter.Open(this.Messaging, bundleTempPath)) - { - var burnStubFile = new FileInfo(bundleTempPath); - writer.InitializeBundleSectionData(burnStubFile.Length, this.BundleSymbol.BundleId); - - // Always attach the UX container first - writer.AppendContainer(this.UXContainer.WorkingPath, BurnWriter.Container.UX); - - // Now append all other attached containers - foreach (var container in this.Containers) - { - if (ContainerType.Attached == container.Type) - { - // The container was only created if it had payloads. - if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) - { - writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); - } - } - } - } - } - - private static byte[] GenerateApplicationManifest(WixBundleSymbol bundleSymbol, WixBootstrapperApplicationDllSymbol bootstrapperApplicationSymbol, string outputPath, Version windowsAssemblyVersion) - { - const string asmv1Namespace = "urn:schemas-microsoft-com:asm.v1"; - const string asmv3Namespace = "urn:schemas-microsoft-com:asm.v3"; - const string compatv1Namespace = "urn:schemas-microsoft-com:compatibility.v1"; - const string ws2005Namespace = "http://schemas.microsoft.com/SMI/2005/WindowsSettings"; - const string ws2016Namespace = "http://schemas.microsoft.com/SMI/2016/WindowsSettings"; - const string ws2017Namespace = "http://schemas.microsoft.com/SMI/2017/WindowsSettings"; - - var bundleFileName = Path.GetFileName(outputPath); - var bundleAssemblyVersion = windowsAssemblyVersion.ToString(); - var bundlePlatform = bundleSymbol.Platform == Platform.X64 ? "amd64" : bundleSymbol.Platform.ToString().ToLower(); - var bundleDescription = bundleSymbol.Name; - - using (var memoryStream = new MemoryStream()) - using (var writer = new XmlTextWriter(memoryStream, Encoding.UTF8)) - { - writer.WriteStartDocument(); - - writer.WriteStartElement("assembly", asmv1Namespace); - writer.WriteAttributeString("manifestVersion", "1.0"); - - writer.WriteStartElement("assemblyIdentity"); - writer.WriteAttributeString("name", bundleFileName); - writer.WriteAttributeString("version", bundleAssemblyVersion); - writer.WriteAttributeString("processorArchitecture", bundlePlatform); - writer.WriteAttributeString("type", "win32"); - writer.WriteEndElement(); // - - if (!String.IsNullOrEmpty(bundleDescription)) - { - writer.WriteStartElement("description"); - writer.WriteString(bundleDescription); - writer.WriteEndElement(); - } - - writer.WriteStartElement("dependency"); - writer.WriteStartElement("dependentAssembly"); - writer.WriteStartElement("assemblyIdentity"); - writer.WriteAttributeString("name", "Microsoft.Windows.Common-Controls"); - writer.WriteAttributeString("version", "6.0.0.0"); - writer.WriteAttributeString("processorArchitecture", bundlePlatform); - writer.WriteAttributeString("publicKeyToken", "6595b64144ccf1df"); - writer.WriteAttributeString("language", "*"); - writer.WriteAttributeString("type", "win32"); - writer.WriteEndElement(); // - writer.WriteEndElement(); // - writer.WriteEndElement(); // - - writer.WriteStartElement("compatibility", compatv1Namespace); - writer.WriteStartElement("application"); - - writer.WriteStartElement("supportedOS"); - writer.WriteAttributeString("Id", "{e2011457-1546-43c5-a5fe-008deee3d3f0}"); // Windows Vista - writer.WriteEndElement(); - writer.WriteStartElement("supportedOS"); - writer.WriteAttributeString("Id", "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"); // Windows 7 - writer.WriteEndElement(); - writer.WriteStartElement("supportedOS"); - writer.WriteAttributeString("Id", "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"); // Windows 8 - writer.WriteEndElement(); - writer.WriteStartElement("supportedOS"); - writer.WriteAttributeString("Id", "{1f676c76-80e1-4239-95bb-83d0f6d0da78}"); // Windows 8.1 - writer.WriteEndElement(); - writer.WriteStartElement("supportedOS"); - writer.WriteAttributeString("Id", "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"); // Windows 10 - writer.WriteEndElement(); - - writer.WriteEndElement(); // - writer.WriteEndElement(); // - - writer.WriteStartElement("trustInfo", asmv3Namespace); - writer.WriteStartElement("security"); - writer.WriteStartElement("requestedPrivileges"); - writer.WriteStartElement("requestedExecutionLevel"); - writer.WriteAttributeString("level", "asInvoker"); - writer.WriteAttributeString("uiAccess", "false"); - writer.WriteEndElement(); // - writer.WriteEndElement(); // - writer.WriteEndElement(); // - writer.WriteEndElement(); // - - if (bootstrapperApplicationSymbol.DpiAwareness != WixBootstrapperApplicationDpiAwarenessType.Unaware) - { - string dpiAwareValue = null; - string dpiAwarenessValue = null; - string gdiScalingValue = null; - - switch(bootstrapperApplicationSymbol.DpiAwareness) - { - case WixBootstrapperApplicationDpiAwarenessType.GdiScaled: - gdiScalingValue = "true"; - break; - case WixBootstrapperApplicationDpiAwarenessType.PerMonitor: - dpiAwareValue = "true/pm"; - break; - case WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2: - dpiAwareValue = "true/pm"; - dpiAwarenessValue = "PerMonitorV2, PerMonitor"; - break; - case WixBootstrapperApplicationDpiAwarenessType.System: - dpiAwareValue = "true"; - break; - } - - writer.WriteStartElement("application", asmv3Namespace); - writer.WriteStartElement("windowsSettings"); - - if (dpiAwareValue != null) - { - writer.WriteStartElement("dpiAware", ws2005Namespace); - writer.WriteString(dpiAwareValue); - writer.WriteEndElement(); - } - - if (dpiAwarenessValue != null) - { - writer.WriteStartElement("dpiAwareness", ws2016Namespace); - writer.WriteString(dpiAwarenessValue); - writer.WriteEndElement(); - } - - if (gdiScalingValue != null) - { - writer.WriteStartElement("gdiScaling", ws2017Namespace); - writer.WriteString(gdiScalingValue); - writer.WriteEndElement(); - } - - writer.WriteEndElement(); // - writer.WriteEndElement(); // - } - - writer.WriteEndDocument(); // - writer.Close(); - - return memoryStream.ToArray(); - } - } - - private static Version GetWindowsAssemblyVersion(WixBundleSymbol bundleSymbol) - { - // Ensure the bundle info provides a full four part version. - var fourPartVersion = new Version(bundleSymbol.Version); - var major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; - var minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; - var build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; - var revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; - - if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) - { - throw new WixException(ErrorMessages.InvalidModuleOrBundleVersion(bundleSymbol.SourceLineNumbers, "Bundle", bundleSymbol.Version)); - } - - return new Version(major, minor, build, revision); - } - - private static void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleSymbol bundleInfo, Version windowsAssemblyVersion, byte[] applicationManifestData) - { - const int burnLocale = 1033; - var resources = new Dtf.Resources.ResourceCollection(); - var version = new Dtf.Resources.VersionResource("#1", burnLocale); - - version.Load(bundleTempPath); - resources.Add(version); - - version.FileVersion = windowsAssemblyVersion; - version.ProductVersion = windowsAssemblyVersion; - - var strings = version[burnLocale] ?? version.Add(burnLocale); - strings["LegalCopyright"] = bundleInfo.Copyright; - strings["OriginalFilename"] = Path.GetFileName(outputPath); - strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. - strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts. - - if (!String.IsNullOrEmpty(bundleInfo.Name)) - { - strings["ProductName"] = bundleInfo.Name; - strings["FileDescription"] = bundleInfo.Name; - } - - if (!String.IsNullOrEmpty(bundleInfo.Manufacturer)) - { - strings["CompanyName"] = bundleInfo.Manufacturer; - } - else - { - strings["CompanyName"] = String.Empty; - } - - if (!String.IsNullOrEmpty(bundleInfo.IconSourceFile)) - { - var iconGroup = new Dtf.Resources.GroupIconResource("#1", burnLocale); - iconGroup.ReadFromFile(bundleInfo.IconSourceFile); - resources.Add(iconGroup); - - foreach (var icon in iconGroup.Icons) - { - resources.Add(icon); - } - } - - if (!String.IsNullOrEmpty(bundleInfo.SplashScreenSourceFile)) - { - var bitmap = new Dtf.Resources.BitmapResource("#1", burnLocale); - bitmap.ReadFromFile(bundleInfo.SplashScreenSourceFile); - resources.Add(bitmap); - } - - var manifestResource = new Resource(ResourceType.Manifest, "#1", burnLocale, applicationManifestData); - resources.Add(manifestResource); - - resources.Save(bundleTempPath); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs deleted file mode 100644 index e587413e..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Globalization; - using System.IO; - using System.Text; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - - internal class CreateBundleExtensionManifestCommand - { - public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, int lastUXPayloadIndex, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) - { - this.Section = section; - this.BundleSymbol = bundleSymbol; - this.LastUXPayloadIndex = lastUXPayloadIndex; - this.IntermediateFolder = intermediateFolder; - this.InternalBurnBackendHelper = internalBurnBackendHelper; - } - - private IntermediateSection Section { get; } - - private WixBundleSymbol BundleSymbol { get; } - - private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } - - private int LastUXPayloadIndex { get; } - - private string IntermediateFolder { get; } - - public WixBundlePayloadSymbol BundleExtensionManifestPayloadRow { get; private set; } - - public string OutputPath { get; private set; } - - public void Execute() - { - this.OutputPath = this.CreateBundleExtensionManifest(); - - this.BundleExtensionManifestPayloadRow = this.CreateBundleExtensionManifestPayloadRow(this.OutputPath); - } - - private string CreateBundleExtensionManifest() - { - var path = Path.Combine(this.IntermediateFolder, "wix-bextdata.xml"); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - using (var writer = new XmlTextWriter(path, Encoding.Unicode)) - { - writer.Formatting = Formatting.Indented; - writer.WriteStartDocument(); - writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); - - this.InternalBurnBackendHelper.WriteBundleExtensionData(writer); - - writer.WriteEndElement(); - writer.WriteEndDocument(); - } - - return path; - } - - private WixBundlePayloadSymbol CreateBundleExtensionManifestPayloadRow(string bextManifestPath) - { - var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName); - - this.Section.AddSymbol(new WixGroupSymbol(this.BundleSymbol.SourceLineNumbers) - { - ParentType = ComplexReferenceParentType.Container, - ParentId = BurnConstants.BurnUXContainerName, - ChildType = ComplexReferenceChildType.Payload, - ChildId = generatedId - }); - - var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) - { - Name = BurnCommon.BundleExtensionDataFileName, - SourceFile = new IntermediateFieldPathValue { Path = bextManifestPath }, - Compressed = true, - UnresolvedSourceFile = bextManifestPath, - ContainerRef = BurnConstants.BurnUXContainerName, - EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), - Packaging = PackagingType.Embedded, - }); - - var fileInfo = new FileInfo(bextManifestPath); - - symbol.FileSize = (int)fileInfo.Length; - - symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); - - return symbol; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs deleted file mode 100644 index 5655d23d..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ /dev/null @@ -1,700 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class CreateBurnManifestCommand - { - public CreateBurnManifestCommand(string executableName, IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable containers, WixChainSymbol chainSymbol, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, Dictionary> packagesPayloads, IEnumerable orderedSearches, string intermediateFolder) - { - this.ExecutableName = executableName; - this.Section = section; - this.BundleSymbol = bundleSymbol; - this.Chain = chainSymbol; - this.Containers = containers; - this.OrderedPackages = orderedPackages; - this.RollbackBoundaries = boundaries; - this.UXContainerPayloads = uxPayloads; - this.Payloads = allPayloadsById; - this.PackagesPayloads = packagesPayloads; - this.OrderedSearches = orderedSearches; - this.IntermediateFolder = intermediateFolder; - } - - public string OutputPath { get; private set; } - - private string ExecutableName { get; } - - private IntermediateSection Section { get; } - - private WixBundleSymbol BundleSymbol { get; } - - private WixChainSymbol Chain { get; } - - private IEnumerable RollbackBoundaries { get; } - - private IEnumerable OrderedPackages { get; } - - private IEnumerable OrderedSearches { get; } - - private Dictionary Payloads { get; } - - private Dictionary> PackagesPayloads { get; } - - private IEnumerable Containers { get; } - - private IEnumerable UXContainerPayloads { get; } - - private string IntermediateFolder { get; } - - public void Execute() - { - this.OutputPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml"); - - using (var writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) - { - writer.WriteStartDocument(); - - writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); - - // Write the condition, if there is one - if (null != this.BundleSymbol.Condition) - { - writer.WriteElementString("Condition", this.BundleSymbol.Condition); - } - - // Write the log element if default logging wasn't disabled. - if (!String.IsNullOrEmpty(this.BundleSymbol.LogPrefix)) - { - writer.WriteStartElement("Log"); - if (!String.IsNullOrEmpty(this.BundleSymbol.LogPathVariable)) - { - writer.WriteAttributeString("PathVariable", this.BundleSymbol.LogPathVariable); - } - writer.WriteAttributeString("Prefix", this.BundleSymbol.LogPrefix); - writer.WriteAttributeString("Extension", this.BundleSymbol.LogExtension); - writer.WriteEndElement(); - } - - - // Get update if specified. - var updateSymbol = this.Section.Symbols.OfType().FirstOrDefault(); - - if (null != updateSymbol) - { - writer.WriteStartElement("Update"); - writer.WriteAttributeString("Location", updateSymbol.Location); - writer.WriteEndElement(); // - } - - // Write the RelatedBundle elements - - // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates - // enumeration in the index row list is not used). - var relatedBundles = this.Section.Symbols.OfType(); - var distinctRelatedBundles = new HashSet(); - - foreach (var relatedBundle in relatedBundles) - { - if (distinctRelatedBundles.Add(relatedBundle.BundleId)) - { - writer.WriteStartElement("RelatedBundle"); - writer.WriteAttributeString("Id", relatedBundle.BundleId); - writer.WriteAttributeString("Action", relatedBundle.Action.ToString()); - writer.WriteEndElement(); - } - } - - // Write the variables - var variables = this.Section.Symbols.OfType(); - - foreach (var variable in variables) - { - writer.WriteStartElement("Variable"); - writer.WriteAttributeString("Id", variable.Id.Id); - if (variable.Type != WixBundleVariableType.Unknown) - { - writer.WriteAttributeString("Value", variable.Value); - - switch (variable.Type) - { - case WixBundleVariableType.Formatted: - writer.WriteAttributeString("Type", "formatted"); - break; - case WixBundleVariableType.Numeric: - writer.WriteAttributeString("Type", "numeric"); - break; - case WixBundleVariableType.String: - writer.WriteAttributeString("Type", "string"); - break; - case WixBundleVariableType.Version: - writer.WriteAttributeString("Type", "version"); - break; - } - } - writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no"); - writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no"); - writer.WriteEndElement(); - } - - // Write the searches - foreach (var searchinfo in this.OrderedSearches) - { - searchinfo.WriteXml(writer); - } - - // write the UX element - writer.WriteStartElement("UX"); - if (!String.IsNullOrEmpty(this.BundleSymbol.SplashScreenSourceFile)) - { - writer.WriteAttributeString("SplashScreen", "yes"); - } - - // write the UX allPayloads... - foreach (var payload in this.UXContainerPayloads) - { - this.WriteBurnManifestUXPayload(writer, payload); - } - - writer.WriteEndElement(); // - - foreach (var container in this.Containers) - { - if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) - { - writer.WriteStartElement("Container"); - this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); - writer.WriteEndElement(); - } - } - - foreach (var payload in this.Payloads.Values.Where(p => p.ContainerRef != BurnConstants.BurnUXContainerName)) - { - this.WriteBurnManifestPayload(writer, payload); - } - - foreach (var rollbackBoundary in this.RollbackBoundaries) - { - writer.WriteStartElement("RollbackBoundary"); - writer.WriteAttributeString("Id", rollbackBoundary.Id.Id); - writer.WriteAttributeString("Vital", rollbackBoundary.Vital == false ? "no" : "yes"); - writer.WriteAttributeString("Transaction", rollbackBoundary.Transaction == true ? "yes" : "no"); - writer.WriteEndElement(); - } - - // Write the registration information... - writer.WriteStartElement("Registration"); - - writer.WriteAttributeString("Id", this.BundleSymbol.BundleId); - writer.WriteAttributeString("ExecutableName", this.ExecutableName); - writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); - writer.WriteAttributeString("Tag", this.BundleSymbol.Tag); - writer.WriteAttributeString("Version", this.BundleSymbol.Version); - writer.WriteAttributeString("ProviderKey", this.BundleSymbol.ProviderKey); - - writer.WriteStartElement("Arp"); - writer.WriteAttributeString("Register", (this.BundleSymbol.DisableModify || this.BundleSymbol.SingleChangeUninstallButton) && this.BundleSymbol.DisableRemove ? "no" : "yes"); // do not register if disabled modify and remove. - writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); - writer.WriteAttributeString("DisplayVersion", this.BundleSymbol.Version); - - if (!String.IsNullOrEmpty(this.BundleSymbol.Manufacturer)) - { - writer.WriteAttributeString("Publisher", this.BundleSymbol.Manufacturer); - } - - if (!String.IsNullOrEmpty(this.BundleSymbol.HelpUrl)) - { - writer.WriteAttributeString("HelpLink", this.BundleSymbol.HelpUrl); - } - - if (!String.IsNullOrEmpty(this.BundleSymbol.HelpTelephone)) - { - writer.WriteAttributeString("HelpTelephone", this.BundleSymbol.HelpTelephone); - } - - if (!String.IsNullOrEmpty(this.BundleSymbol.AboutUrl)) - { - writer.WriteAttributeString("AboutUrl", this.BundleSymbol.AboutUrl); - } - - if (!String.IsNullOrEmpty(this.BundleSymbol.UpdateUrl)) - { - writer.WriteAttributeString("UpdateUrl", this.BundleSymbol.UpdateUrl); - } - - if (!String.IsNullOrEmpty(this.BundleSymbol.ParentName)) - { - writer.WriteAttributeString("ParentDisplayName", this.BundleSymbol.ParentName); - } - - if (this.BundleSymbol.DisableModify) - { - writer.WriteAttributeString("DisableModify", "yes"); - } - - if (this.BundleSymbol.DisableRemove) - { - writer.WriteAttributeString("DisableRemove", "yes"); - } - - if (this.BundleSymbol.SingleChangeUninstallButton) - { - writer.WriteAttributeString("DisableModify", "button"); - } - writer.WriteEndElement(); // - - // Get update registration if specified. - var updateRegistrationInfo = this.Section.Symbols.OfType().FirstOrDefault(); - - if (null != updateRegistrationInfo) - { - writer.WriteStartElement("Update"); // - writer.WriteAttributeString("Manufacturer", updateRegistrationInfo.Manufacturer); - - if (!String.IsNullOrEmpty(updateRegistrationInfo.Department)) - { - writer.WriteAttributeString("Department", updateRegistrationInfo.Department); - } - - if (!String.IsNullOrEmpty(updateRegistrationInfo.ProductFamily)) - { - writer.WriteAttributeString("ProductFamily", updateRegistrationInfo.ProductFamily); - } - - writer.WriteAttributeString("Name", updateRegistrationInfo.Name); - writer.WriteAttributeString("Classification", updateRegistrationInfo.Classification); - writer.WriteEndElement(); // - } - - foreach (var bundleTagSymbol in this.Section.Symbols.OfType()) - { - writer.WriteStartElement("SoftwareTag"); - writer.WriteAttributeString("Filename", bundleTagSymbol.Filename); - writer.WriteAttributeString("Regid", bundleTagSymbol.Regid); - writer.WriteAttributeString("Path", bundleTagSymbol.InstallPath); - writer.WriteCData(bundleTagSymbol.Xml); - writer.WriteEndElement(); - } - - writer.WriteEndElement(); // - - // write the Chain... - writer.WriteStartElement("Chain"); - if (this.Chain.DisableRollback) - { - writer.WriteAttributeString("DisableRollback", "yes"); - } - - if (this.Chain.DisableSystemRestore) - { - writer.WriteAttributeString("DisableSystemRestore", "yes"); - } - - if (this.Chain.ParallelCache) - { - writer.WriteAttributeString("ParallelCache", "yes"); - } - - // Index a few tables by package. - var targetCodesByPatch = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); - var msiFeaturesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); - var msiPropertiesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); - var relatedPackagesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); - var slipstreamMspsByPackage = this.Section.Symbols.OfType().ToLookup(r => r.TargetPackageRef); - var exitCodesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.ChainPackageId); - var commandLinesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.WixBundlePackageRef); - - var dependenciesByPackage = this.Section.Symbols.OfType().ToLookup(p => p.ParentRef); - - - // Build up the list of target codes from all the MSPs in the chain. - var targetCodes = new List(); - - foreach (var package in this.OrderedPackages) - { - writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.PackageSymbol.Type)); - - writer.WriteAttributeString("Id", package.PackageId); - - switch (package.PackageSymbol.Cache) - { - case YesNoAlwaysType.No: - writer.WriteAttributeString("Cache", "remove"); - break; - case YesNoAlwaysType.Yes: - writer.WriteAttributeString("Cache", "keep"); - break; - case YesNoAlwaysType.Always: - writer.WriteAttributeString("Cache", "force"); - break; - } - - writer.WriteAttributeString("CacheId", package.PackageSymbol.CacheId); - writer.WriteAttributeString("InstallSize", Convert.ToString(package.PackageSymbol.InstallSize)); - writer.WriteAttributeString("Size", Convert.ToString(package.PackageSymbol.Size)); - writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.PackageSymbol.PerMachine ? "yes" : "no"); - writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); - writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == false ? "no" : "yes"); - - if (null != package.PackageSymbol.RollbackBoundaryRef) - { - writer.WriteAttributeString("RollbackBoundaryForward", package.PackageSymbol.RollbackBoundaryRef); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackBoundaryBackwardRef)) - { - writer.WriteAttributeString("RollbackBoundaryBackward", package.PackageSymbol.RollbackBoundaryBackwardRef); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.LogPathVariable)) - { - writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackLogPathVariable)) - { - writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); - } - - if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) - { - writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); - } - - if (package.SpecificPackageSymbol is WixBundleExePackageSymbol exePackage) // EXE - { - writer.WriteAttributeString("DetectCondition", exePackage.DetectCondition); - writer.WriteAttributeString("InstallArguments", exePackage.InstallCommand); - writer.WriteAttributeString("UninstallArguments", exePackage.UninstallCommand); - writer.WriteAttributeString("RepairArguments", exePackage.RepairCommand); - writer.WriteAttributeString("Repairable", exePackage.Repairable ? "yes" : "no"); - if (!String.IsNullOrEmpty(exePackage.ExeProtocol)) - { - writer.WriteAttributeString("Protocol", exePackage.ExeProtocol); - } - } - else if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) // MSI - { - writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); - writer.WriteAttributeString("Language", msiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Version", msiPackage.ProductVersion); - if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); - } - } - else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) // MSP - { - writer.WriteAttributeString("PatchCode", mspPackage.PatchCode); - writer.WriteAttributeString("PatchXml", mspPackage.PatchXml); - - // If there is still a chance that all of our patches will target a narrow set of - // product codes, add the patch list to the overall list. - if (null != targetCodes) - { - if (!mspPackage.TargetUnspecified) - { - var patchTargetCodes = targetCodesByPatch[mspPackage.Id.Id]; - - targetCodes.AddRange(patchTargetCodes); - } - else // we have a patch that targets the world, so throw the whole list away. - { - targetCodes = null; - } - } - } - else if (package.SpecificPackageSymbol is WixBundleMsuPackageSymbol msuPackage) // MSU - { - writer.WriteAttributeString("DetectCondition", msuPackage.DetectCondition); - writer.WriteAttributeString("KB", msuPackage.MsuKB); - } - - var packageMsiFeatures = msiFeaturesByPackage[package.PackageId]; - - foreach (var feature in packageMsiFeatures) - { - writer.WriteStartElement("MsiFeature"); - writer.WriteAttributeString("Id", feature.Name); - writer.WriteEndElement(); - } - - var packageMsiProperties = msiPropertiesByPackage[package.PackageId]; - - foreach (var msiProperty in packageMsiProperties) - { - writer.WriteStartElement("MsiProperty"); - writer.WriteAttributeString("Id", msiProperty.Name); - writer.WriteAttributeString("Value", msiProperty.Value); - if (!String.IsNullOrEmpty(msiProperty.Condition)) - { - writer.WriteAttributeString("Condition", msiProperty.Condition); - } - writer.WriteEndElement(); - } - - var packageSlipstreamMsps = slipstreamMspsByPackage[package.PackageId]; - - foreach (var slipstreamMsp in packageSlipstreamMsps) - { - writer.WriteStartElement("SlipstreamMsp"); - writer.WriteAttributeString("Id", slipstreamMsp.MspPackageRef); - writer.WriteEndElement(); - } - - var packageExitCodes = exitCodesByPackage[package.PackageId]; - - foreach (var exitCode in packageExitCodes) - { - writer.WriteStartElement("ExitCode"); - - if (exitCode.Code.HasValue) - { - writer.WriteAttributeString("Code", unchecked((uint)exitCode.Code).ToString(CultureInfo.InvariantCulture)); - } - else - { - writer.WriteAttributeString("Code", "*"); - } - - writer.WriteAttributeString("Type", ((int)exitCode.Behavior).ToString(CultureInfo.InvariantCulture)); - writer.WriteEndElement(); - } - - var packageCommandLines = commandLinesByPackage[package.PackageId]; - - foreach (var commandLine in packageCommandLines) - { - writer.WriteStartElement("CommandLine"); - writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument); - writer.WriteAttributeString("UninstallArgument", commandLine.UninstallArgument); - writer.WriteAttributeString("RepairArgument", commandLine.RepairArgument); - writer.WriteAttributeString("Condition", commandLine.Condition); - writer.WriteEndElement(); - } - - // Output the dependency information. - var dependencies = dependenciesByPackage[package.PackageId]; - - foreach (var dependency in dependencies) - { - writer.WriteStartElement("Provides"); - writer.WriteAttributeString("Key", dependency.ProviderKey); - - if (!String.IsNullOrEmpty(dependency.Version)) - { - writer.WriteAttributeString("Version", dependency.Version); - } - - if (!String.IsNullOrEmpty(dependency.DisplayName)) - { - writer.WriteAttributeString("DisplayName", dependency.DisplayName); - } - - if (dependency.Imported) - { - // The package dependency was explicitly authored into the manifest. - writer.WriteAttributeString("Imported", "yes"); - } - - writer.WriteEndElement(); - } - - var packageRelatedPackages = relatedPackagesByPackage[package.PackageId]; - - foreach (var related in packageRelatedPackages) - { - writer.WriteStartElement("RelatedPackage"); - writer.WriteAttributeString("Id", related.RelatedId); - if (!String.IsNullOrEmpty(related.MinVersion)) - { - writer.WriteAttributeString("MinVersion", related.MinVersion); - writer.WriteAttributeString("MinInclusive", related.MinInclusive ? "yes" : "no"); - } - if (!String.IsNullOrEmpty(related.MaxVersion)) - { - writer.WriteAttributeString("MaxVersion", related.MaxVersion); - writer.WriteAttributeString("MaxInclusive", related.MaxInclusive ? "yes" : "no"); - } - writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no"); - - var relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (0 < relatedLanguages.Length) - { - writer.WriteAttributeString("LangInclusive", related.LangInclusive ? "yes" : "no"); - foreach (string language in relatedLanguages) - { - writer.WriteStartElement("Language"); - writer.WriteAttributeString("Id", language); - writer.WriteEndElement(); - } - } - writer.WriteEndElement(); - } - - // Write any contained Payloads with the PackagePayload being first - var packagePayloadId = package.PackageSymbol.PayloadRef; - writer.WriteStartElement("PayloadRef"); - writer.WriteAttributeString("Id", packagePayloadId); - writer.WriteEndElement(); - - var packagePayloads = this.PackagesPayloads[package.PackageId]; - - foreach (var payload in packagePayloads.Values) - { - if (payload.Id.Id != packagePayloadId) - { - writer.WriteStartElement("PayloadRef"); - writer.WriteAttributeString("Id", payload.Id.Id); - writer.WriteEndElement(); - } - } - - writer.WriteEndElement(); // - } - writer.WriteEndElement(); // - - if (null != targetCodes) - { - foreach (var targetCode in targetCodes) - { - writer.WriteStartElement("PatchTargetCode"); - writer.WriteAttributeString("TargetCode", targetCode.TargetCode); - writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no"); - writer.WriteEndElement(); - } - } - - // Write the ApprovedExeForElevation elements. - var approvedExesForElevation = this.Section.Symbols.OfType(); - - foreach (var approvedExeForElevation in approvedExesForElevation) - { - writer.WriteStartElement("ApprovedExeForElevation"); - writer.WriteAttributeString("Id", approvedExeForElevation.Id.Id); - writer.WriteAttributeString("Key", approvedExeForElevation.Key); - - if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName)) - { - writer.WriteAttributeString("ValueName", approvedExeForElevation.ValueName); - } - - if (approvedExeForElevation.Win64) - { - writer.WriteAttributeString("Win64", "yes"); - } - - writer.WriteEndElement(); - } - - // Write the BundleExtension elements. - var bundleExtensions = this.Section.Symbols.OfType(); - - foreach (var bundleExtension in bundleExtensions) - { - writer.WriteStartElement("BundleExtension"); - writer.WriteAttributeString("Id", bundleExtension.Id.Id); - writer.WriteAttributeString("EntryPayloadId", bundleExtension.PayloadRef); - - writer.WriteEndElement(); - } - - writer.WriteEndDocument(); // - } - } - - private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerSymbol container) - { - writer.WriteAttributeString("Id", container.Id.Id); - writer.WriteAttributeString("FileSize", container.Size.Value.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Hash", container.Hash); - - if (ContainerType.Detached == container.Type) - { - if (!String.IsNullOrEmpty(container.DownloadUrl)) - { - writer.WriteAttributeString("DownloadUrl", container.DownloadUrl); - } - - writer.WriteAttributeString("FilePath", container.Name); - } - else if (ContainerType.Attached == container.Type) - { - writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. - writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.Value.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Attached", "yes"); - writer.WriteAttributeString("Primary", "yes"); - } - } - - private void WriteBurnManifestPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) - { - writer.WriteStartElement("Payload"); - - writer.WriteAttributeString("Id", payload.Id.Id); - writer.WriteAttributeString("FilePath", payload.Name); - writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Hash", payload.Hash); - - if (payload.LayoutOnly) - { - writer.WriteAttributeString("LayoutOnly", "yes"); - } - - if (!String.IsNullOrEmpty(payload.DownloadUrl)) - { - writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); - } - - switch (payload.Packaging) - { - case PackagingType.Embedded: // this means it's in a container. - Debug.Assert(BurnConstants.BurnUXContainerName != payload.ContainerRef); - - writer.WriteAttributeString("Packaging", "embedded"); - writer.WriteAttributeString("SourcePath", payload.EmbeddedId); - writer.WriteAttributeString("Container", payload.ContainerRef); - break; - - case PackagingType.External: - writer.WriteAttributeString("Packaging", "external"); - writer.WriteAttributeString("SourcePath", payload.Name); - break; - } - - writer.WriteEndElement(); - } - - private void WriteBurnManifestUXPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) - { - Debug.Assert(PackagingType.Embedded == payload.Packaging); - Debug.Assert(BurnConstants.BurnUXContainerName == payload.ContainerRef); - - writer.WriteStartElement("Payload"); - - // TODO: The engine should be updated to not require FileSize, Hash, or Packaging for UX payloads since the values are never used. - writer.WriteAttributeString("Id", payload.Id.Id); - writer.WriteAttributeString("FilePath", payload.Name); - writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); - writer.WriteAttributeString("Hash", payload.Hash); - writer.WriteAttributeString("Packaging", "embedded"); - writer.WriteAttributeString("SourcePath", payload.EmbeddedId); - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs deleted file mode 100644 index 87a63cc3..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - /// - /// Creates cabinet files. - /// - internal class CreateContainerCommand - { - public CreateContainerCommand(IEnumerable payloads, string outputPath, CompressionLevel? compressionLevel) - { - this.Payloads = payloads; - this.OutputPath = outputPath; - this.CompressionLevel = compressionLevel; - } - - public CreateContainerCommand(string manifestPath, IEnumerable payloads, string outputPath, CompressionLevel? compressionLevel) - { - this.ManifestFile = manifestPath; - this.Payloads = payloads; - this.OutputPath = outputPath; - this.CompressionLevel = compressionLevel; - } - - private CompressionLevel? CompressionLevel { get; } - - private string ManifestFile { get; } - - private string OutputPath { get; } - - private IEnumerable Payloads { get; } - - public string Hash { get; private set; } - - public long Size { get; private set; } - - public void Execute() - { - var cabinetPath = Path.GetFullPath(this.OutputPath); - - var files = new List(); - - // If a manifest was provided always add it as "payload 0" to the container. - if (!String.IsNullOrEmpty(this.ManifestFile)) - { - files.Add(new CabinetCompressFile(this.ManifestFile, "0")); - } - - files.AddRange(this.Payloads.Select(p => new CabinetCompressFile(p.SourceFile.Path, p.EmbeddedId))); - - var cab = new Cabinet(cabinetPath); - cab.Compress(files, this.CompressionLevel ?? Data.CompressionLevel.Medium); - - // Now that the container is created, set the outputs of the command. - var fileInfo = new FileInfo(cabinetPath); - - this.Hash = BundleHashAlgorithm.Hash(fileInfo); - - this.Size = fileInfo.Length; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs deleted file mode 100644 index f020ed84..00000000 --- a/src/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs +++ /dev/null @@ -1,151 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CreateNonUXContainers - { - public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable containerSymbols, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) - { - this.BackendHelper = backendHelper; - this.Messaging = messaging; - this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; - this.Containers = containerSymbols; - this.PayloadSymbols = payloadSymbols; - this.IntermediateFolder = intermediateFolder; - this.LayoutFolder = layoutFolder; - this.DefaultCompressionLevel = defaultCompressionLevel; - } - - public IEnumerable FileTransfers { get; private set; } - - public IEnumerable TrackedFiles { get; private set; } - - public WixBundleContainerSymbol UXContainer { get; set; } - - public IEnumerable UXContainerPayloads { get; private set; } - - private IEnumerable Containers { get; } - - private IBackendHelper BackendHelper { get; } - - private IMessaging Messaging { get; } - - private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } - - private Dictionary PayloadSymbols { get; } - - private string IntermediateFolder { get; } - - private string LayoutFolder { get; } - - private CompressionLevel? DefaultCompressionLevel { get; } - - public void Execute() - { - var fileTransfers = new List(); - var trackedFiles = new List(); - var uxPayloadSymbols = new List(); - - var attachedContainerIndex = 1; // count starts at one because UX container is "0". - - var payloadsByContainer = this.PayloadSymbols.Values.ToLookup(p => p.ContainerRef); - - foreach (var container in this.Containers) - { - var containerId = container.Id.Id; - - var containerPayloads = payloadsByContainer[containerId]; - - if (!containerPayloads.Any()) - { - if (containerId != BurnConstants.BurnDefaultAttachedContainerName) - { - this.Messaging.Write(BurnBackendWarnings.EmptyContainer(container.SourceLineNumbers, containerId)); - } - } - else if (BurnConstants.BurnUXContainerName == containerId) - { - this.UXContainer = container; - - container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); - container.AttachedContainerIndex = 0; - - // Gather the list of UX payloads but ensure the BootstrapperApplicationDll Payload is the first - // in the list since that is the Payload that Burn attempts to load. - var baPayloadId = this.BootstrapperApplicationDllSymbol.Id.Id; - - foreach (var uxPayload in containerPayloads) - { - if (uxPayload.Id.Id == baPayloadId) - { - uxPayloadSymbols.Insert(0, uxPayload); - } - else - { - uxPayloadSymbols.Add(uxPayload); - } - } - } - else - { - container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); - - // Add detached containers to the list of file transfers. - if (ContainerType.Detached == container.Type) - { - var transfer = this.BackendHelper.CreateFileTransfer(container.WorkingPath, Path.Combine(this.LayoutFolder, container.Name), true, container.SourceLineNumbers); - fileTransfers.Add(transfer); - } - else // update the attached container index. - { - Debug.Assert(ContainerType.Attached == container.Type); - - container.AttachedContainerIndex = attachedContainerIndex; - ++attachedContainerIndex; - } - } - } - - foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName)) - { - if (container.Type == ContainerType.Attached && attachedContainerIndex > 2 && container.Id.Id != BurnConstants.BurnDefaultAttachedContainerName) - { - this.Messaging.Write(BurnBackendErrors.MultipleAttachedContainersUnsupported(container.SourceLineNumbers, container.Id.Id)); - } - } - - if (!this.Messaging.EncounteredError) - { - foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName)) - { - this.CreateContainer(container, payloadsByContainer[container.Id.Id]); - trackedFiles.Add(this.BackendHelper.TrackFile(container.WorkingPath, TrackedFileType.Temporary, container.SourceLineNumbers)); - } - } - - this.UXContainerPayloads = uxPayloadSymbols; - this.FileTransfers = fileTransfers; - this.TrackedFiles = trackedFiles; - } - - private void CreateContainer(WixBundleContainerSymbol container, IEnumerable containerPayloads) - { - var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); - command.Execute(); - - container.Hash = command.Hash; - container.Size = command.Size; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs b/src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs deleted file mode 100644 index bfb6b918..00000000 --- a/src/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs +++ /dev/null @@ -1,137 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class DetectPayloadCollisionsCommand - { - public DetectPayloadCollisionsCommand(IMessaging messaging, Dictionary containerSymbols, IEnumerable packages, Dictionary payloadSymbols, Dictionary> packagePayloads) - { - this.Messaging = messaging; - this.Containers = containerSymbols; - this.Packages = packages; - this.PayloadSymbols = payloadSymbols; - this.PackagePayloads = packagePayloads; - } - - private IMessaging Messaging { get; } - - private Dictionary Containers { get; } - - private IEnumerable Packages { get; } - - private Dictionary PayloadSymbols { get; } - - private Dictionary> PackagePayloads { get; } - - public void Execute() - { - this.DetectAttachedContainerCollisions(); - this.DetectExternalCollisions(); - this.DetectPackageCacheCollisions(); - } - - public void DetectAttachedContainerCollisions() - { - var attachedContainerPayloadsByNameByContainer = new Dictionary>(); - - foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.Embedded)) - { - var containerId = payload.ContainerRef; - var container = this.Containers[containerId]; - if (container.Type == ContainerType.Attached) - { - if (!attachedContainerPayloadsByNameByContainer.TryGetValue(containerId, out var attachedContainerPayloadsByName)) - { - attachedContainerPayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); - attachedContainerPayloadsByNameByContainer.Add(containerId, attachedContainerPayloadsByName); - } - - if (!attachedContainerPayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) - { - attachedContainerPayloadsByName.Add(payload.Name, payload); - } - else - { - if (containerId == BurnConstants.BurnUXContainerName) - { - this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); - this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); - } - else - { - this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); - this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); - } - } - } - } - } - - public void DetectExternalCollisions() - { - var externalPayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.External)) - { - if (!externalPayloadsByName.TryGetValue(payload.Name, out var collisionSymbol)) - { - externalPayloadsByName.Add(payload.Name, payload); - } - else - { - this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(payload.SourceLineNumbers, "Payload", payload.Id.Id, payload.Name)); - this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); - } - } - - foreach (var container in this.Containers.Values.Where(c => c.Type == ContainerType.Detached)) - { - if (!externalPayloadsByName.TryGetValue(container.Name, out var collisionSymbol)) - { - externalPayloadsByName.Add(container.Name, container); - } - else - { - this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(container.SourceLineNumbers, "Container", container.Id.Id, container.Name)); - this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); - } - } - } - - public void DetectPackageCacheCollisions() - { - var packageCachePayloadsByNameByCacheId = new Dictionary>(); - - foreach (var packageFacade in this.Packages) - { - var packagePayloads = this.PackagePayloads[packageFacade.PackageId]; - if (!packageCachePayloadsByNameByCacheId.TryGetValue(packageFacade.PackageSymbol.CacheId, out var packageCachePayloadsByName)) - { - packageCachePayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); - packageCachePayloadsByNameByCacheId.Add(packageFacade.PackageSymbol.CacheId, packageCachePayloadsByName); - } - - foreach (var payload in packagePayloads.Values) - { - if (!packageCachePayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) - { - packageCachePayloadsByName.Add(payload.Name, payload); - } - else - { - this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name, packageFacade.PackageId)); - this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision2(collisionPayload.SourceLineNumbers)); - } - } - } - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs deleted file mode 100644 index b8b256fd..00000000 --- a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs +++ /dev/null @@ -1,181 +0,0 @@ -// 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.Burn.Bundles -{ - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class GetPackageFacadesCommand - { - public GetPackageFacadesCommand(IMessaging messaging, IEnumerable chainPackageSymbols, IntermediateSection section) - { - this.Messaging = messaging; - this.ChainPackageSymbols = chainPackageSymbols; - this.Section = section; - } - - private IEnumerable ChainPackageSymbols { get; } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - public IDictionary PackageFacades { get; private set; } - - public void Execute() - { - var wixGroupPackagesGroupedById = this.Section.Symbols.OfType().Where(g => g.ParentType == ComplexReferenceParentType.Package).ToLookup(g => g.ParentId); - var exePackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var msiPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var mspPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var msuPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var exePackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var msiPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var mspPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - var msuPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - - var facades = new Dictionary(); - - foreach (var package in this.ChainPackageSymbols) - { - var id = package.Id.Id; - - IntermediateSymbol packagePayload = null; - foreach (var wixGroup in wixGroupPackagesGroupedById[id]) - { - if (wixGroup.ChildType == ComplexReferenceChildType.PackagePayload) - { - IntermediateSymbol tempPackagePayload = null; - if (exePackagePayloads.TryGetValue(wixGroup.ChildId, out var exePackagePayload)) - { - if (package.Type == WixBundlePackageType.Exe) - { - tempPackagePayload = exePackagePayload; - } - else - { - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(exePackagePayload.SourceLineNumbers, "Exe")); - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); - } - } - else if (msiPackagePayloads.TryGetValue(wixGroup.ChildId, out var msiPackagePayload)) - { - if (package.Type == WixBundlePackageType.Msi) - { - tempPackagePayload = msiPackagePayload; - } - else - { - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msiPackagePayload.SourceLineNumbers, "Msi")); - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); - } - } - else if (mspPackagePayloads.TryGetValue(wixGroup.ChildId, out var mspPackagePayload)) - { - if (package.Type == WixBundlePackageType.Msp) - { - tempPackagePayload = mspPackagePayload; - } - else - { - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(mspPackagePayload.SourceLineNumbers, "Msp")); - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); - } - } - else if (msuPackagePayloads.TryGetValue(wixGroup.ChildId, out var msuPackagePayload)) - { - if (package.Type == WixBundlePackageType.Msu) - { - tempPackagePayload = msuPackagePayload; - } - else - { - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msuPackagePayload.SourceLineNumbers, "Msu")); - this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); - } - } - else - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound(package.Type + "PackagePayload", wixGroup.ChildId)); - } - - if (tempPackagePayload != null) - { - if (packagePayload == null) - { - packagePayload = tempPackagePayload; - } - else - { - this.Messaging.Write(ErrorMessages.MultiplePackagePayloads(tempPackagePayload.SourceLineNumbers, id, packagePayload.Id.Id, tempPackagePayload.Id.Id)); - this.Messaging.Write(ErrorMessages.MultiplePackagePayloads2(packagePayload.SourceLineNumbers)); - this.Messaging.Write(ErrorMessages.MultiplePackagePayloads3(package.SourceLineNumbers)); - } - } - } - } - - if (packagePayload == null) - { - this.Messaging.Write(ErrorMessages.MissingPackagePayload(package.SourceLineNumbers, id, package.Type.ToString())); - } - else - { - package.PayloadRef = packagePayload.Id.Id; - } - - switch (package.Type) - { - case WixBundlePackageType.Exe: - if (exePackages.TryGetValue(id, out var exePackage)) - { - facades.Add(id, new PackageFacade(package, exePackage)); - } - else - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleExePackage", id)); - } - break; - - case WixBundlePackageType.Msi: - if (msiPackages.TryGetValue(id, out var msiPackage)) - { - facades.Add(id, new PackageFacade(package, msiPackage)); - } - else - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsiPackage", id)); - } - break; - - case WixBundlePackageType.Msp: - if (mspPackages.TryGetValue(id, out var mspPackage)) - { - facades.Add(id, new PackageFacade(package, mspPackage)); - } - else - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMspPackage", id)); - } - break; - - case WixBundlePackageType.Msu: - if (msuPackages.TryGetValue(id, out var msuPackage)) - { - facades.Add(id, new PackageFacade(package, msuPackage)); - } - else - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsuPackage", id)); - } - break; - } - } - - this.PackageFacades = facades; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs deleted file mode 100644 index ccf6b1c2..00000000 --- a/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs +++ /dev/null @@ -1,171 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class OrderPackagesAndRollbackBoundariesCommand - { - public OrderPackagesAndRollbackBoundariesCommand(IMessaging messaging, IntermediateSection section, IDictionary packageFacades) - { - this.Messaging = messaging; - this.Section = section; - this.PackageFacades = packageFacades; - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - private IDictionary PackageFacades { get; } - - public IEnumerable OrderedPackageFacades { get; private set; } - - public IEnumerable UsedRollbackBoundaries { get; private set; } - - public void Execute() - { - var groupSymbols = this.Section.Symbols.OfType().ToList(); - var boundariesById = this.Section.Symbols.OfType().ToDictionary(b => b.Id.Id); - - var orderedFacades = new List(); - var usedBoundaries = new List(); - - // Process the chain of packages to add them in the correct order - // and assign the forward rollback boundaries as appropriate. Remember - // rollback boundaries are authored as elements in the chain which - // we re-interpret here to add them as attributes on the next available - // package in the chain. Essentially we mark some packages as being - // the start of a rollback boundary when installing and repairing. - // We handle uninstall (aka: backwards) rollback boundaries after - // we get these install/repair (aka: forward) rollback boundaries - // defined. - var pendingRollbackBoundary = new WixBundleRollbackBoundarySymbol(null, new Identifier(AccessModifier.Section, BurnConstants.BundleDefaultBoundaryId)) { Vital = true }; - var lastRollbackBoundary = pendingRollbackBoundary; - var boundaryHadX86Package = false; - var warnedMsiTransaction = false; - - foreach (var groupSymbol in groupSymbols) - { - if (ComplexReferenceChildType.Package == groupSymbol.ChildType && ComplexReferenceParentType.PackageGroup == groupSymbol.ParentType && BurnConstants.BundleChainPackageGroupId == groupSymbol.ParentId) - { - if (this.PackageFacades.TryGetValue(groupSymbol.ChildId, out var facade)) - { - var insideMsiTransaction = lastRollbackBoundary?.Transaction ?? false; - - if (null != pendingRollbackBoundary) - { - // If we used the default boundary, ensure the symbol is added to the section. - if (pendingRollbackBoundary.Id.Id == BurnConstants.BundleDefaultBoundaryId) - { - this.Section.AddSymbol(pendingRollbackBoundary); - } - - if (insideMsiTransaction && !warnedMsiTransaction) - { - warnedMsiTransaction = true; - this.Messaging.Write(WarningMessages.MsiTransactionLimitations(pendingRollbackBoundary.SourceLineNumbers)); - } - - usedBoundaries.Add(pendingRollbackBoundary); - facade.PackageSymbol.RollbackBoundaryRef = pendingRollbackBoundary.Id.Id; - pendingRollbackBoundary = null; - - boundaryHadX86Package = !facade.PackageSymbol.Win64; - } - - // Error if MSI transaction has x86 package preceding x64 packages - if (insideMsiTransaction && boundaryHadX86Package && facade.PackageSymbol.Win64) - { - this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64(facade.PackageSymbol.SourceLineNumbers)); - } - - boundaryHadX86Package |= !facade.PackageSymbol.Win64; - - orderedFacades.Add(facade); - } - else // must be a rollback boundary. - { - // Discard the next rollback boundary if we have a previously defined boundary. - var nextRollbackBoundary = boundariesById[groupSymbol.ChildId]; - if (null != pendingRollbackBoundary) - { - if (pendingRollbackBoundary.Id.Id != BurnConstants.BundleDefaultBoundaryId) - { - this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id.Id)); - } - } - - lastRollbackBoundary = pendingRollbackBoundary = nextRollbackBoundary; - } - } - } - - if (null != pendingRollbackBoundary) - { - this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(pendingRollbackBoundary.SourceLineNumbers, pendingRollbackBoundary.Id.Id)); - } - - // With the forward rollback boundaries assigned, we can now go - // through the packages with rollback boundaries and assign backward - // rollback boundaries. Backward rollback boundaries are used when - // the chain is going "backwards" which (AFAIK) only happens during - // uninstall. - // - // Consider the scenario with three packages: A, B and C. Packages A - // and C are marked as rollback boundary packages and package B is - // not. The naive implementation would execute the chain like this - // (numbers indicate where rollback boundaries would end up): - // install: 1 A B 2 C - // uninstall: 2 C B 1 A - // - // The uninstall chain is wrong, A and B should be grouped together - // not C and B. The fix is to label packages with a "backwards" - // rollback boundary used during uninstall. The backwards rollback - // boundaries are assigned to the package *before* the next rollback - // boundary. Using our example from above again, I'll mark the - // backwards rollback boundaries prime (aka: with '). - // install: 1 A B 1' 2 C 2' - // uninstall: 2' C 2 1' B A 1 - // - // If the marked boundaries are ignored during install you get the - // same thing as above (good) and if the non-marked boundaries are - // ignored during uninstall then A and B are correctly grouped. - // Here's what it looks like without all the markers: - // install: 1 A B 2 C - // uninstall: 2 C 1 B A - // Woot! - string previousRollbackBoundaryId = null; - PackageFacade previousFacade = null; - - foreach (var package in orderedFacades) - { - if (null != package.PackageSymbol.RollbackBoundaryRef) - { - if (null != previousFacade) - { - previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; - } - - previousRollbackBoundaryId = package.PackageSymbol.RollbackBoundaryRef; - } - - previousFacade = package; - } - - if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) - { - previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; - } - - this.OrderedPackageFacades = orderedFacades; - this.UsedRollbackBoundaries = usedBoundaries; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs deleted file mode 100644 index f3afd64e..00000000 --- a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs +++ /dev/null @@ -1,367 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class OrderSearchesCommand - { - public OrderSearchesCommand(IMessaging messaging, IntermediateSection section) - { - this.Messaging = messaging; - this.Section = section; - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - public IDictionary> ExtensionSearchSymbolsByExtensionId { get; private set; } - - public IEnumerable OrderedSearchFacades { get; private set; } - - public void Execute() - { - this.ExtensionSearchSymbolsByExtensionId = new Dictionary>(); - this.OrderedSearchFacades = Array.Empty(); - - var searchSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - if (searchSymbols.Count == 0) - { - // Nothing to do! - return; - } - - var constraints = new Constraints(); - - // Add relational info to our data... - foreach (var searchRelationSymbol in this.Section.Symbols.OfType()) - { - constraints.AddConstraint(searchRelationSymbol.Id.Id, searchRelationSymbol.ParentSearchRef); - } - - this.FindCircularReference(constraints); - - if (this.Messaging.EncounteredError) - { - return; - } - - this.FlattenDependentReferences(constraints); - - // Reorder by topographical sort (http://en.wikipedia.org/wiki/Topological_sorting) - // We use a variation of Kahn (1962) algorithm as described in - // Wikipedia, with the additional criteria that start nodes are sorted - // lexicographically at each step to ensure a deterministic ordering - // based on 'after' dependencies and ID. - var sorter = new TopologicalSort(); - var sortedIds = sorter.Sort(searchSymbols.Keys, constraints); - - // Now, create the search facades with the searches in order... - (var orderedSearchFacades, var extensionSearchSymbolsByExtensionId) = this.OrderSearches(sortedIds, searchSymbols); - - this.OrderedSearchFacades = orderedSearchFacades; - this.ExtensionSearchSymbolsByExtensionId = extensionSearchSymbolsByExtensionId; - } - - /// - /// A dictionary of constraints, mapping an id to a list of ids. - /// - private class Constraints : Dictionary> - { - public void AddConstraint(string id, string afterId) - { - if (!this.ContainsKey(id)) - { - this.Add(id, new List()); - } - - // TODO: Show warning if a constraint is seen twice? - if (!this[id].Contains(afterId)) - { - this[id].Add(afterId); - } - } - - // TODO: Hide other Add methods? - } - - /// - /// Finds circular references in the constraints. - /// - /// Constraints to check. - /// This is not particularly performant, but it works. - private void FindCircularReference(Constraints constraints) - { - foreach (var id in constraints.Keys) - { - var seenIds = new List(); - - if (this.FindCircularReference(constraints, id, id, seenIds, out var chain)) - { - // We will show a separate message for every ID that's in - // the loop. We could bail after the first one, but then - // we wouldn't catch disjoint loops in a single run. - this.Messaging.Write(ErrorMessages.CircularSearchReference(chain)); - } - } - } - - /// - /// Recursive function that finds circular references in the constraints. - /// - /// Constraints to check. - /// The identifier currently being looking for. (Fixed across a given run.) - /// The idenifier curently being tested. - /// A list of identifiers seen, to ensure each identifier is only expanded once. - /// If a circular reference is found, will contain the chain of references. - /// True if a circular reference is found, false otherwise. - private bool FindCircularReference(Constraints constraints, string checkId, string currentId, List seenIds, out string chain) - { - chain = null; - if (constraints.TryGetValue(currentId, out var afterList)) - { - foreach (string afterId in afterList) - { - if (afterId == checkId) - { - chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, afterId); - return true; - } - - if (!seenIds.Contains(afterId)) - { - seenIds.Add(afterId); - if (this.FindCircularReference(constraints, checkId, afterId, seenIds, out chain)) - { - chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, chain); - return true; - } - } - } - } - - return false; - } - - /// - /// Flattens any dependency chains to simplify reordering. - /// - /// - private void FlattenDependentReferences(Constraints constraints) - { - foreach (string id in constraints.Keys) - { - var flattenedIds = new List(); - this.AddDependentReferences(constraints, id, flattenedIds); - var constraintList = constraints[id]; - foreach (var flattenedId in flattenedIds) - { - if (!constraintList.Contains(flattenedId)) - { - constraintList.Add(flattenedId); - } - } - } - } - - /// - /// Adds dependent references to a list. - /// - /// - /// - /// - private void AddDependentReferences(Constraints constraints, string currentId, List seenIds) - { - if (constraints.TryGetValue(currentId, out var afterList)) - { - foreach (var afterId in afterList) - { - if (!seenIds.Contains(afterId)) - { - seenIds.Add(afterId); - this.AddDependentReferences(constraints, afterId, seenIds); - } - } - } - } - - /// - /// Reorder by topological sort - /// - /// - /// We use a variation of Kahn (1962) algorithm as described in - /// Wikipedia (http://en.wikipedia.org/wiki/Topological_sorting), with - /// the additional criteria that start nodes are sorted lexicographically - /// at each step to ensure a deterministic ordering based on 'after' - /// dependencies and ID. - /// - private class TopologicalSort - { - private readonly List startIds = new List(); - private Constraints constraints; - - /// - /// Reorder by topological sort - /// - /// The complete list of IDs. - /// Constraints to use. - /// The topologically sorted list of IDs. - internal List Sort(IEnumerable allIds, Constraints constraints) - { - this.startIds.Clear(); - this.CopyConstraints(constraints); - - this.FindInitialStartIds(allIds); - - // We always create a new sortedId list, because we return it - // to the caller and don't know what its lifetime may be. - var sortedIds = new List(); - - while (this.startIds.Count > 0) - { - this.SortStartIds(); - - var currentId = this.startIds[0]; - sortedIds.Add(currentId); - this.startIds.RemoveAt(0); - - this.ResolveConstraint(currentId); - } - - return sortedIds; - } - - /// - /// Copies a Constraints set (to prevent modifying the incoming data). - /// - /// Constraints to copy. - private void CopyConstraints(Constraints constraints) - { - this.constraints = new Constraints(); - foreach (var id in constraints.Keys) - { - foreach (var afterId in constraints[id]) - { - this.constraints.AddConstraint(id, afterId); - } - } - } - - /// - /// Finds initial start IDs. (Those with no constraints.) - /// - /// The complete list of IDs. - private void FindInitialStartIds(IEnumerable allIds) - { - foreach (var id in allIds) - { - if (!this.constraints.ContainsKey(id)) - { - this.startIds.Add(id); - } - } - } - - /// - /// Sorts start IDs. - /// - private void SortStartIds() - { - this.startIds.Sort(); - } - - /// - /// Removes the resolved constraint and updates the list of startIds - /// with any now-valid (all constraints resolved) IDs. - /// - /// The ID to resolve from the set of constraints. - private void ResolveConstraint(string resolvedId) - { - var newStartIds = new List(); - - foreach (var id in this.constraints.Keys) - { - if (this.constraints[id].Contains(resolvedId)) - { - this.constraints[id].Remove(resolvedId); - - // If we just removed the last constraint for this - // ID, it is now a valid start ID. - if (this.constraints[id].Count == 0) - { - newStartIds.Add(id); - } - } - } - - foreach (var id in newStartIds) - { - this.constraints.Remove(id); - } - - this.startIds.AddRange(newStartIds); - } - } - - private (IEnumerable, Dictionary>) OrderSearches(IEnumerable sortedIds, Dictionary searchSymbolDictionary) - { - var orderedSearchFacades = new List(); - var extensionSearchSymbolsByExtensionId = new Dictionary>(); - - // TODO: Although the WixSearch tables are defined in the Util extension, - // the Bundle Binder has to know all about them. We hope to revisit all - // of this in the 4.0 timeframe. - var legacySearchesById = this.Section.Symbols - .Where(t => t.Definition.Type == SymbolDefinitionType.WixComponentSearch || - t.Definition.Type == SymbolDefinitionType.WixFileSearch || - t.Definition.Type == SymbolDefinitionType.WixProductSearch || - t.Definition.Type == SymbolDefinitionType.WixRegistrySearch) - .ToDictionary(t => t.Id.Id); - var setVariablesById = this.Section.Symbols - .OfType() - .ToDictionary(t => t.Id.Id); - var extensionSearchesById = this.Section.Symbols - .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag)) - .ToDictionary(t => t.Id.Id); - - foreach (var searchId in sortedIds) - { - var searchSymbol = searchSymbolDictionary[searchId]; - - if (legacySearchesById.TryGetValue(searchId, out var specificSearchSymbol)) - { - orderedSearchFacades.Add(new LegacySearchFacade(searchSymbol, specificSearchSymbol)); - } - else if (setVariablesById.TryGetValue(searchId, out var setVariableSymbol)) - { - orderedSearchFacades.Add(new SetVariableSearchFacade(searchSymbol, setVariableSymbol)); - } - else if (extensionSearchesById.TryGetValue(searchId, out var extensionSearchSymbol)) - { - orderedSearchFacades.Add(new ExtensionSearchFacade(searchSymbol)); - - if (!extensionSearchSymbolsByExtensionId.TryGetValue(searchSymbol.BundleExtensionRef, out var extensionSearchSymbols)) - { - extensionSearchSymbols = new List(); - extensionSearchSymbolsByExtensionId[searchSymbol.BundleExtensionRef] = extensionSearchSymbols; - } - extensionSearchSymbols.Add(extensionSearchSymbol); - } - else - { - this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchSymbol.SourceLineNumbers, searchId)); - } - } - - return (orderedSearchFacades, extensionSearchSymbolsByExtensionId.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable)kvp.Value)); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs b/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs deleted file mode 100644 index 471262de..00000000 --- a/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.Burn.Bundles -{ - using System.Diagnostics; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class PackageFacade - { - public PackageFacade(WixBundlePackageSymbol packageSymbol, IntermediateSymbol specificPackageSymbol) - { - Debug.Assert(packageSymbol.Id.Id == specificPackageSymbol.Id.Id); - - this.PackageSymbol = packageSymbol; - this.SpecificPackageSymbol = specificPackageSymbol; - } - - public string PackageId => this.PackageSymbol.Id.Id; - - public WixBundlePackageSymbol PackageSymbol { get; } - - public IntermediateSymbol SpecificPackageSymbol { get; } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs deleted file mode 100644 index 8d8ea986..00000000 --- a/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using WixToolset.Data.Symbols; - - /// - /// Initializes package state from the Exe contents. - /// - internal class ProcessExePackageCommand - { - public ProcessExePackageCommand(PackageFacade facade, Dictionary payloadSymbols) - { - this.AuthoredPayloads = payloadSymbols; - this.Facade = facade; - } - - public Dictionary AuthoredPayloads { get; } - - public PackageFacade Facade { get; } - - /// - /// Processes the Exe packages to add properties and payloads from the Exe packages. - /// - public void Execute() - { - var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) - { - this.Facade.PackageSymbol.CacheId = packagePayload.Hash; - } - - this.Facade.PackageSymbol.Version = packagePayload.Version; - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs deleted file mode 100644 index 99e2eda5..00000000 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs +++ /dev/null @@ -1,558 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Core.Native.Msi; - - /// - /// Initializes package state from the MSI contents. - /// - internal class ProcessMsiPackageCommand - { - private const string PropertySqlQuery = "SELECT `Value` FROM `Property` WHERE `Property` = ?"; - - public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary packagePayloads) - { - this.Messaging = serviceProvider.GetService(); - this.BackendHelper = serviceProvider.GetService(); - this.PathResolver = serviceProvider.GetService(); - - this.BackendExtensions = backendExtensions; - - this.PackagePayloads = packagePayloads; - this.Section = section; - this.Facade = facade; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private IPathResolver PathResolver { get; } - - private IEnumerable BackendExtensions { get; } - - private Dictionary PackagePayloads { get; } - - private PackageFacade Facade { get; } - - private IntermediateSection Section { get; } - - /// - /// Processes the MSI packages to add properties and payloads from the MSI packages. - /// - public void Execute() - { - var packagePayload = this.PackagePayloads[this.Facade.PackageSymbol.PayloadRef]; - - var msiPackage = (WixBundleMsiPackageSymbol)this.Facade.SpecificPackageSymbol; - - var sourcePath = packagePayload.SourceFile.Path; - var longNamesInImage = false; - var compressed = false; - try - { - using (var db = new Database(sourcePath, OpenDatabase.ReadOnly)) - { - // Read data out of the msi database... - using (var sumInfo = new SummaryInformation(db)) - { - var fileAndElevateFlags = sumInfo.GetNumericProperty(SummaryInformation.Package.FileAndElevatedFlags); - var platformsAndLanguages = sumInfo.GetProperty(SummaryInformation.Package.PlatformsAndLanguages); - - // 1 is the Word Count summary information stream bit that means - // the MSI uses short file names when set. We care about long file - // names so check when the bit is not set. - - longNamesInImage = 0 == (fileAndElevateFlags & 1); - - // 2 is the Word Count summary information stream bit that means - // files are compressed in the MSI by default when the bit is set. - compressed = 2 == (fileAndElevateFlags & 2); - - // 8 is the Word Count summary information stream bit that means - // "Elevated privileges are not required to install this package." - // in MSI 4.5 and below, if this bit is 0, elevation is required. - var perMachine = (0 == (fileAndElevateFlags & 8)); - var x64 = platformsAndLanguages.Contains("x64"); - - this.Facade.PackageSymbol.PerMachine = perMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; - this.Facade.PackageSymbol.Win64 = x64; - } - - string packageName = null; - string packageDescription = null; - string allusers = null; - string fastInstall = null; - string systemComponent = null; - - using (var view = db.OpenView(PropertySqlQuery)) - { - packageName = ProcessMsiPackageCommand.GetProperty(view, "ProductName"); - packageDescription = ProcessMsiPackageCommand.GetProperty(view, "ARPCOMMENTS"); - allusers = ProcessMsiPackageCommand.GetProperty(view, "ALLUSERS"); - fastInstall = ProcessMsiPackageCommand.GetProperty(view, "MSIFASTINSTALL"); - systemComponent = ProcessMsiPackageCommand.GetProperty(view, "ARPSYSTEMCOMPONENT"); - - msiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(view, "ProductCode"); - msiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(view, "UpgradeCode"); - msiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(view, "Manufacturer"); - msiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(view, "ProductLanguage"), CultureInfo.InvariantCulture); - msiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(view, "ProductVersion"); - } - - if (!this.BackendHelper.IsValidFourPartVersion(msiPackage.ProductVersion)) - { - // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? - string version = null; - var versionParts = msiPackage.ProductVersion.Split('.'); - var count = versionParts.Length; - if (0 < count) - { - version = versionParts[0]; - for (var i = 1; i < 4 && i < count; ++i) - { - version = String.Concat(version, ".", versionParts[i]); - } - } - - if (!String.IsNullOrEmpty(version) && this.BackendHelper.IsValidFourPartVersion(version)) - { - this.Messaging.Write(WarningMessages.VersionTruncated(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath, version)); - msiPackage.ProductVersion = version; - } - else - { - this.Messaging.Write(ErrorMessages.InvalidProductVersion(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath)); - } - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) - { - this.Facade.PackageSymbol.CacheId = String.Format("{0}v{1}", msiPackage.ProductCode, msiPackage.ProductVersion); - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName)) - { - this.Facade.PackageSymbol.DisplayName = packageName; - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description)) - { - this.Facade.PackageSymbol.Description = packageDescription; - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Version)) - { - this.Facade.PackageSymbol.Version = msiPackage.ProductVersion; - } - - var payloadNames = this.GetPayloadTargetNames(); - - var msiPropertyNames = this.GetMsiPropertyNames(packagePayload.Id.Id); - - this.SetPerMachineAppropriately(allusers, msiPackage, sourcePath); - - // Ensure the MSI package is appropriately marked visible or not. - this.SetPackageVisibility(systemComponent, msiPackage, msiPropertyNames); - - // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. - if (!String.IsNullOrEmpty(fastInstall)) - { - this.AddMsiProperty(msiPackage, "MSIFASTINSTALL", "7"); - } - - this.CreateRelatedPackages(db); - - // If feature selection is enabled, represent the Feature table in the manifest. - if ((msiPackage.Attributes & WixBundleMsiPackageAttributes.EnableFeatureSelection) == WixBundleMsiPackageAttributes.EnableFeatureSelection) - { - this.CreateMsiFeatures(db); - } - - // Add all external cabinets as package payloads. - this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames); - - // Add all external files as package payloads and calculate the total install size as the rollup of - // File table's sizes. - this.Facade.PackageSymbol.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); - - // Add all dependency providers from the MSI. - this.ImportDependencyProviders(db, msiPackage); - } - } - catch (MsiException e) - { - this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, e.Message)); - } - } - - private ISet GetPayloadTargetNames() - { - var payloadNames = this.PackagePayloads.Values.Select(p => p.Name); - - return new HashSet(payloadNames, StringComparer.OrdinalIgnoreCase); - } - - private ISet GetMsiPropertyNames(string packageId) - { - var properties = this.Section.Symbols.OfType() - .Where(p => p.PackageRef == packageId) - .Select(p => p.Name); - - return new HashSet(properties, StringComparer.Ordinal); - } - - private void SetPerMachineAppropriately(string allusers, WixBundleMsiPackageSymbol msiPackage, string sourcePath) - { - if (msiPackage.ForcePerMachine) - { - if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine) - { - this.Messaging.Write(WarningMessages.PerUserButForcingPerMachine(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); - this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. - } - - // Force ALLUSERS=1 via the MSI command-line. - this.AddMsiProperty(msiPackage, "ALLUSERS", "1"); - } - else - { - if (String.IsNullOrEmpty(allusers)) - { - // Not forced per-machine and no ALLUSERS property, flip back to per-user. - if (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) - { - this.Messaging.Write(WarningMessages.ImplicitlyPerUser(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); - this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.No; - } - } - else if (allusers.Equals("1", StringComparison.Ordinal)) - { - if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine) - { - this.Messaging.Write(ErrorMessages.PerUserButAllUsersEquals1(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); - } - } - else if (allusers.Equals("2", StringComparison.Ordinal)) - { - this.Messaging.Write(WarningMessages.DiscouragedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) ? "machine" : "user")); - } - else - { - this.Messaging.Write(ErrorMessages.UnsupportedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, allusers)); - } - } - } - - private void SetPackageVisibility(string systemComponent, WixBundleMsiPackageSymbol msiPackage, ISet msiPropertyNames) - { - // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. - if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) - { - var alreadyVisible = String.IsNullOrEmpty(systemComponent); - var visible = (this.Facade.PackageSymbol.Attributes & WixBundlePackageAttributes.Visible) == WixBundlePackageAttributes.Visible; - - // If not already set to the correct visibility. - if (alreadyVisible != visible) - { - this.AddMsiProperty(msiPackage, "ARPSYSTEMCOMPONENT", visible ? String.Empty : "1"); - } - } - } - - private void CreateRelatedPackages(Database db) - { - // Represent the Upgrade table as related packages. - if (db.TableExists("Upgrade")) - { - using (var view = db.OpenExecuteView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) - { - foreach (var record in view.Records) - { - var recordAttributes = record.GetInteger(5); - - var attributes = WixBundleRelatedPackageAttributes.None; - attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect) == WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect ? WixBundleRelatedPackageAttributes.OnlyDetect : 0; - attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive ? WixBundleRelatedPackageAttributes.MinInclusive : 0; - attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive ? WixBundleRelatedPackageAttributes.MaxInclusive : 0; - attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive ? WixBundleRelatedPackageAttributes.LangInclusive : 0; - - this.Section.AddSymbol(new WixBundleRelatedPackageSymbol(this.Facade.PackageSymbol.SourceLineNumbers) - { - PackageRef = this.Facade.PackageId, - RelatedId = record.GetString(1), - MinVersion = record.GetString(2), - MaxVersion = record.GetString(3), - Languages = record.GetString(4), - Attributes = attributes, - }); - } - } - } - } - - private void CreateMsiFeatures(Database db) - { - if (db.TableExists("Feature")) - { - using (var allFeaturesView = db.OpenExecuteView("SELECT * FROM `Feature`")) - using (var featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) - using (var componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) - { - using (var featureRecord = new Record(1)) - using (var componentRecord = new Record(1)) - { - foreach (var allFeaturesResultRecord in allFeaturesView.Records) - { - var featureName = allFeaturesResultRecord.GetString(1); - - // Calculate the Feature size. - featureRecord.SetString(1, featureName); - featureView.Execute(featureRecord); - - // Loop over all the components for the feature to calculate the size of the feature. - long size = 0; - foreach (var componentResultRecord in featureView.Records) - { - var component = componentResultRecord.GetString(1); - componentRecord.SetString(1, component); - componentView.Execute(componentRecord); - - foreach (var fileResultRecord in componentView.Records) - { - var fileSize = fileResultRecord.GetString(1); - size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); - } - } - - this.Section.AddSymbol(new WixBundleMsiFeatureSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, this.Facade.PackageId, featureName)) - { - PackageRef = this.Facade.PackageId, - Name = featureName, - Parent = allFeaturesResultRecord.GetString(2), - Title = allFeaturesResultRecord.GetString(3), - Description = allFeaturesResultRecord.GetString(4), - Display = allFeaturesResultRecord.GetInteger(5), - Level = allFeaturesResultRecord.GetInteger(6), - Directory = allFeaturesResultRecord.GetString(7), - Attributes = allFeaturesResultRecord.GetInteger(8), - Size = size - }); - } - } - } - } - } - - private void ImportExternalCabinetAsPayloads(Database db, WixBundlePayloadSymbol packagePayload, ISet payloadNames) - { - if (db.TableExists("Media")) - { - using (var view = db.OpenExecuteView("SELECT `Cabinet` FROM `Media`")) - { - foreach (var cabinetRecord in view.Records) - { - var cabinet = cabinetRecord.GetString(1); - - if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) - { - // If we didn't find the Payload as an existing child of the package, we need to - // add it. We expect the file to exist on-disk in the same relative location as - // the MSI expects to find it... - var cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); - - if (!payloadNames.Contains(cabinetName)) - { - var generatedId = this.BackendHelper.GenerateIdentifier("cab", packagePayload.Id.Id, cabinet); - var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.PackageSymbol.SourceLineNumbers); - - this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) - { - ParentType = ComplexReferenceParentType.Package, - ParentId = this.Facade.PackageId, - ChildType = ComplexReferenceChildType.Payload, - ChildId = generatedId - }); - - this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) - { - Name = cabinetName, - SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, - Compressed = packagePayload.Compressed, - UnresolvedSourceFile = cabinetName, - ContainerRef = packagePayload.ContainerRef, - ContentFile = true, - Packaging = packagePayload.Packaging, - ParentPackagePayloadRef = packagePayload.Id.Id, - }); - } - } - } - } - } - } - - private long ImportExternalFileAsPayloadsAndReturnInstallSize(Database db, WixBundlePayloadSymbol packagePayload, bool longNamesInImage, bool compressed, ISet payloadNames) - { - long size = 0; - - if (db.TableExists("Component") && db.TableExists("Directory") && db.TableExists("File")) - { - var directories = new Dictionary(); - - // Load up the directory hash table so we will be able to resolve source paths - // for files in the MSI database. - using (var view = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) - { - foreach (var record in view.Records) - { - var sourceName = this.BackendHelper.GetMsiFileName(record.GetString(3), true, longNamesInImage); - - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(record.GetString(2), sourceName); - - directories.Add(record.GetString(1), resolvedDirectory); - } - } - - // Resolve the source paths to external files and add each file size to the total - // install size of the package. - using (var view = db.OpenExecuteView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) - { - foreach (var record in view.Records) - { - // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not - // explicitly marked compressed then this is an external file. - var compressionBit = record.GetInteger(4); - if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) || - (!compressed && 0 == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesCompressed))) - { - var fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); - var name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); - - if (!payloadNames.Contains(name)) - { - var generatedId = this.BackendHelper.GenerateIdentifier("f", packagePayload.Id.Id, record.GetString(2)); - var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.PackageSymbol.SourceLineNumbers); - - this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) - { - ParentType = ComplexReferenceParentType.Package, - ParentId = this.Facade.PackageId, - ChildType = ComplexReferenceChildType.Payload, - ChildId = generatedId - }); - - this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) - { - Name = name, - SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, - Compressed = packagePayload.Compressed, - UnresolvedSourceFile = name, - ContainerRef = packagePayload.ContainerRef, - ContentFile = true, - Packaging = packagePayload.Packaging, - ParentPackagePayloadRef = packagePayload.Id.Id, - }); - } - } - - size += record.GetInteger(5); - } - } - } - - return size; - } - - private void AddMsiProperty(WixBundleMsiPackageSymbol msiPackage, string name, string value) - { - this.Section.AddSymbol(new WixBundleMsiPropertySymbol(msiPackage.SourceLineNumbers, new Identifier(AccessModifier.Section, msiPackage.Id.Id, name)) - { - PackageRef = msiPackage.Id.Id, - Name = name, - Value = value, - }); - } - - private void ImportDependencyProviders(Database db, WixBundleMsiPackageSymbol msiPackage) - { - if (db.TableExists("WixDependencyProvider")) - { - using (var view = db.OpenExecuteView("SELECT `WixDependencyProvider`, `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`")) - { - foreach (var record in view.Records) - { - var id = new Identifier(AccessModifier.Section, this.BackendHelper.GenerateIdentifier("dep", msiPackage.Id.Id, record.GetString(1))); - - // Import the provider key and attributes. - this.Section.AddSymbol(new WixDependencyProviderSymbol(msiPackage.SourceLineNumbers, id) - { - ParentRef = msiPackage.Id.Id, - ProviderKey = record.GetString(2), - Version = record.GetString(3) ?? msiPackage.ProductVersion, - DisplayName = record.GetString(4) ?? this.Facade.PackageSymbol.DisplayName, - Attributes = WixDependencyProviderAttributes.ProvidesAttributesImported | (WixDependencyProviderAttributes)record.GetInteger(5), - }); - } - } - } - } - - private string ResolveRelatedFile(string resolvedSource, string unresolvedSource, string relatedSource, string type, SourceLineNumber sourceLineNumbers) - { - var checkedPaths = new List(); - - foreach (var extension in this.BackendExtensions) - { - var resolved = extension.ResolveRelatedFile(unresolvedSource, relatedSource, type, sourceLineNumbers); - - if (resolved?.CheckedPaths != null) - { - checkedPaths.AddRange(resolved.CheckedPaths); - } - - if (!String.IsNullOrEmpty(resolved?.Path)) - { - return resolved?.Path; - } - } - - var resolvedPath = Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource); - - if (!File.Exists(resolvedPath)) - { - checkedPaths.Add(resolvedPath); - this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, resolvedPath, type, checkedPaths)); - } - - return resolvedPath; - } - - private static string GetProperty(View view, string property) - { - using (var queryRecord = new Record(1)) - { - queryRecord[1] = property; - - view.Execute(queryRecord); - - using (var record = view.Fetch()) - { - return record?.GetString(1); - } - } - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs deleted file mode 100644 index 5f431b38..00000000 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs +++ /dev/null @@ -1,183 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Xml; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - /// - /// Initializes package state from the Msp contents. - /// - internal class ProcessMspPackageCommand - { - private const string PatchMetadataQuery = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = ?"; - private static readonly XmlWriterSettings XmlSettings = new XmlWriterSettings() - { - Encoding = new UTF8Encoding(false), - Indent = false, - NewLineChars = String.Empty, - NewLineHandling = NewLineHandling.Replace, - }; - - public ProcessMspPackageCommand(IMessaging messaging, IntermediateSection section, PackageFacade facade, Dictionary payloadSymbols) - { - this.Messaging = messaging; - - this.AuthoredPayloads = payloadSymbols; - this.Section = section; - this.Facade = facade; - } - - public IMessaging Messaging { get; } - - public Dictionary AuthoredPayloads { private get; set; } - - public PackageFacade Facade { private get; set; } - - public IntermediateSection Section { get; } - - /// - /// Processes the Msp packages to add properties and payloads from the Msp packages. - /// - public void Execute() - { - var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; - - var mspPackage = (WixBundleMspPackageSymbol)this.Facade.SpecificPackageSymbol; - - var sourcePath = packagePayload.SourceFile.Path; - - try - { - using (var db = new Database(sourcePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) - { - // Read data out of the msp database... - using (var sumInfo = new SummaryInformation(db)) - { - var patchCode = sumInfo.GetProperty(SummaryInformation.Patch.PatchCode); - mspPackage.PatchCode = patchCode.Substring(0, 38); - } - - using (var view = db.OpenView(PatchMetadataQuery)) - { - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName)) - { - this.Facade.PackageSymbol.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "DisplayName"); - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description)) - { - this.Facade.PackageSymbol.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "Description"); - } - - mspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "ManufacturerName"); - } - } - - this.ProcessPatchXml(packagePayload, mspPackage, sourcePath); - } - catch (MsiException e) - { - this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); - return; - } - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) - { - this.Facade.PackageSymbol.CacheId = mspPackage.PatchCode; - } - } - - private void ProcessPatchXml(WixBundlePayloadSymbol packagePayload, WixBundleMspPackageSymbol mspPackage, string sourcePath) - { - var uniqueTargetCodes = new HashSet(); - - var patchXml = Installer.ExtractPatchXml(sourcePath); - - var doc = new XmlDocument(); - doc.LoadXml(patchXml); - - var nsmgr = new XmlNamespaceManager(doc.NameTable); - nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); - - // Determine target ProductCodes and/or UpgradeCodes. - foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) - { - // If this patch targets a product code, this is the best case. - var targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); - var attributes = WixBundlePatchTargetCodeAttributes.None; - - if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) - { - attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode; - } - else // maybe targets an upgrade code? - { - targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr); - if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) - { - attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode; - } - else // this patch targets an unknown number of products - { - mspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; - } - } - - var targetCode = targetCodeElement.InnerText; - - if (uniqueTargetCodes.Add(targetCode)) - { - this.Section.AddSymbol(new WixBundlePatchTargetCodeSymbol(packagePayload.SourceLineNumbers) - { - PackageRef = packagePayload.Id.Id, - TargetCode = targetCode, - Attributes = attributes - }); - } - } - - // Suppress patch sequence data for improved performance. - var root = doc.DocumentElement; - foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) - { - root.RemoveChild(node); - } - - // Save the XML as compact as possible. - using (var writer = new StringWriter()) - { - using (var xmlWriter = XmlWriter.Create(writer, XmlSettings)) - { - doc.WriteTo(xmlWriter); - } - - mspPackage.PatchXml = writer.ToString(); - } - } - - private static string GetPatchMetadataProperty(View view, string property) - { - using (var queryRecord = new Record(1)) - { - queryRecord[1] = property; - - view.Execute(queryRecord); - - using (var record = view.Fetch()) - { - return record?.GetString(1); - } - } - } - - private static bool TargetsCode(XmlNode node) => "true" == node?.Attributes["Validate"]?.Value; - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs deleted file mode 100644 index af4ab3a8..00000000 --- a/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - /// - /// Processes the Msu packages to add properties and payloads from the Msu packages. - /// - internal class ProcessMsuPackageCommand - { - public ProcessMsuPackageCommand(PackageFacade facade, Dictionary payloadSymbols) - { - this.AuthoredPayloads = payloadSymbols; - this.Facade = facade; - } - - public Dictionary AuthoredPayloads { private get; set; } - - public PackageFacade Facade { private get; set; } - - public void Execute() - { - var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; - - if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) - { - this.Facade.PackageSymbol.CacheId = packagePayload.Hash; - } - - this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs deleted file mode 100644 index fa70251a..00000000 --- a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs +++ /dev/null @@ -1,108 +0,0 @@ -// 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.Burn.Bundles -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using WixToolset.Core.Burn.Interfaces; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ProcessPayloadsCommand - { - public ProcessPayloadsCommand(IBackendHelper backendHelper, IPayloadHarvester payloadHarvester, IEnumerable payloads, PackagingType defaultPackaging, string layoutDirectory) - { - this.BackendHelper = backendHelper; - this.PayloadHarvester = payloadHarvester; - this.Payloads = payloads; - this.DefaultPackaging = defaultPackaging; - this.LayoutDirectory = layoutDirectory; - } - - public IEnumerable FileTransfers { get; private set; } - - public IEnumerable TrackedFiles { get; private set; } - - private IBackendHelper BackendHelper { get; } - - private IPayloadHarvester PayloadHarvester { get; } - - private IEnumerable Payloads { get; } - - private PackagingType DefaultPackaging { get; } - - private string LayoutDirectory { get; } - - public void Execute() - { - var fileTransfers = new List(); - var trackedFiles = new List(); - - foreach (var payload in this.Payloads) - { - payload.Name = this.BackendHelper.GetCanonicalRelativePath(payload.SourceLineNumbers, "Payload", "Name", payload.Name); - - // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden - // in the .wixlib). - var sourceFile = payload.SourceFile; - payload.ContentFile = sourceFile != null && !sourceFile.Embed; - - this.UpdatePayloadPackagingType(payload); - - if (!this.PayloadHarvester.HarvestStandardInformation(payload)) - { - // Remote payloads obviously cannot be embedded. - Debug.Assert(PackagingType.Embedded != payload.Packaging); - } - else // not a remote payload so we have a lot more to update. - { - // External payloads need to be transfered. - if (PackagingType.External == payload.Packaging) - { - var transfer = this.BackendHelper.CreateFileTransfer(sourceFile.Path, Path.Combine(this.LayoutDirectory, payload.Name), false, payload.SourceLineNumbers); - fileTransfers.Add(transfer); - } - - if (payload.ContentFile) - { - trackedFiles.Add(this.BackendHelper.TrackFile(sourceFile.Path, TrackedFileType.Input, payload.SourceLineNumbers)); - } - } - } - - this.FileTransfers = fileTransfers; - this.TrackedFiles = trackedFiles; - } - - private void UpdatePayloadPackagingType(WixBundlePayloadSymbol payload) - { - if (!payload.Packaging.HasValue || PackagingType.Unknown == payload.Packaging) - { - if (!payload.Compressed.HasValue) - { - payload.Packaging = this.DefaultPackaging; - } - else if (payload.Compressed.Value) - { - payload.Packaging = PackagingType.Embedded; - } - else - { - payload.Packaging = PackagingType.External; - } - } - - // Embedded payloads that are not assigned a container already are placed in the default attached - // container. - if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.ContainerRef)) - { - payload.ContainerRef = BurnConstants.BurnDefaultAttachedContainerName; - } - } - } -} diff --git a/src/WixToolset.Core.Burn/BurnBackendErrors.cs b/src/WixToolset.Core.Burn/BurnBackendErrors.cs deleted file mode 100644 index 854c84e0..00000000 --- a/src/WixToolset.Core.Burn/BurnBackendErrors.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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.Burn -{ - using WixToolset.Data; - - internal static class BurnBackendErrors - { - public static Message BAContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) - { - return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the BA container. When extracting the container at runtime, the file will get overwritten.", payloadId, payloadName); - } - - public static Message BAContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) - { - return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision2, "The location of the payload related to the previous error."); - } - - public static Message DuplicateCacheIds(SourceLineNumber originalLineNumber, string cacheId, string packageId) - { - return Message(originalLineNumber, Ids.DuplicateCacheIds, "The CacheId '{0}' for package '{1}' is duplicated. Each package must have a unique CacheId.", cacheId, packageId); - } - - public static Message DuplicateCacheIds2(SourceLineNumber duplicateLineNumber) - { - return Message(duplicateLineNumber, Ids.DuplicateCacheIds2, "The location of the package related to the previous error."); - } - - public static Message ExternalPayloadCollision(SourceLineNumber sourceLineNumbers, string symbolName, string payloadId, string payloadName) - { - return Message(sourceLineNumbers, Ids.ExternalPayloadCollision, "The external {0} '{1}' has a duplicate Name '{2}'. When building the bundle or laying out the bundle, the file will get overwritten.", symbolName, payloadId, payloadName); - } - - public static Message ExternalPayloadCollision2(SourceLineNumber sourceLineNumbers) - { - return Message(sourceLineNumbers, Ids.ExternalPayloadCollision2, "The location of the symbol related to the previous error."); - } - - public static Message MultipleAttachedContainersUnsupported(SourceLineNumber sourceLineNumbers, string containerId) - { - return Message(sourceLineNumbers, Ids.MultipleAttachedContainersUnsupported, "Bundles don't currently support having more than one attached container. Either remove all authored attached containers to use the default attached container, or make sure all compressed payloads are included in this Container '{0}'.", containerId); - } - - public static Message PackageCachePayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName, string packageId) - { - return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in package '{2}'. When caching the package, the file will get overwritten.", payloadId, payloadName, packageId); - } - - public static Message PackageCachePayloadCollision2(SourceLineNumber sourceLineNumbers) - { - return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision2, "The location of the payload related to the previous error."); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); - } - - public enum Ids - { - DuplicateCacheIds = 8000, - DuplicateCacheIds2 = 8001, - BAContainerPayloadCollision = 8002, - BAContainerPayloadCollision2 = 8003, - ExternalPayloadCollision = 8004, - ExternalPayloadCollision2 = 8005, - PackageCachePayloadCollision = 8006, - PackageCachePayloadCollision2 = 8007, - MultipleAttachedContainersUnsupported = 8008, - } // last available is 8499. 8500 is BurnBackendWarnings. - } -} diff --git a/src/WixToolset.Core.Burn/BurnBackendFactory.cs b/src/WixToolset.Core.Burn/BurnBackendFactory.cs deleted file mode 100644 index 03013a08..00000000 --- a/src/WixToolset.Core.Burn/BurnBackendFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Burn -{ - using System; - using System.IO; - using WixToolset.Extensibility; - - internal class BurnBackendFactory : IBackendFactory - { - public bool TryCreateBackend(string outputType, string outputFile, out IBackend backend) - { - if (String.IsNullOrEmpty(outputType)) - { - outputType = Path.GetExtension(outputFile); - } - - switch (outputType.ToLowerInvariant()) - { - case "bundle": - case ".exe": - backend = new BundleBackend(); - return true; - } - - backend = null; - return false; - } - } -} diff --git a/src/WixToolset.Core.Burn/BurnBackendWarnings.cs b/src/WixToolset.Core.Burn/BurnBackendWarnings.cs deleted file mode 100644 index a0ffa1dc..00000000 --- a/src/WixToolset.Core.Burn/BurnBackendWarnings.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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.Burn -{ - using WixToolset.Data; - - internal static class BurnBackendWarnings - { - public static Message AttachedContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) - { - return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", payloadId, payloadName); - } - - public static Message AttachedContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) - { - return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision2, "The location of the payload related to the previous error."); - } - - public static Message EmptyContainer(SourceLineNumber sourceLineNumbers, string containerId) - { - return Message(sourceLineNumbers, Ids.EmptyContainer, "The Container '{0}' is being ignored because it doesn't have any payloads.", containerId); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); - } - - public enum Ids - { - AttachedContainerPayloadCollision = 8500, - AttachedContainerPayloadCollision2 = 8501, - EmptyContainer = 8502, - } // last available is 8999. 9000 is VerboseMessages. - } -} diff --git a/src/WixToolset.Core.Burn/BurnExtensionFactory.cs b/src/WixToolset.Core.Burn/BurnExtensionFactory.cs deleted file mode 100644 index b34d12c1..00000000 --- a/src/WixToolset.Core.Burn/BurnExtensionFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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.Burn -{ - using System; - using WixToolset.Extensibility; - - internal class BurnExtensionFactory : IExtensionFactory - { - public bool TryCreateExtension(Type extensionType, out object extension) - { - extension = null; - - if (extensionType == typeof(IBackendFactory)) - { - extension = new BurnBackendFactory(); - } - - return extension != null; - } - } -} diff --git a/src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs b/src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs deleted file mode 100644 index e4d2b0c9..00000000 --- a/src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs +++ /dev/null @@ -1,214 +0,0 @@ -// 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.Burn.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Xml; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class BurnBackendHelper : IInternalBurnBackendHelper - { - public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment }; - public static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment }; - - private readonly IBackendHelper backendHelper; - - private ManifestData BootstrapperApplicationManifestData { get; } = new ManifestData(); - - private Dictionary BundleExtensionDataById { get; } = new Dictionary(); - - public BurnBackendHelper(IServiceProvider serviceProvider) - { - this.backendHelper = serviceProvider.GetService(); - } - - #region IBackendHelper interfaces - - public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); - - public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); - - public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); - - public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); - - public string CreateGuid() => this.backendHelper.CreateGuid(); - - public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); - - public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); - - public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); - - public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); - - public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); - - public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); - - public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); - - public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); - - public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); - - public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); - - public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); - - public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); - - public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); - - public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); - - public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); - - #endregion - - #region IBurnBackendHelper interfaces - - public void AddBootstrapperApplicationData(string xml) - { - this.BootstrapperApplicationManifestData.AddXml(xml); - } - - public void AddBootstrapperApplicationData(IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) - { - this.BootstrapperApplicationManifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BADataNamespace); - } - - public void AddBundleExtensionData(string extensionId, string xml) - { - var manifestData = this.GetBundleExtensionManifestData(extensionId); - manifestData.AddXml(xml); - } - - public void AddBundleExtensionData(string extensionId, IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) - { - var manifestData = this.GetBundleExtensionManifestData(extensionId); - manifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BundleExtensionDataNamespace); - } - - #endregion - - #region IInternalBurnBackendHelper interfaces - - public void WriteBootstrapperApplicationData(XmlWriter writer) - { - this.BootstrapperApplicationManifestData.Write(writer); - } - - public void WriteBundleExtensionData(XmlWriter writer) - { - foreach (var kvp in this.BundleExtensionDataById) - { - this.WriteExtension(writer, kvp.Key, kvp.Value); - } - } - - #endregion - - private ManifestData GetBundleExtensionManifestData(string extensionId) - { - if (!this.backendHelper.IsValidIdentifier(extensionId)) - { - throw new ArgumentException($"'{extensionId}' is not a valid extensionId"); - } - - if (!this.BundleExtensionDataById.TryGetValue(extensionId, out var manifestData)) - { - manifestData = new ManifestData(); - this.BundleExtensionDataById.Add(extensionId, manifestData); - } - - return manifestData; - } - - private void WriteExtension(XmlWriter writer, string extensionId, ManifestData manifestData) - { - writer.WriteStartElement("BundleExtension"); - - writer.WriteAttributeString("Id", extensionId); - - manifestData.Write(writer); - - writer.WriteEndElement(); - } - - private class ManifestData - { - public ManifestData() - { - this.Builder = new StringBuilder(); - } - - private StringBuilder Builder { get; } - - public void AddSymbol(IntermediateSymbol symbol, bool symbolIdIsIdAttribute, string ns) - { - // There might be a more efficient way to do this, - // but this is an easy way to ensure we're creating valid XML. - var sb = new StringBuilder(); - using (var writer = XmlWriter.Create(sb, WriterSettings)) - { - writer.WriteStartElement(symbol.Definition.Name, ns); - - if (symbolIdIsIdAttribute && symbol.Id != null) - { - writer.WriteAttributeString("Id", symbol.Id.Id); - } - - foreach (var field in symbol.Fields) - { - if (!field.IsNull()) - { - writer.WriteAttributeString(field.Definition.Name, field.AsString()); - } - } - - writer.WriteEndElement(); - } - - this.AddXml(sb.ToString()); - } - - public void AddXml(string xml) - { - // There might be a more efficient way to do this, - // but this is an easy way to ensure we're given valid XML. - var sb = new StringBuilder(); - using (var xmlWriter = XmlWriter.Create(sb, WriterSettings)) - { - AddManifestDataFromString(xmlWriter, xml); - } - this.Builder.Append(sb.ToString()); - } - - public void Write(XmlWriter writer) - { - AddManifestDataFromString(writer, this.Builder.ToString()); - } - - private static void AddManifestDataFromString(XmlWriter xmlWriter, string xml) - { - using (var stringReader = new StringReader(xml)) - using (var xmlReader = XmlReader.Create(stringReader, ReaderSettings)) - { - while (xmlReader.MoveToContent() != XmlNodeType.None) - { - xmlWriter.WriteNode(xmlReader, false); - } - } - } - } - } -} diff --git a/src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs b/src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs deleted file mode 100644 index 9ef91028..00000000 --- a/src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs +++ /dev/null @@ -1,68 +0,0 @@ -// 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.Burn.ExtensibilityServices -{ - using System; - using System.Diagnostics; - using System.IO; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Burn.Interfaces; - using WixToolset.Data.Symbols; - - internal class PayloadHarvester : IPayloadHarvester - { - private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); - - /// - public bool HarvestStandardInformation(WixBundlePayloadSymbol payload) - { - var filePath = payload.SourceFile?.Path; - - if (String.IsNullOrEmpty(filePath)) - { - return false; - } - - this.UpdatePayloadFileInformation(payload, filePath); - - this.UpdatePayloadVersionInformation(payload, filePath); - - return true; - } - - private void UpdatePayloadFileInformation(WixBundlePayloadSymbol payload, string filePath) - { - var fileInfo = new FileInfo(filePath); - - if (null != fileInfo) - { - payload.FileSize = fileInfo.Length; - - payload.Hash = BundleHashAlgorithm.Hash(fileInfo); - } - else - { - payload.FileSize = 0; - } - } - - private void UpdatePayloadVersionInformation(WixBundlePayloadSymbol payload, string filePath) - { - var versionInfo = FileVersionInfo.GetVersionInfo(filePath); - - if (null != versionInfo) - { - // Use the fixed version info block for the file since the resource text may not be a dotted quad. - var version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); - - if (PayloadHarvester.EmptyVersion != version) - { - payload.Version = version.ToString(); - } - - payload.Description = versionInfo.FileDescription; - payload.DisplayName = versionInfo.ProductName; - } - } - } -} diff --git a/src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs b/src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs deleted file mode 100644 index 59c4f20f..00000000 --- a/src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs +++ /dev/null @@ -1,14 +0,0 @@ -// 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.Burn -{ - using System.Xml; - using WixToolset.Extensibility.Services; - - internal interface IInternalBurnBackendHelper : IBurnBackendHelper - { - void WriteBootstrapperApplicationData(XmlWriter writer); - - void WriteBundleExtensionData(XmlWriter writer); - } -} diff --git a/src/WixToolset.Core.Burn/ISearchFacade.cs b/src/WixToolset.Core.Burn/ISearchFacade.cs deleted file mode 100644 index b9ad8649..00000000 --- a/src/WixToolset.Core.Burn/ISearchFacade.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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.Burn -{ - using System.Xml; - - internal interface ISearchFacade - { - /// - /// Writes the search to the Burn manifest. - /// - /// - void WriteXml(XmlTextWriter writer); - } -} diff --git a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs deleted file mode 100644 index b466d0de..00000000 --- a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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.Burn.Inscribe -{ - using System.IO; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Native; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class InscribeBundleCommand - { - public InscribeBundleCommand(IInscribeContext context) - { - this.Context = context; - - this.Messaging = context.ServiceProvider.GetService(); - } - - private IInscribeContext Context { get; } - - public IMessaging Messaging { get; } - - public bool Execute() - { - var inscribed = false; - var tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_signed.exe"); - - using (var reader = BurnReader.Open(this.Context.InputFilePath)) - { - FileSystem.CopyFile(this.Context.SignedEngineFile, tempFile, allowHardlink: false); - - // If there was an attached container on the original (unsigned) bundle, put it back. - if (reader.AttachedContainerSize > 0) - { - reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin); - - using (var writer = BurnWriter.Open(this.Messaging, tempFile)) - { - writer.RememberThenResetSignature(); - writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); - inscribed = true; - } - } - } - - Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile)); - - FileSystem.MoveFile(tempFile, this.Context.OutputFile); - - return inscribed; - } - } -} diff --git a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs deleted file mode 100644 index a6789796..00000000 --- a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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.Burn.Inscribe -{ - using System; - using System.IO; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Core.Native; - using WixToolset.Extensibility.Data; - - internal class InscribeBundleEngineCommand - { - public InscribeBundleEngineCommand(IInscribeContext context) - { - this.IntermediateFolder = context.IntermediateFolder; - this.InputFilePath = context.InputFilePath; - this.OutputFile = context.OutputFile; - } - - private string IntermediateFolder { get; } - - private string InputFilePath { get; } - - private string OutputFile { get; } - - public bool Execute() - { - var tempFile = Path.Combine(this.IntermediateFolder, "bundle_engine_unsigned.exe"); - - using (var reader = BurnReader.Open(this.InputFilePath)) - using (var writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) - { - reader.Stream.Seek(0, SeekOrigin.Begin); - - var buffer = new byte[4 * 1024]; - var total = 0; - var read = 0; - do - { - read = Math.Min(buffer.Length, (int)reader.EngineSize - total); - - read = reader.Stream.Read(buffer, 0, read); - writer.Write(buffer, 0, read); - - total += read; - } while (total < reader.EngineSize && 0 < read); - - if (total != reader.EngineSize) - { - throw new InvalidOperationException("Failed to copy engine out of bundle."); - } - - // TODO: update writer with detached container signatures. - } - - Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile)); - - FileSystem.MoveFile(tempFile, this.OutputFile); - - return true; - } - } -} diff --git a/src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs b/src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs deleted file mode 100644 index 1bafa46e..00000000 --- a/src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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.Burn.Interfaces -{ - using System.Diagnostics; - using WixToolset.Data.Symbols; - - /// - /// Service for harvesting payload information. - /// - public interface IPayloadHarvester - { - /// - /// Uses to: - /// update from file contents, - /// update from file size, and - /// update , , and from . - /// - /// The symbol to update. - /// Whether the symbol had a source file specified. - bool HarvestStandardInformation(WixBundlePayloadSymbol payload); - } -} diff --git a/src/WixToolset.Core.Burn/RowIndexedList.cs b/src/WixToolset.Core.Burn/RowIndexedList.cs deleted file mode 100644 index fd762a24..00000000 --- a/src/WixToolset.Core.Burn/RowIndexedList.cs +++ /dev/null @@ -1,299 +0,0 @@ -// 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.Burn -{ - using System; - using System.Collections.Generic; - using WixToolset.Data.WindowsInstaller; - - /// - /// A list of rows indexed by their primary key. Unlike a RowDictionary - /// this indexed list will track rows in their added order and will allow rows with - /// duplicate keys to be added to the list, although only the first row will be indexed. - /// - internal sealed class RowIndexedList : IList where T : Row - { - private readonly Dictionary index; - private readonly List rows; - private readonly List duplicates; - - /// - /// Creates an empty . - /// - public RowIndexedList() - { - this.index = new Dictionary(StringComparer.InvariantCulture); - this.rows = new List(); - this.duplicates = new List(); - } - - /// - /// Creates and populates a with the rows from the given enumerator. - /// - /// Rows to index. - public RowIndexedList(IEnumerable rows) - : this() - { - foreach (var row in rows) - { - this.Add(row); - } - } - - /// - /// Creates and populates a with the rows from the given . - /// - /// The table to index. - /// - /// Rows added to the index are not automatically added to the given . - /// - public RowIndexedList(Table table) - : this() - { - if (null != table) - { - foreach (T row in table.Rows) - { - this.Add(row); - } - } - } - - /// - /// Gets the duplicates in the list. - /// - public IEnumerable Duplicates { get { return this.duplicates; } } - - /// - /// Gets the row by integer key. - /// - /// Integer key to look up. - /// Row or null if key is not found. - public T Get(int key) - { - return this.Get(key.ToString()); - } - - /// - /// Gets the row by string key. - /// - /// String key to look up. - /// Row or null if key is not found. - public T Get(string key) - { - return this.TryGet(key, out var result) ? result : null; - } - - /// - /// Gets the row by string key if it exists. - /// - /// Key of row to get. - /// Row found. - /// True if key was found otherwise false. - public bool TryGet(string key, out T row) - { - return this.index.TryGetValue(key, out row); - } - - /// - /// Tries to add a row as long as it would not create a duplicate. - /// - /// Row to add. - /// True if the row as added otherwise false. - public bool TryAdd(T row) - { - try - { - this.index.Add(row.GetKey(), row); - } - catch (ArgumentException) // if the key already exists, bail. - { - return false; - } - - this.rows.Add(row); - return true; - } - - /// - /// Adds a row to the list. If a row with the same key is already index, the row is - /// is not in the index but will still be part of the list and added to the duplicates - /// list. - /// - /// - public void Add(T row) - { - this.rows.Add(row); - try - { - this.index.Add(row.GetKey(), row); - } - catch (ArgumentException) // if the key already exists, we have a duplicate. - { - this.duplicates.Add(row); - } - } - - /// - /// Gets the index of a row. - /// - /// Iterates through the list of rows to find the index of a particular row. - /// Index of row or -1 if not found. - public int IndexOf(T row) - { - return this.rows.IndexOf(row); - } - - /// - /// Inserts a row at a particular index of the list. - /// - /// Index to insert the row after. - /// Row to insert. - public void Insert(int index, T row) - { - this.rows.Insert(index, row); - try - { - this.index.Add(row.GetKey(), row); - } - catch (ArgumentException) // if the key already exists, we have a duplicate. - { - this.duplicates.Add(row); - } - } - - /// - /// Removes a row from a particular index. - /// - /// Index to remove the row at. - public void RemoveAt(int index) - { - var row = this.rows[index]; - - this.rows.RemoveAt(index); - - if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row) - { - this.index.Remove(row.GetKey()); - } - else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe). - { - this.duplicates.Remove(row); - } - } - - /// - /// Gets or sets a row at the specified index. - /// - /// Index to get the row. - /// Row at specified index. - public T this[int index] - { - get - { - return this.rows[index]; - } - set - { - this.rows[index] = value; - try - { - this.index.Add(value.GetKey(), value); - } - catch (ArgumentException) // if the key already exists, we have a duplicate. - { - this.duplicates.Add(value); - } - } - } - - /// - /// Empties the list and it's index. - /// - public void Clear() - { - this.index.Clear(); - this.rows.Clear(); - this.duplicates.Clear(); - } - - /// - /// Searches the list for a row without using the index. - /// - /// Row to look for in the list. - /// True if the row is in the list, otherwise false. - public bool Contains(T row) - { - return this.rows.Contains(row); - } - - /// - /// Copies the rows of the list to an array. - /// - /// Array to copy the list into. - /// Index to start copying at. - public void CopyTo(T[] array, int arrayIndex) - { - this.rows.CopyTo(array, arrayIndex); - } - - /// - /// Number of rows in the list. - /// - public int Count - { - get { return this.rows.Count; } - } - - /// - /// Indicates whether the list is read-only. Always false. - /// - public bool IsReadOnly - { - get { return false; } - } - - /// - /// Removes a row from the list. Indexed rows will be removed but the colleciton will NOT - /// promote duplicates to the index automatically. The duplicate would also need to be removed - /// and re-added to be indexed. - /// - /// - /// - public bool Remove(T row) - { - var removed = this.rows.Remove(row); - if (removed) - { - if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row) - { - this.index.Remove(row.GetKey()); - } - else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe). - { - this.duplicates.Remove(row); - } - } - - return removed; - } - - /// - /// Gets an enumerator over the whole list. - /// - /// List enumerator. - public IEnumerator GetEnumerator() - { - return this.rows.GetEnumerator(); - } - - /// - /// Gets an untyped enumerator over the whole list. - /// - /// Untyped list enumerator. - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return this.rows.GetEnumerator(); - } - } -} diff --git a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj deleted file mode 100644 index f2da8a50..00000000 --- a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - netstandard2.0 - $(TargetFrameworks);net461;net472 - Core Burn - WiX Toolset Core Burn - embedded - true - true - - - - - <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs b/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs deleted file mode 100644 index 58076d5e..00000000 --- a/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -// 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.Burn -{ - using System; - using System.Collections.Generic; - using WixToolset.Core.Burn.ExtensibilityServices; - using WixToolset.Core.Burn.Interfaces; - using WixToolset.Extensibility.Services; - - /// - /// Extensions methods for adding Burn services. - /// - public static class WixToolsetCoreServiceProviderExtensions - { - /// - /// Adds Burn Services. - /// - /// - /// - public static IWixToolsetCoreServiceProvider AddBundleBackend(this IWixToolsetCoreServiceProvider coreProvider) - { - AddServices(coreProvider); - - var extensionManager = coreProvider.GetService(); - extensionManager.Add(typeof(BurnExtensionFactory).Assembly); - - return coreProvider; - } - - private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) - { - // Singletons. - coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new BurnBackendHelper(provider))); - coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new PayloadHarvester())); - coreProvider.AddService((provider, singletons) => AddSingleton(singletons, provider.GetService())); - } - - private static T AddSingleton(Dictionary singletons, T service) where T : class - { - singletons.Add(typeof(T), service); - return service; - } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/CachedExtension.cs b/src/WixToolset.Core.ExtensionCache/CachedExtension.cs deleted file mode 100644 index 5567541c..00000000 --- a/src/WixToolset.Core.ExtensionCache/CachedExtension.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.ExtensionCache -{ - internal class CachedExtension - { - public CachedExtension(string id, string version, bool damaged) - { - this.Id = id; - this.Version = version; - this.Damaged = damaged; - } - - public string Id { get; } - - public string Version { get; } - - public bool Damaged { get; } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs b/src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs deleted file mode 100644 index 256eeb0b..00000000 --- a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs +++ /dev/null @@ -1,248 +0,0 @@ -// 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.ExtensionCache -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using NuGet.Common; - using NuGet.Configuration; - using NuGet.Credentials; - using NuGet.Packaging; - using NuGet.Protocol; - using NuGet.Protocol.Core.Types; - using NuGet.Versioning; - - /// - /// Extension cache manager. - /// - internal class ExtensionCacheManager - { - public string CacheFolder(bool global) => global ? this.GlobalCacheFolder() : this.LocalCacheFolder(); - - public string LocalCacheFolder() => Path.Combine(Environment.CurrentDirectory, ".wix", "extensions"); - - public string GlobalCacheFolder() - { - var baseFolder = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - return Path.Combine(baseFolder, ".wix", "extensions"); - } - - public async Task AddAsync(bool global, string extension, CancellationToken cancellationToken) - { - if (String.IsNullOrEmpty(extension)) - { - throw new ArgumentNullException(nameof(extension)); - } - - (var extensionId, var extensionVersion) = ParseExtensionReference(extension); - - var result = await this.DownloadAndExtractAsync(global, extensionId, extensionVersion, cancellationToken); - - return result; - } - - public Task RemoveAsync(bool global, string extension, CancellationToken cancellationToken) - { - if (String.IsNullOrEmpty(extension)) - { - throw new ArgumentNullException(nameof(extension)); - } - - (var extensionId, var extensionVersion) = ParseExtensionReference(extension); - - var cacheFolder = this.CacheFolder(global); - - cacheFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); - - if (Directory.Exists(cacheFolder)) - { - cancellationToken.ThrowIfCancellationRequested(); - - Directory.Delete(cacheFolder, true); - return Task.FromResult(true); - } - - return Task.FromResult(false); - } - - public Task> ListAsync(bool global, string extension, CancellationToken cancellationToken) - { - var found = new List(); - - (var extensionId, var extensionVersion) = ParseExtensionReference(extension); - - var cacheFolder = this.CacheFolder(global); - - var searchFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); - - if (!Directory.Exists(searchFolder)) - { - } - else if (!String.IsNullOrEmpty(extensionVersion)) // looking for an explicit version of an extension. - { - var present = ExtensionFileExists(cacheFolder, extensionId, extensionVersion); - found.Add(new CachedExtension(extensionId, extensionVersion, !present)); - } - else // looking for all versions of an extension or all versions of all extensions. - { - IEnumerable foundExtensionIds; - - if (String.IsNullOrEmpty(extensionId)) - { - // Looking for all versions of all extensions. - foundExtensionIds = Directory.GetDirectories(cacheFolder).Select(folder => Path.GetFileName(folder)).ToList(); - } - else - { - // Looking for all versions of a single extension. - var extensionFolder = Path.Combine(cacheFolder, extensionId); - foundExtensionIds = Directory.Exists(extensionFolder) ? new[] { extensionId } : Array.Empty(); - } - - foreach (var foundExtensionId in foundExtensionIds) - { - var extensionFolder = Path.Combine(cacheFolder, foundExtensionId); - - foreach (var folder in Directory.GetDirectories(extensionFolder)) - { - cancellationToken.ThrowIfCancellationRequested(); - - var foundExtensionVersion = Path.GetFileName(folder); - - if (!NuGetVersion.TryParse(foundExtensionVersion, out _)) - { - continue; - } - - var present = ExtensionFileExists(cacheFolder, foundExtensionId, foundExtensionVersion); - found.Add(new CachedExtension(foundExtensionId, foundExtensionVersion, !present)); - } - } - } - - return Task.FromResult((IEnumerable)found); - } - - private async Task DownloadAndExtractAsync(bool global, string id, string version, CancellationToken cancellationToken) - { - var logger = NullLogger.Instance; - - DefaultCredentialServiceUtility.SetupDefaultCredentialService(logger, nonInteractive: false); - - var settings = Settings.LoadDefaultSettings(root: Environment.CurrentDirectory); - var sources = PackageSourceProvider.LoadPackageSources(settings).Where(s => s.IsEnabled); - - using (var cache = new SourceCacheContext()) - { - PackageSource versionSource = null; - - var nugetVersion = String.IsNullOrEmpty(version) ? null : new NuGetVersion(version); - - if (nugetVersion is null) - { - foreach (var source in sources) - { - var repository = Repository.Factory.GetCoreV3(source.Source); - var resource = await repository.GetResourceAsync(); - - var availableVersions = await resource.GetAllVersionsAsync(id, cache, logger, cancellationToken); - foreach (var availableVersion in availableVersions) - { - if (nugetVersion is null || nugetVersion < availableVersion) - { - nugetVersion = availableVersion; - versionSource = source; - } - } - } - - if (nugetVersion is null) - { - return false; - } - } - - var searchSources = versionSource is null ? sources : new[] { versionSource }; - - var extensionFolder = Path.Combine(this.CacheFolder(global), id, nugetVersion.ToString()); - - foreach (var source in searchSources) - { - var repository = Repository.Factory.GetCoreV3(source.Source); - var resource = await repository.GetResourceAsync(); - - using (var stream = new MemoryStream()) - { - var downloaded = await resource.CopyNupkgToStreamAsync(id, nugetVersion, stream, cache, logger, cancellationToken); - - if (downloaded) - { - stream.Position = 0; - - using (var archive = new PackageArchiveReader(stream)) - { - var files = PackagingConstants.Folders.Known.SelectMany(folder => archive.GetFiles(folder)).Distinct(StringComparer.OrdinalIgnoreCase); - await archive.CopyFilesAsync(extensionFolder, files, this.ExtractProgress, logger, cancellationToken); - } - - return true; - } - } - } - } - - return false; - } - - private string ExtractProgress(string sourceFile, string targetPath, Stream fileStream) => fileStream.CopyToFile(targetPath); - - private static (string extensionId, string extensionVersion) ParseExtensionReference(string extensionReference) - { - var extensionId = extensionReference ?? String.Empty; - var extensionVersion = String.Empty; - - var index = extensionId.LastIndexOf('/'); - if (index > 0) - { - extensionVersion = extensionReference.Substring(index + 1); - extensionId = extensionReference.Substring(0, index); - - if (!NuGetVersion.TryParse(extensionVersion, out _)) - { - throw new ArgumentException($"Invalid extension version in {extensionReference}"); - } - - if (String.IsNullOrEmpty(extensionId)) - { - throw new ArgumentException($"Invalid extension id in {extensionReference}"); - } - } - - return (extensionId, extensionVersion); - } - - private static bool ExtensionFileExists(string baseFolder, string extensionId, string extensionVersion) - { - var toolsFolder = Path.Combine(baseFolder, extensionId, extensionVersion, "tools"); - if (!Directory.Exists(toolsFolder)) - { - return false; - } - - var extensionAssembly = Path.Combine(toolsFolder, extensionId + ".dll"); - - var present = File.Exists(extensionAssembly); - if (!present) - { - extensionAssembly = Path.Combine(toolsFolder, extensionId + ".exe"); - present = File.Exists(extensionAssembly); - } - - return present; - } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs b/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs deleted file mode 100644 index 94ee4f22..00000000 --- a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs +++ /dev/null @@ -1,181 +0,0 @@ -// 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.ExtensionCache -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Extension cache manager command. - /// - internal class ExtensionCacheManagerCommand : ICommandLineCommand - { - private enum CacheSubcommand - { - Add, - Remove, - List - } - - public ExtensionCacheManagerCommand(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - this.ExtensionReferences = new List(); - } - - private IMessaging Messaging { get; } - - public bool ShowLogo { get; private set; } - - public bool StopParsing { get; private set; } - - private bool ShowHelp { get; set; } - - private bool Global { get; set; } - - private CacheSubcommand? Subcommand { get; set; } - - private List ExtensionReferences { get; } - - public async Task ExecuteAsync(CancellationToken cancellationToken) - { - if (this.ShowHelp || !this.Subcommand.HasValue) - { - DisplayHelp(); - return 1; - } - - var success = false; - var cacheManager = new ExtensionCacheManager(); - - switch (this.Subcommand) - { - case CacheSubcommand.Add: - success = await this.AddExtensions(cacheManager, cancellationToken); - break; - - case CacheSubcommand.Remove: - success = await this.RemoveExtensions(cacheManager, cancellationToken); - break; - - case CacheSubcommand.List: - success = await this.ListExtensions(cacheManager, cancellationToken); - break; - } - - return success ? 0 : 2; - } - - public bool TryParseArgument(ICommandLineParser parser, string argument) - { - if (!parser.IsSwitch(argument)) - { - if (!this.Subcommand.HasValue) - { - if (!Enum.TryParse(argument, true, out CacheSubcommand subcommand)) - { - return false; - } - - this.Subcommand = subcommand; - } - else - { - this.ExtensionReferences.Add(argument); - } - - return true; - } - - var parameter = argument.Substring(1); - switch (parameter.ToLowerInvariant()) - { - case "?": - case "h": - case "-help": - this.ShowHelp = true; - this.ShowLogo = true; - this.StopParsing = true; - return true; - - case "nologo": - case "-nologo": - this.ShowLogo = false; - return true; - - case "g": - case "-global": - this.Global = true; - return true; - } - - return false; - } - - private async Task AddExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) - { - var success = false; - - foreach (var extensionRef in this.ExtensionReferences) - { - var added = await cacheManager.AddAsync(this.Global, extensionRef, cancellationToken); - success |= added; - } - - return success; - } - - private async Task RemoveExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) - { - var success = false; - - foreach (var extensionRef in this.ExtensionReferences) - { - var removed = await cacheManager.RemoveAsync(this.Global, extensionRef, cancellationToken); - success |= removed; - } - - return success; - } - - private async Task ListExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) - { - var found = false; - var extensionRef = this.ExtensionReferences.FirstOrDefault(); - - var extensions = await cacheManager.ListAsync(this.Global, extensionRef, cancellationToken); - - foreach (var extension in extensions) - { - this.Messaging.Write($"{extension.Id} {extension.Version}{(extension.Damaged ? " (damaged)" : String.Empty)}"); - found = true; - } - - return found; - } - - private static void DisplayHelp() - { - Console.WriteLine(); - Console.WriteLine("Usage: wix extension add|remove|list [extensionRef]"); - Console.WriteLine(); - Console.WriteLine("Options:"); - Console.WriteLine(" -h|--help Show command line help."); - Console.WriteLine(" -g|--global Add/remove the extension for the current user."); - Console.WriteLine(" --nologo Suppress displaying the logo information."); - Console.WriteLine(); - Console.WriteLine("Commands:"); - Console.WriteLine(); - Console.WriteLine(" add Add extension to the cache."); - Console.WriteLine(" list List extensions in the cache."); - Console.WriteLine(" remove Remove extension from the cache."); - Console.WriteLine(); - Console.WriteLine(" extensionRef format: extensionId/version (the version is optional)"); - } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs b/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs deleted file mode 100644 index 2a603adf..00000000 --- a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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.ExtensionCache -{ - using System; - using System.Collections.Generic; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Parses the "extension" command-line command. See ExtensionCacheManagerCommand - /// for the bulk of the command-line processing. - /// - internal class ExtensionCacheManagerExtensionCommandLine : BaseExtensionCommandLine - { - public ExtensionCacheManagerExtensionCommandLine(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - private IServiceProvider ServiceProvider { get; } - - public override IReadOnlyCollection CommandLineSwitches => new ExtensionCommandLineSwitch[] - { - new ExtensionCommandLineSwitch { Switch = "extension", Description = "Manage extension cache." }, - }; - - public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) - { - command = null; - - if ("extension".Equals(argument, StringComparison.OrdinalIgnoreCase)) - { - command = new ExtensionCacheManagerCommand(this.ServiceProvider); - } - - return command != null; - } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs b/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs deleted file mode 100644 index c38e5c70..00000000 --- a/src/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.ExtensionCache -{ - using System; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class ExtensionCacheManagerExtensionFactory : IExtensionFactory - { - public ExtensionCacheManagerExtensionFactory(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - private IServiceProvider ServiceProvider { get; } - - public bool TryCreateExtension(Type extensionType, out object extension) - { - extension = null; - - if (extensionType == typeof(IExtensionCommandLine)) - { - extension = new ExtensionCacheManagerExtensionCommandLine(this.ServiceProvider); - } - - return extension != null; - } - } -} diff --git a/src/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj b/src/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj deleted file mode 100644 index 1383305c..00000000 --- a/src/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - netstandard2.0 - $(TargetFrameworks);net461;net472 - Extension Cache - WiX Toolset Extension Cache - embedded - true - true - - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs b/src/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs deleted file mode 100644 index 424fc469..00000000 --- a/src/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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.ExtensionCache -{ - using System; - using System.Collections.Generic; - using WixToolset.Extensibility.Services; - - /// - /// Extensions methods for adding ExtensionCache services. - /// - public static class WixToolsetCoreServiceProviderExtensions - { - /// - /// Adds ExtensionCache services. - /// - /// - /// - public static IWixToolsetCoreServiceProvider AddExtensionCacheManager(this IWixToolsetCoreServiceProvider coreProvider) - { - var extensionManager = coreProvider.GetService(); - extensionManager.Add(typeof(ExtensionCacheManagerExtensionFactory).Assembly); - - coreProvider.AddService(CreateExtensionCacheManager); - return coreProvider; - } - - private static ExtensionCacheManager CreateExtensionCacheManager(IWixToolsetCoreServiceProvider coreProvider, Dictionary singletons) - { - var extensionCacheManager = new ExtensionCacheManager(); - singletons.Add(typeof(ExtensionCacheManager), extensionCacheManager); - - return extensionCacheManager; - } - } -} diff --git a/src/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/WixToolset.Core.TestPackage/BundleExtractor.cs deleted file mode 100644 index 8c9f31e6..00000000 --- a/src/WixToolset.Core.TestPackage/BundleExtractor.cs +++ /dev/null @@ -1,139 +0,0 @@ -// 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.TestPackage -{ - using System.IO; - using System.Xml; - using WixToolset.Core.Burn.Bundles; - using WixToolset.Extensibility.Services; - - /// - /// Class to extract bundle contents for testing. - /// - public class BundleExtractor - { - /// - /// Extracts the BA container. - /// - /// - /// Path to the bundle. - /// Path to extract to. - /// Temp path for extraction. - /// - public static ExtractBAContainerResult ExtractBAContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) - { - var result = new ExtractBAContainerResult(); - Directory.CreateDirectory(tempFolderPath); - using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) - { - result.Success = burnReader.ExtractUXContainer(destinationFolderPath, tempFolderPath); - } - - if (result.Success) - { - result.ManifestDocument = LoadBurnManifest(destinationFolderPath); - result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); - - result.BADataDocument = LoadBAData(destinationFolderPath); - result.BADataNamespaceManager = GetBADataNamespaceManager(result.BADataDocument, "ba"); - - result.BundleExtensionDataDocument = LoadBundleExtensionData(destinationFolderPath); - result.BundleExtensionDataNamespaceManager = GetBundleExtensionDataNamespaceManager(result.BundleExtensionDataDocument, "be"); - } - - return result; - } - - /// - /// Extracts the attached container. - /// - /// - /// Path to the bundle. - /// Path to extract to. - /// Temp path for extraction. - /// True if there was an attached container. - public static bool ExtractAttachedContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) - { - Directory.CreateDirectory(tempFolderPath); - using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) - { - return burnReader.ExtractAttachedContainer(destinationFolderPath, tempFolderPath); - } - } - - /// - /// Gets an for BootstrapperApplicationData.xml with the given prefix assigned to the root namespace. - /// - /// - /// - /// - public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix) - { - var namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace); - return namespaceManager; - } - - /// - /// Gets an for BundleExtensionData.xml with the given prefix assigned to the root namespace. - /// - /// - /// - /// - public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix) - { - var namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace); - return namespaceManager; - } - - /// - /// Gets an for the Burn manifest.xml with the given prefix assigned to the root namespace. - /// - /// - /// - /// - public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) - { - var namespaceManager = new XmlNamespaceManager(document.NameTable); - namespaceManager.AddNamespace(prefix, BurnCommon.BurnNamespace); - return namespaceManager; - } - - /// - /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. - /// - /// - /// - public static XmlDocument LoadBAData(string baFolderPath) - { - var document = new XmlDocument(); - document.Load(Path.Combine(baFolderPath, BurnCommon.BADataFileName)); - return document; - } - - /// - /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. - /// - /// - /// - public static XmlDocument LoadBundleExtensionData(string baFolderPath) - { - var document = new XmlDocument(); - document.Load(Path.Combine(baFolderPath, BurnCommon.BundleExtensionDataFileName)); - return document; - } - - /// - /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. - /// - /// - /// - public static XmlDocument LoadBurnManifest(string baFolderPath) - { - var document = new XmlDocument(); - document.Load(Path.Combine(baFolderPath, "manifest.xml")); - return document; - } - } -} diff --git a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs deleted file mode 100644 index 277861ff..00000000 --- a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs +++ /dev/null @@ -1,116 +0,0 @@ -// 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.TestPackage -{ - using System.IO; - using System.Xml; - using Xunit; - - /// - /// The result of extracting the BA container. - /// - public class ExtractBAContainerResult - { - /// - /// for BundleExtensionData.xml. - /// - public XmlDocument BundleExtensionDataDocument { get; set; } - - /// - /// for BundleExtensionData.xml. - /// - public XmlNamespaceManager BundleExtensionDataNamespaceManager { get; set; } - - /// - /// for BootstrapperApplicationData.xml. - /// - public XmlDocument BADataDocument { get; set; } - - /// - /// for BootstrapperApplicationData.xml. - /// - public XmlNamespaceManager BADataNamespaceManager { get; set; } - - /// - /// for the Burn manifest.xml. - /// - public XmlDocument ManifestDocument { get; set; } - - /// - /// for the Burn manifest.xml. - /// - public XmlNamespaceManager ManifestNamespaceManager { get; set; } - - /// - /// Whether extraction succeeded. - /// - public bool Success { get; set; } - - /// - /// - /// - /// - public ExtractBAContainerResult AssertSuccess() - { - Assert.True(this.Success); - return this; - } - - /// - /// Returns the relative path of the BA entry point dll in the given folder. - /// - /// - /// - public string GetBAFilePath(string extractedBAContainerFolderPath) - { - var uxPayloads = this.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload"); - var baPayload = uxPayloads[0]; - var relativeBAPath = baPayload.Attributes["FilePath"].Value; - return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); - } - - /// - /// Returns the relative path of the BundleExtension entry point dll in the given folder. - /// - /// - /// - /// - public string GetBundleExtensionFilePath(string extractedBAContainerFolderPath, string extensionId) - { - var uxPayloads = this.SelectManifestNodes($"/burn:BurnManifest/burn:UX/burn:Payload[@Id='{extensionId}']"); - var bextPayload = uxPayloads[0]; - var relativeBextPath = bextPayload.Attributes["FilePath"].Value; - return Path.Combine(extractedBAContainerFolderPath, relativeBextPath); - } - - /// - /// - /// - /// elements must have the 'ba' prefix - /// - public XmlNodeList SelectBADataNodes(string xpath) - { - return this.BADataDocument.SelectNodes(xpath, this.BADataNamespaceManager); - } - - /// - /// - /// - /// elements must have the 'be' prefix - /// - public XmlNodeList SelectBundleExtensionDataNodes(string xpath) - { - return this.BundleExtensionDataDocument.SelectNodes(xpath, this.BundleExtensionDataNamespaceManager); - } - - /// - /// - /// - /// elements must have the 'burn' prefix - /// - public XmlNodeList SelectManifestNodes(string xpath) - { - return this.ManifestDocument.SelectNodes(xpath, this.ManifestNamespaceManager); - } - } -} diff --git a/src/WixToolset.Core.TestPackage/TestMessageListener.cs b/src/WixToolset.Core.TestPackage/TestMessageListener.cs deleted file mode 100644 index 7040fe82..00000000 --- a/src/WixToolset.Core.TestPackage/TestMessageListener.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; -using WixToolset.Data; -using WixToolset.Extensibility; -using WixToolset.Extensibility.Services; - -namespace WixToolset.Core.TestPackage -{ - /// - /// An that simply stores all the messages. - /// - public sealed class TestMessageListener : IMessageListener - { - /// - /// All messages that have been received. - /// - public List Messages { get; } = new List(); - - /// - /// - /// - public string ShortAppName => "TEST"; - - /// - /// - /// - public string LongAppName => "Test"; - - /// - /// Stores the message in . - /// - /// - public void Write(Message message) - { - this.Messages.Add(message); - } - - /// - /// Stores the message in . - /// - /// - public void Write(string message) - { - this.Messages.Add(new Message(null, MessageLevel.Information, 0, message)); - } - - /// - /// Always returns defaultMessageLevel. - /// - /// - /// - /// - /// - public MessageLevel CalculateMessageLevel(IMessaging messaging, Message message, MessageLevel defaultMessageLevel) => defaultMessageLevel; - } -} diff --git a/src/WixToolset.Core.TestPackage/WixRunner.cs b/src/WixToolset.Core.TestPackage/WixRunner.cs deleted file mode 100644 index ed7c49b8..00000000 --- a/src/WixToolset.Core.TestPackage/WixRunner.cs +++ /dev/null @@ -1,88 +0,0 @@ -// 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.TestPackage -{ - using System; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using WixToolset.Core.Burn; - using WixToolset.Core.WindowsInstaller; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - /// - /// Utility class to emulate wix.exe with standard backends. - /// - public static class WixRunner - { - /// - /// Emulates calling wix.exe with standard backends. - /// - /// - /// - /// - /// - public static int Execute(string[] args, out List messages, bool warningsAsErrors = true) - { - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var task = Execute(args, serviceProvider, out messages, warningsAsErrors: warningsAsErrors); - return task.Result; - } - - /// - /// Emulates calling wix.exe with standard backends. - /// This overload always treats warnings as errors. - /// - /// - /// - public static WixRunnerResult Execute(params string[] args) - { - return Execute(true, args); - } - - /// - /// Emulates calling wix.exe with standard backends. - /// - /// - /// - /// - public static WixRunnerResult Execute(bool warningsAsErrors, params string[] args) - { - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var exitCode = Execute(args, serviceProvider, out var messages, warningsAsErrors: warningsAsErrors); - return new WixRunnerResult { ExitCode = exitCode.Result, Messages = messages.ToArray() }; - } - - /// - /// Emulates calling wix.exe with standard backends. - /// - /// - /// - /// - /// - /// - public static Task Execute(string[] args, IWixToolsetCoreServiceProvider coreProvider, out List messages, bool warningsAsErrors = true) - { - coreProvider.AddWindowsInstallerBackend() - .AddBundleBackend(); - - var listener = new TestMessageListener(); - - messages = listener.Messages; - - var messaging = coreProvider.GetService(); - messaging.SetListener(listener); - - var arguments = new List(args); - if (warningsAsErrors) - { - arguments.Add("-wx"); - } - - var commandLine = coreProvider.GetService(); - var command = commandLine.CreateCommand(arguments.ToArray()); - return command?.ExecuteAsync(CancellationToken.None) ?? Task.FromResult(1); - } - } -} diff --git a/src/WixToolset.Core.TestPackage/WixRunnerResult.cs b/src/WixToolset.Core.TestPackage/WixRunnerResult.cs deleted file mode 100644 index 6a3d714c..00000000 --- a/src/WixToolset.Core.TestPackage/WixRunnerResult.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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.TestPackage -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using Xunit; - - /// - /// The result of an Execute method of . - /// - public class WixRunnerResult - { - /// - /// ExitCode for the operation. - /// - public int ExitCode { get; set; } - - /// - /// Messages from the operation. - /// - public Message[] Messages { get; set; } - - /// - /// - /// - /// - public WixRunnerResult AssertSuccess() - { - AssertSuccess(this.ExitCode, this.Messages); - return this; - } - - /// - /// - /// - /// - /// - public static void AssertSuccess(int exitCode, IEnumerable messages) - { - Assert.True(0 == exitCode, $"\r\n\r\nWixRunner failed with exit code: {exitCode}\r\n Output: {String.Join("\r\n ", FormatMessages(messages))}\r\n"); - } - - private static IEnumerable FormatMessages(IEnumerable messages) - { - foreach (var message in messages) - { - var filename = message.SourceLineNumbers?.FileName ?? "TEST"; - var line = message.SourceLineNumbers?.LineNumber ?? -1; - var type = message.Level.ToString().ToLowerInvariant(); - - if (line > 0) - { - filename = String.Concat(filename, "(", line, ")"); - } - - yield return String.Format("{0} : {1} {2}{3:0000}: {4}", filename, type, "TEST", message.Id, message.ToString()); - } - } - } -} diff --git a/src/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj b/src/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj deleted file mode 100644 index b64b4075..00000000 --- a/src/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - netstandard2.0 - $(TargetFrameworks);net461;net472 - Internal WiX Toolset Test Package - embedded - true - true - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs b/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs deleted file mode 100644 index f4966f74..00000000 --- a/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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.TestPackage -{ - using System.Collections.Generic; - using System.IO; - using System.Text.RegularExpressions; - using System.Xml; - - /// - /// Utility class to help compare XML in tests using string comparisons by using single quotes and stripping all namespaces. - /// - public static class XmlNodeExtensions - { - /// - /// Returns the node's outer XML using single quotes and stripping all namespaces. - /// - /// - /// Attributes for which the value should be set to '*'. - /// - public static string GetTestXml(this XmlNode node, Dictionary> ignoredAttributesByElementName = null) - { - return node.OuterXml.GetTestXml(ignoredAttributesByElementName); - } - - /// - /// Returns the XML using single quotes and stripping all namespaces. - /// - /// - /// Attributes for which the value should be set to '*'. - /// - public static string GetTestXml(this string xml, Dictionary> ignoredAttributesByElementName = null) - { - string formattedXml; - using (var sw = new StringWriter()) - using (var writer = new TestXmlWriter(sw)) - { - var doc = new XmlDocument(); - doc.LoadXml(xml); - - if (ignoredAttributesByElementName != null) - { - HandleIgnoredAttributes(doc, ignoredAttributesByElementName); - } - - doc.Save(writer); - formattedXml = sw.ToString(); - } - - return Regex.Replace(formattedXml, " xmlns(:[^=]+)?='[^']*'", ""); - } - - private static void HandleIgnoredAttributes(XmlNode node, Dictionary> ignoredAttributesByElementName) - { - if (node.Attributes != null && ignoredAttributesByElementName.TryGetValue(node.LocalName, out var ignoredAttributes)) - { - foreach (var ignoredAttribute in ignoredAttributes) - { - var attribute = node.Attributes[ignoredAttribute]; - if (attribute != null) - { - attribute.Value = "*"; - } - } - } - - if (node.ChildNodes != null) - { - foreach (XmlNode childNode in node.ChildNodes) - { - HandleIgnoredAttributes(childNode, ignoredAttributesByElementName); - } - } - } - - private class TestXmlWriter : XmlTextWriter - { - public TestXmlWriter(TextWriter w) - : base(w) - { - this.QuoteChar = '\''; - } - - public override void WriteStartDocument() - { - //OmitXmlDeclaration - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs deleted file mode 100644 index cbba6030..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs +++ /dev/null @@ -1,52 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - - /// - /// Add back possibly suppressed sequence tables since all sequence tables must be present - /// for the merge process to work. We'll drop the suppressed sequence tables again as - /// necessary. - /// - internal class AddBackSuppressedSequenceTablesCommand - { - public AddBackSuppressedSequenceTablesCommand(WindowsInstallerData output, TableDefinitionCollection tableDefinitions) - { - this.Output = output; - this.TableDefinitions = tableDefinitions; - } - - private WindowsInstallerData Output { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - public IEnumerable SuppressedTableNames { get; private set; } - - public IEnumerable Execute() - { - var suppressedTableNames = new HashSet(); - - foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) - { - var sequenceTableName = sequence.WindowsInstallerTableName(); - var sequenceTable = this.Output.Tables[sequenceTableName]; - - if (null == sequenceTable) - { - sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); - } - - if (0 == sequenceTable.Rows.Count) - { - suppressedTableNames.Add(sequenceTableName); - } - } - - return this.SuppressedTableNames = suppressedTableNames; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs deleted file mode 100644 index c4fddb3e..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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.Bind -{ - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - /// - /// Add CreateFolder symbols, if not already present, for null-keypath components. - /// - internal class AddCreateFoldersCommand - { - internal AddCreateFoldersCommand(IntermediateSection section) - { - this.Section = section; - } - - private IntermediateSection Section { get; } - - public void Execute() - { - var createFolderSymbolsByComponentRef = new HashSet(this.Section.Symbols.OfType().Select(t => t.ComponentRef)); - foreach (var componentSymbol in this.Section.Symbols.OfType().Where(t => t.KeyPathType == ComponentKeyPathType.Directory).ToList()) - { - if (!createFolderSymbolsByComponentRef.Contains(componentSymbol.Id.Id)) - { - this.Section.AddSymbol(new CreateFolderSymbol(componentSymbol.SourceLineNumbers) - { - DirectoryRef = componentSymbol.DirectoryRef, - ComponentRef = componentSymbol.Id.Id, - }); - } - } - } - } -} \ No newline at end of file diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs deleted file mode 100644 index ee3bcc91..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs +++ /dev/null @@ -1,95 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - - /// - /// Add referenced standard directory symbols, if not already present. - /// - internal class AddRequiredStandardDirectories - { - internal AddRequiredStandardDirectories(IntermediateSection section, Platform platform) - { - this.Section = section; - this.Platform = platform; - } - - private IntermediateSection Section { get; } - - private Platform Platform { get; } - - public void Execute() - { - var directories = this.Section.Symbols.OfType().ToList(); - var directoryIds = new SortedSet(directories.Select(d => d.Id.Id)); - - foreach (var directory in directories) - { - var parentDirectoryId = directory.ParentDirectoryRef; - - if (String.IsNullOrEmpty(parentDirectoryId)) - { - if (directory.Id.Id != "TARGETDIR") - { - directory.ParentDirectoryRef = "TARGETDIR"; - } - } - else - { - this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, directory.SourceLineNumbers); - } - } - - if (!directoryIds.Contains("TARGETDIR") && WindowsInstallerStandard.TryGetStandardDirectory("TARGETDIR", out var targetDir)) - { - directoryIds.Add(targetDir.Id.Id); - this.Section.AddSymbol(targetDir); - } - } - - private void EnsureStandardDirectoryAdded(ISet directoryIds, string directoryId, SourceLineNumber sourceLineNumbers) - { - if (!directoryIds.Contains(directoryId) && WindowsInstallerStandard.TryGetStandardDirectory(directoryId, out var standardDirectory)) - { - var parentDirectoryId = this.GetStandardDirectoryParent(directoryId); - - var directory = new DirectorySymbol(sourceLineNumbers, standardDirectory.Id) - { - Name = standardDirectory.Name, - ParentDirectoryRef = parentDirectoryId, - }; - - directoryIds.Add(directory.Id.Id); - this.Section.AddSymbol(directory); - - if (!String.IsNullOrEmpty(parentDirectoryId)) - { - this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, sourceLineNumbers); - } - } - } - - private string GetStandardDirectoryParent(string directoryId) - { - switch (directoryId) - { - case "TARGETDIR": - return null; - - case "CommonFiles6432Folder": - case "ProgramFiles6432Folder": - case "System6432Folder": - return WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directoryId, this.Platform); - - default: - return "TARGETDIR"; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs deleted file mode 100644 index 759ba303..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs +++ /dev/null @@ -1,60 +0,0 @@ -// 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.Bind -{ - using System; - using System.Text; - - internal class AssemblyName - { - public AssemblyName(string name, string culture, string version, string fileVersion, string architecture, string publicKeyToken, string type) - { - this.Name = name; - this.Culture = culture ?? "neutral"; - this.Version = version; - this.FileVersion = fileVersion; - this.Architecture = architecture; - - this.StrongNamedSigned = !String.IsNullOrEmpty(publicKeyToken); - this.PublicKeyToken = publicKeyToken; - this.Type = type; - } - - public string Name { get; } - - public string Culture { get; } - - public string Version { get; } - - public string FileVersion { get; } - - public string Architecture { get; } - - public string PublicKeyToken { get; } - - public bool StrongNamedSigned { get; } - - public string Type { get; } - - public string GetFullName() - { - var assemblyName = new StringBuilder(); - - assemblyName.Append(this.Name); - assemblyName.Append(", Version="); - assemblyName.Append(this.Version); - assemblyName.Append(", Culture="); - assemblyName.Append(this.Culture); - assemblyName.Append(", PublicKeyToken="); - assemblyName.Append(this.PublicKeyToken ?? "null"); - - if (!String.IsNullOrEmpty(this.Architecture)) - { - assemblyName.Append(", ProcessorArchitecture="); - assemblyName.Append(this.Architecture); - } - - return assemblyName.ToString(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs deleted file mode 100644 index 2103cd32..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs +++ /dev/null @@ -1,214 +0,0 @@ -// 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.Bind -{ - using System; - using System.IO; - using System.Reflection.Metadata; - using System.Reflection.PortableExecutable; - using System.Security.Cryptography; - using System.Text; - using System.Xml; - using System.Xml.XPath; - using WixToolset.Data; - - internal static class AssemblyNameReader - { - public static AssemblyName ReadAssembly(SourceLineNumber sourceLineNumbers, string assemblyPath, string fileVersion) - { - try - { - using (var stream = File.OpenRead(assemblyPath)) - using (var peReader = new PEReader(stream)) - { - var reader = peReader.GetMetadataReader(); - var headers = peReader.PEHeaders; - - var assembly = reader.GetAssemblyDefinition(); - var attributes = assembly.GetCustomAttributes(); - - var name = ReadString(reader, assembly.Name); - var culture = ReadString(reader, assembly.Culture); - var architecture = ArchitectureFromHeaders(headers); - var version = assembly.Version.ToString(); - var publicKeyToken = ReadPublicKeyToken(reader, assembly.PublicKey); - - // There is a bug in v1 fusion that requires the assembly's "version" attribute - // to be equal to or longer than the "fileVersion" in length when its present; - // the workaround is to prepend zeroes to the last version number in the assembly - // version. - var targetNetfx1 = (headers.CorHeader.MajorRuntimeVersion == 2) && (headers.CorHeader.MinorRuntimeVersion == 0); - if (targetNetfx1 && !String.IsNullOrEmpty(fileVersion) && fileVersion.Length > version.Length) - { - var versionParts = version.Split('.'); - - if (versionParts.Length > 0) - { - var padding = new string('0', fileVersion.Length - version.Length); - - versionParts[versionParts.Length - 1] = String.Concat(padding, versionParts[versionParts.Length - 1]); - version = String.Join(".", versionParts); - } - } - - return new AssemblyName(name, culture, version, fileVersion, architecture, publicKeyToken, null); - } - } - catch (Exception e) when (e is FileNotFoundException || e is BadImageFormatException || e is InvalidOperationException) - { - throw new WixException(ErrorMessages.InvalidAssemblyFile(sourceLineNumbers, assemblyPath, $"{e.GetType().Name}: {e.Message}")); - } - } - - public static AssemblyName ReadAssemblyManifest(SourceLineNumber sourceLineNumbers, string manifestPath) - { - string win32Type = null; - string win32Name = null; - string win32Version = null; - string win32ProcessorArchitecture = null; - string win32PublicKeyToken = null; - - // Loading the dom is expensive we want more performant APIs than the DOM - // Navigator is cheaper than dom. Perhaps there is a cheaper API still. - try - { - var doc = new XPathDocument(manifestPath); - var nav = doc.CreateNavigator(); - nav.MoveToRoot(); - - // This assumes a particular schema for a win32 manifest and does not - // provide error checking if the file does not conform to schema. - // The fallback case here is that nothing is added to the MsiAssemblyName - // table for an out of tolerance Win32 manifest. Perhaps warnings needed. - if (nav.MoveToFirstChild()) - { - while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") - { - nav.MoveToNext(); - } - - if (nav.MoveToFirstChild()) - { - var hasNextSibling = true; - while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) - { - hasNextSibling = nav.MoveToNext(); - } - - if (!hasNextSibling) - { - throw new WixException(ErrorMessages.InvalidManifestContent(sourceLineNumbers, manifestPath)); - } - - if (nav.MoveToAttribute("type", String.Empty)) - { - win32Type = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("name", String.Empty)) - { - win32Name = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("version", String.Empty)) - { - win32Version = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("processorArchitecture", String.Empty)) - { - win32ProcessorArchitecture = nav.Value; - nav.MoveToParent(); - } - - if (nav.MoveToAttribute("publicKeyToken", String.Empty)) - { - win32PublicKeyToken = nav.Value; - nav.MoveToParent(); - } - } - } - } - catch (FileNotFoundException fe) - { - throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, fe.FileName, "AssemblyManifest")); - } - catch (XmlException xe) - { - throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "manifest", xe.Message)); - } - - return new AssemblyName(win32Name, null, win32Version, null, win32ProcessorArchitecture, win32PublicKeyToken, win32Type); - } - - private static string ArchitectureFromHeaders(PEHeaders headers) - { - if (headers.PEHeader.Magic == PEMagic.PE32Plus) - { - return "AMD64"; - } - else if ((headers.CorHeader.Flags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit) - { - return "x86"; - } - else if ((headers.CorHeader.Flags & CorFlags.ILOnly) == CorFlags.ILOnly) - { - return "MSIL"; - } - else - { - // We return "x86" here because that seems to best match the Fusion-based - // GetAssemblyIdentityFromFile() method of acquiring the assembly identity. - return "x86"; - } - } - - private static string ReadString(MetadataReader reader, StringHandle handle) - { - return handle.IsNil ? null : reader.GetString(handle); - } - - private static string ReadPublicKeyToken(MetadataReader reader, BlobHandle handle) - { - if (handle.IsNil) - { - return null; - } - - var bytes = reader.GetBlobBytes(handle); - if (bytes.Length == 0) - { - return null; - } - - var result = new StringBuilder(); - - // If we have the full public key, calculate the public key token from the - // last 8 bytes (in reverse order) of the public key's SHA1 hash. - if (bytes.Length > 8) - { - using (var sha1 = SHA1.Create()) - { - var hash = sha1.ComputeHash(bytes); - - for (var i = 1; i <= 8; ++i) - { - result.Append(hash[hash.Length - i].ToString("X2")); - } - } - } - else - { - foreach (var b in bytes) - { - result.Append(b.ToString("X2")); - } - } - - return result.ToString(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs deleted file mode 100644 index cfa84629..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs +++ /dev/null @@ -1,302 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. - /// - internal class AssignMediaCommand - { - private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB - - public AssignMediaCommand(IntermediateSection section, IMessaging messaging, IEnumerable fileFacades, bool compressed) - { - this.CabinetNameTemplate = "Cab{0}.cab"; - this.Section = section; - this.Messaging = messaging; - this.FileFacades = fileFacades; - this.FilesCompressed = compressed; - } - - private IntermediateSection Section { get; } - - private IMessaging Messaging { get; } - - private IEnumerable FileFacades { get; } - - private bool FilesCompressed { get; } - - private string CabinetNameTemplate { get; set; } - - /// - /// Gets cabinets with their file rows. - /// - public Dictionary> FileFacadesByCabinetMedia { get; private set; } - - /// - /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no. - /// This contains all the files when Package element is marked with compression=no - /// - public IEnumerable UncompressedFileFacades { get; private set; } - - public void Execute() - { - var mediaSymbols = this.Section.Symbols.OfType().ToList(); - var mediaTemplateSymbols = this.Section.Symbols.OfType().ToList(); - - // If both symbols are authored, it is an error. - if (mediaTemplateSymbols.Count > 0 && mediaSymbols.Count > 1) - { - throw new WixException(ErrorMessages.MediaTableCollision(null)); - } - - // If neither symbol is authored, default to a media template. - if (SectionType.Product == this.Section.Type && mediaTemplateSymbols.Count == 0 && mediaSymbols.Count == 0) - { - var mediaTemplate = new WixMediaTemplateSymbol() - { - CabinetTemplate = "cab{0}.cab", - }; - - this.Section.AddSymbol(mediaTemplate); - mediaTemplateSymbols.Add(mediaTemplate); - } - - // When building merge module, all the files go to "#MergeModule.CABinet". - if (SectionType.Module == this.Section.Type) - { - var mergeModuleMediaSymbol = this.Section.AddSymbol(new MediaSymbol - { - Cabinet = "#MergeModule.CABinet", - }); - - this.FileFacadesByCabinetMedia = new Dictionary> - { - { mergeModuleMediaSymbol, this.FileFacades } - }; - - this.UncompressedFileFacades = Array.Empty(); - } - else - { - var filesByCabinetMedia = new Dictionary>(); - var uncompressedFiles = new List(); - - if (mediaTemplateSymbols.Count > 0) - { - this.AutoAssignFiles(mediaTemplateSymbols, mediaSymbols, filesByCabinetMedia, uncompressedFiles); - } - else - { - this.ManuallyAssignFiles(mediaSymbols, filesByCabinetMedia, uncompressedFiles); - } - - this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable)kvp.Value); - - this.UncompressedFileFacades = uncompressedFiles; - } - } - - /// - /// Assign files to cabinets based on MediaTemplate authoring. - /// - private void AutoAssignFiles(List mediaTemplateTable, List mediaSymbols, Dictionary> filesByCabinetMedia, List uncompressedFiles) - { - const int MaxCabIndex = 999; - - ulong currentPreCabSize = 0; - ulong maxPreCabSizeInBytes; - var maxPreCabSizeInMB = 0; - var currentCabIndex = 0; - - MediaSymbol currentMediaRow = null; - - // Remove all previous media symbols since they will be replaced with - // media template. - foreach (var mediaSymbol in mediaSymbols) - { - this.Section.RemoveSymbol(mediaSymbol); - } - - // Auto assign files to cabinets based on maximum uncompressed media size - var mediaTemplateRow = mediaTemplateTable.Single(); - - if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) - { - this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate; - } - - var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); - - try - { - // Override authored mums value if environment variable is authored. - if (!String.IsNullOrEmpty(mumsString)) - { - maxPreCabSizeInMB = Int32.Parse(mumsString); - } - else - { - maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; - } - - maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; - } - catch (FormatException) - { - throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); - } - catch (OverflowException) - { - throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); - } - - var mediaSymbolsByDiskId = new Dictionary(); - - foreach (var facade in this.FileFacades) - { - // When building a product, if the current file is not to be compressed or if - // the package set not to be compressed, don't cab it. - if (SectionType.Product == this.Section.Type && (facade.Uncompressed || !this.FilesCompressed)) - { - uncompressedFiles.Add(facade); - continue; - } - - if (currentCabIndex == MaxCabIndex) - { - // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. - } - else - { - // Update current cab size. - currentPreCabSize += (ulong)facade.FileSize; - - // Overflow due to current file - if (currentPreCabSize > maxPreCabSizeInBytes) - { - currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex); - mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); - filesByCabinetMedia.Add(currentMediaRow, new List()); - - // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize - currentPreCabSize = (ulong)facade.FileSize; - } - else // file fits in the current cab. - { - if (currentMediaRow == null) - { - // Create new cab and MediaRow - currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex); - mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); - filesByCabinetMedia.Add(currentMediaRow, new List()); - } - } - } - - // Associate current file with current cab. - var cabinetFiles = filesByCabinetMedia[currentMediaRow]; - facade.DiskId = currentCabIndex; - cabinetFiles.Add(facade); - } - - // If there are uncompressed files and no MediaRow, create a default one. - if (uncompressedFiles.Count > 0 && mediaSymbolsByDiskId.Count == 0) - { - var defaultMediaRow = this.Section.AddSymbol(new MediaSymbol(null, new Identifier(AccessModifier.Section, 1)) - { - DiskId = 1, - }); - - mediaSymbolsByDiskId.Add(1, defaultMediaRow); - } - } - - /// - /// Assign files to cabinets based on Media authoring. - /// - private void ManuallyAssignFiles(List mediaSymbols, Dictionary> filesByCabinetMedia, List uncompressedFiles) - { - var mediaSymbolsByDiskId = new Dictionary(); - - if (mediaSymbols.Any()) - { - var cabinetMediaSymbols = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var mediaSymbol in mediaSymbols) - { - // If the Media row has a cabinet, make sure it is unique across all Media rows. - if (!String.IsNullOrEmpty(mediaSymbol.Cabinet)) - { - if (cabinetMediaSymbols.TryGetValue(mediaSymbol.Cabinet, out var existingRow)) - { - this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet)); - this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); - } - else - { - cabinetMediaSymbols.Add(mediaSymbol.Cabinet, mediaSymbol); - } - - filesByCabinetMedia.Add(mediaSymbol, new List()); - } - - mediaSymbolsByDiskId.Add(mediaSymbol.DiskId, mediaSymbol); - } - } - - foreach (var facade in this.FileFacades) - { - if (!mediaSymbolsByDiskId.TryGetValue(facade.DiskId, out var mediaSymbol)) - { - this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId)); - continue; - } - - // When building a product, if the current file is to be uncompressed or if - // the package set not to be compressed, don't cab it. - var compressed = facade.Compressed; - var uncompressed = facade.Uncompressed; - if (SectionType.Product == this.Section.Type && (uncompressed || (!compressed && !this.FilesCompressed))) - { - uncompressedFiles.Add(facade); - } - else // file is marked compressed. - { - if (filesByCabinetMedia.TryGetValue(mediaSymbol, out var cabinetFiles)) - { - cabinetFiles.Add(facade); - } - else - { - this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.SourceLineNumber, facade.Id, facade.DiskId)); - } - } - } - } - - /// - /// Adds a symbol to the section with cab name template filled in. - /// - /// - /// - /// - private MediaSymbol AddMediaSymbol(WixMediaTemplateSymbol mediaTemplateSymbol, int cabIndex) - { - return this.Section.AddSymbol(new MediaSymbol(mediaTemplateSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, cabIndex)) - { - DiskId = cabIndex, - Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex), - CompressionLevel = mediaTemplateSymbol.CompressionLevel, - }); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs deleted file mode 100644 index 76bcd532..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs +++ /dev/null @@ -1,1305 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Text.RegularExpressions; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Services; - - /// - /// Include transforms in a patch. - /// - internal class AttachPatchTransformsCommand - { - private static readonly string[] PatchUninstallBreakingTables = new[] - { - "AppId", - "BindImage", - "Class", - "Complus", - "CreateFolder", - "DuplicateFile", - "Environment", - "Extension", - "Font", - "IniFile", - "IsolatedComponent", - "LockPermissions", - "MIME", - "MoveFile", - "MsiLockPermissionsEx", - "MsiServiceConfig", - "MsiServiceConfigFailureActions", - "ODBCAttribute", - "ODBCDataSource", - "ODBCDriver", - "ODBCSourceAttribute", - "ODBCTranslator", - "ProgId", - "PublishComponent", - "RemoveIniFile", - "SelfReg", - "ServiceControl", - "ServiceInstall", - "TypeLib", - "Verb", - }; - - private readonly TableDefinitionCollection tableDefinitions; - - public AttachPatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, IEnumerable transforms) - { - this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.Intermediate = intermediate; - this.Transforms = transforms; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private Intermediate Intermediate { get; } - - private IEnumerable Transforms { get; } - - public IEnumerable SubStorages { get; private set; } - - public IEnumerable Execute() - { - var subStorages = new List(); - - if (this.Transforms == null || !this.Transforms.Any()) - { - this.Messaging.Write(ErrorMessages.PatchWithoutTransforms()); - return subStorages; - } - - var summaryInfo = this.ExtractPatchSummaryInfo(); - - var section = this.Intermediate.Sections.First(); - - var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList(); - - // Get the patch id from the WixPatchId symbol. - var patchSymbol = symbols.OfType().FirstOrDefault(); - - if (String.IsNullOrEmpty(patchSymbol.Id?.Id)) - { - this.Messaging.Write(ErrorMessages.ExpectedPatchIdInWixMsp()); - return subStorages; - } - - if (String.IsNullOrEmpty(patchSymbol.ClientPatchId)) - { - this.Messaging.Write(ErrorMessages.ExpectedClientPatchIdInWixMsp()); - return subStorages; - } - - // enumerate patch.Media to map diskId to Media row - var patchMediaByDiskId = symbols.OfType().ToDictionary(t => t.DiskId); - - if (patchMediaByDiskId.Count == 0) - { - this.Messaging.Write(ErrorMessages.ExpectedMediaRowsInWixMsp()); - return subStorages; - } - - // populate MSP summary information - var patchMetadata = this.PopulateSummaryInformation(summaryInfo, symbols, patchSymbol); - - // enumerate transforms - var productCodes = new SortedSet(); - var transformNames = new List(); - var validTransform = new List>(); - - var baselineSymbolsById = symbols.OfType().ToDictionary(t => t.Id.Id); - - foreach (var mainTransform in this.Transforms) - { - var baselineSymbol = baselineSymbolsById[mainTransform.Baseline]; - - var patchRefSymbols = symbols.OfType().ToList(); - if (patchRefSymbols.Count > 0) - { - if (!this.ReduceTransform(mainTransform.Transform, patchRefSymbols)) - { - // transform has none of the content authored into this patch - continue; - } - } - - // Validate the transform doesn't break any patch specific rules. - this.Validate(mainTransform); - - // ensure consistent File.Sequence within each Media - var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId]; - - // Ensure that files are sequenced after the last file in any transform. - var transformMediaTable = mainTransform.Transform.Tables["Media"]; - if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) - { - foreach (MediaRow transformMediaRow in transformMediaTable.Rows) - { - if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < transformMediaRow.LastSequence) - { - // The Binder will pre-increment the sequence. - mediaSymbol.LastSequence = transformMediaRow.LastSequence; - } - } - } - - // Use the Media/@DiskId if greater than the last sequence for backward compatibility. - if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < mediaSymbol.DiskId) - { - mediaSymbol.LastSequence = mediaSymbol.DiskId; - } - - // Ignore media table in the transform. - mainTransform.Transform.Tables.Remove("Media"); - mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); - - var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchSymbol, mainTransform.Transform, mediaSymbol, baselineSymbol, out var productCode); - - productCode = productCode.ToUpperInvariant(); - productCodes.Add(productCode); - validTransform.Add(Tuple.Create(productCode, mainTransform.Transform)); - - // attach these transforms to the patch object - // TODO: is this an acceptable way to auto-generate transform stream names? - var transformName = mainTransform.Baseline + "." + validTransform.Count.ToString(CultureInfo.InvariantCulture); - subStorages.Add(new SubStorage(transformName, mainTransform.Transform)); - subStorages.Add(new SubStorage("#" + transformName, pairedTransform)); - - transformNames.Add(":" + transformName); - transformNames.Add(":#" + transformName); - } - - if (validTransform.Count == 0) - { - this.Messaging.Write(ErrorMessages.PatchWithoutValidTransforms()); - return subStorages; - } - - // Validate that a patch authored as removable is actually removable - if (patchMetadata.TryGetValue("AllowRemoval", out var allowRemoval) && allowRemoval.Value == "1") - { - var uninstallable = true; - - foreach (var entry in validTransform) - { - uninstallable &= this.CheckUninstallableTransform(entry.Item1, entry.Item2); - } - - if (!uninstallable) - { - this.Messaging.Write(ErrorMessages.PatchNotRemovable()); - return subStorages; - } - } - - // Finish filling tables with transform-dependent data. - productCodes = FinalizePatchProductCodes(symbols, productCodes); - - // Semicolon delimited list of the product codes that can accept the patch. - summaryInfo.Add(SummaryInformationType.PatchProductCodes, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) - { - PropertyId = SummaryInformationType.PatchProductCodes, - Value = String.Join(";", productCodes) - }); - - // Semicolon delimited list of transform substorage names in the order they are applied. - summaryInfo.Add(SummaryInformationType.TransformNames, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) - { - PropertyId = SummaryInformationType.TransformNames, - Value = String.Join(";", transformNames) - }); - - // Put the summary information that was extracted back in now that it is updated. - foreach (var readSummaryInfo in summaryInfo.Values.OrderBy(s => s.PropertyId)) - { - section.AddSymbol(readSummaryInfo); - } - - this.SubStorages = subStorages; - - return subStorages; - } - - private Dictionary ExtractPatchSummaryInfo() - { - var result = new Dictionary(); - - foreach (var section in this.Intermediate.Sections) - { - // Remove all summary information from the symbols and remember those that - // are not calculated or reserved. - foreach (var patchSummaryInfo in section.Symbols.OfType().ToList()) - { - section.RemoveSymbol(patchSummaryInfo); - - if (patchSummaryInfo.PropertyId != SummaryInformationType.PatchProductCodes && - patchSummaryInfo.PropertyId != SummaryInformationType.PatchCode && - patchSummaryInfo.PropertyId != SummaryInformationType.PatchInstallerRequirement && - patchSummaryInfo.PropertyId != SummaryInformationType.Reserved11 && - patchSummaryInfo.PropertyId != SummaryInformationType.Reserved14 && - patchSummaryInfo.PropertyId != SummaryInformationType.Reserved16) - { - result.Add(patchSummaryInfo.PropertyId, patchSummaryInfo); - } - } - } - - return result; - } - - private Dictionary PopulateSummaryInformation(Dictionary summaryInfo, List symbols, WixPatchSymbol patchSymbol) - { - // PID_CODEPAGE - if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage)) - { - // Set the code page by default to the same code page for the - // string pool in the database. - AddSummaryInformation(SummaryInformationType.Codepage, patchSymbol.Codepage?.ToString(CultureInfo.InvariantCulture) ?? "0", patchSymbol.SourceLineNumbers); - } - - // GUID patch code for the patch. - AddSummaryInformation(SummaryInformationType.PatchCode, patchSymbol.Id.Id, patchSymbol.SourceLineNumbers); - - // Indicates the minimum Windows Installer version that is required to install the patch. - AddSummaryInformation(SummaryInformationType.PatchInstallerRequirement, ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture), patchSymbol.SourceLineNumbers); - - if (!summaryInfo.ContainsKey(SummaryInformationType.Security)) - { - AddSummaryInformation(SummaryInformationType.Security, "4", patchSymbol.SourceLineNumbers); // Read-only enforced; - } - - // Use authored comments or default to display name. - MsiPatchMetadataSymbol commentsSymbol = null; - - var metadataSymbols = symbols.OfType().Where(t => String.IsNullOrEmpty(t.Company)).ToDictionary(t => t.Property); - - if (!summaryInfo.ContainsKey(SummaryInformationType.Title) && - metadataSymbols.TryGetValue("DisplayName", out var displayName)) - { - AddSummaryInformation(SummaryInformationType.Title, displayName.Value, displayName.SourceLineNumbers); - - // Default comments to use display name as-is. - commentsSymbol = displayName; - } - - // TODO: This code below seems unnecessary given the codepage is set at the top of this method. - //if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage) && - // metadataValues.TryGetValue("CodePage", out var codepage)) - //{ - // AddSummaryInformation(SummaryInformationType.Codepage, codepage); - //} - - if (!summaryInfo.ContainsKey(SummaryInformationType.PatchPackageName) && - metadataSymbols.TryGetValue("Description", out var description)) - { - AddSummaryInformation(SummaryInformationType.PatchPackageName, description.Value, description.SourceLineNumbers); - } - - if (!summaryInfo.ContainsKey(SummaryInformationType.Author) && - metadataSymbols.TryGetValue("ManufacturerName", out var manufacturer)) - { - AddSummaryInformation(SummaryInformationType.Author, manufacturer.Value, manufacturer.SourceLineNumbers); - } - - // Special metadata marshalled through the build. - //var wixMetadataValues = symbols.OfType().ToDictionary(t => t.Id.Id, t => t.Value); - - //if (wixMetadataValues.TryGetValue("Comments", out var wixComments)) - if (metadataSymbols.TryGetValue("Comments", out var wixComments)) - { - commentsSymbol = wixComments; - } - - // Write the package comments to summary info. - if (!summaryInfo.ContainsKey(SummaryInformationType.Comments) && - commentsSymbol != null) - { - AddSummaryInformation(SummaryInformationType.Comments, commentsSymbol.Value, commentsSymbol.SourceLineNumbers); - } - - return metadataSymbols; - - void AddSummaryInformation(SummaryInformationType type, string value, SourceLineNumber sourceLineNumber) - { - summaryInfo.Add(type, new SummaryInformationSymbol(sourceLineNumber) - { - PropertyId = type, - Value = value - }); - } - } - - /// - /// Ensure transform is uninstallable. - /// - /// Product code in transform. - /// Transform generated by torch. - /// True if the transform is uninstallable - private bool CheckUninstallableTransform(string productCode, WindowsInstallerData transform) - { - var success = true; - - foreach (var tableName in PatchUninstallBreakingTables) - { - if (transform.TryGetTable(tableName, out var table)) - { - foreach (var row in table.Rows) - { - if (row.Operation == RowOperation.Add) - { - success = false; - - var primaryKey = row.GetPrimaryKey('/') ?? String.Empty; - - this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey)); - } - } - } - } - - return success; - } - - /// - /// Reduce the transform according to the patch references. - /// - /// transform generated by torch. - /// Table contains patch family filter. - /// true if the transform is not empty - private bool ReduceTransform(WindowsInstallerData transform, IEnumerable patchRefSymbols) - { - // identify sections to keep - var oldSections = new Dictionary(); - var newSections = new Dictionary(); - var tableKeyRows = new Dictionary>(); - var sequenceList = new List
(); - var componentFeatureAddsIndex = new Dictionary>(); - var customActionTable = new Dictionary(); - var directoryTableAdds = new Dictionary(); - var featureTableAdds = new Dictionary(); - var keptComponents = new Dictionary(); - var keptDirectories = new Dictionary(); - var keptFeatures = new Dictionary(); - var keptLockPermissions = new HashSet(); - var keptMsiLockPermissionExs = new HashSet(); - - var componentCreateFolderIndex = new Dictionary>(); - var directoryLockPermissionsIndex = new Dictionary>(); - var directoryMsiLockPermissionsExIndex = new Dictionary>(); - - foreach (var patchRefSymbol in patchRefSymbols) - { - var tableName = patchRefSymbol.Table; - var key = patchRefSymbol.PrimaryKeys; - - // Short circuit filtering if all changes should be included. - if ("*" == tableName && "*" == key) - { - RemoveProductCodeFromTransform(transform); - return true; - } - - if (!transform.Tables.TryGetTable(tableName, out var table)) - { - // Table not found. - continue; - } - - // Index the table. - if (!tableKeyRows.TryGetValue(tableName, out var keyRows)) - { - keyRows = new Dictionary(); - tableKeyRows.Add(tableName, keyRows); - - foreach (var newRow in table.Rows) - { - var primaryKey = newRow.GetPrimaryKey(); - keyRows.Add(primaryKey, newRow); - } - } - - if (!keyRows.TryGetValue(key, out var row)) - { - // Row not found. - continue; - } - - // Differ.sectionDelimiter - var sections = row.SectionId.Split('/'); - oldSections[sections[0]] = row; - newSections[sections[1]] = row; - } - - // throw away sections not referenced - var keptRows = 0; - Table directoryTable = null; - Table featureTable = null; - Table lockPermissionsTable = null; - Table msiLockPermissionsTable = null; - - foreach (var table in transform.Tables) - { - if ("_SummaryInformation" == table.Name) - { - continue; - } - - if (table.Name == "AdminExecuteSequence" - || table.Name == "AdminUISequence" - || table.Name == "AdvtExecuteSequence" - || table.Name == "InstallUISequence" - || table.Name == "InstallExecuteSequence") - { - sequenceList.Add(table); - continue; - } - - for (var i = 0; i < table.Rows.Count; i++) - { - var row = table.Rows[i]; - - if (table.Name == "CreateFolder") - { - var createFolderComponentId = row.FieldAsString(1); - - if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList)) - { - directoryList = new List(); - componentCreateFolderIndex.Add(createFolderComponentId, directoryList); - } - - directoryList.Add(row.FieldAsString(0)); - } - - if (table.Name == "CustomAction") - { - customActionTable.Add(row.FieldAsString(0), row); - } - - if (table.Name == "Directory") - { - directoryTable = table; - if (RowOperation.Add == row.Operation) - { - directoryTableAdds.Add(row.FieldAsString(0), row); - } - } - - if (table.Name == "Feature") - { - featureTable = table; - if (RowOperation.Add == row.Operation) - { - featureTableAdds.Add(row.FieldAsString(0), row); - } - } - - if (table.Name == "FeatureComponents") - { - if (RowOperation.Add == row.Operation) - { - var featureId = row.FieldAsString(0); - var componentId = row.FieldAsString(1); - - if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList)) - { - featureList = new List(); - componentFeatureAddsIndex.Add(componentId, featureList); - } - - featureList.Add(featureId); - } - } - - if (table.Name == "LockPermissions") - { - lockPermissionsTable = table; - if ("CreateFolder" == row.FieldAsString(1)) - { - var directoryId = row.FieldAsString(0); - - if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList)) - { - rowList = new List(); - directoryLockPermissionsIndex.Add(directoryId, rowList); - } - - rowList.Add(row); - } - } - - if (table.Name == "MsiLockPermissionsEx") - { - msiLockPermissionsTable = table; - if ("CreateFolder" == row.FieldAsString(1)) - { - var directoryId = row.FieldAsString(0); - - if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList)) - { - rowList = new List(); - directoryMsiLockPermissionsExIndex.Add(directoryId, rowList); - } - - rowList.Add(row); - } - } - - if (null == row.SectionId) - { - table.Rows.RemoveAt(i); - i--; - } - else - { - var sections = row.SectionId.Split('/'); - // ignore the row without section id. - if (0 == sections[0].Length && 0 == sections[1].Length) - { - table.Rows.RemoveAt(i); - i--; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - if ("Component" == table.Name) - { - keptComponents.Add(row.FieldAsString(0), row); - } - - if ("Directory" == table.Name) - { - keptDirectories.Add(row.FieldAsString(0), row); - } - - if ("Feature" == table.Name) - { - keptFeatures.Add(row.FieldAsString(0), row); - } - - keptRows++; - } - else - { - table.Rows.RemoveAt(i); - i--; - } - } - } - } - - keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); - - if (null != directoryTable) - { - foreach (var componentRow in keptComponents.Values) - { - var componentId = componentRow.FieldAsString(0); - - if (RowOperation.Add == componentRow.Operation) - { - // Make sure each added component has its required directory and feature heirarchy. - var directoryId = componentRow.FieldAsString(2); - while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow)) - { - if (!keptDirectories.ContainsKey(directoryId)) - { - directoryTable.Rows.Add(directoryRow); - keptDirectories.Add(directoryId, directoryRow); - keptRows++; - } - - directoryId = directoryRow.FieldAsString(1); - } - - if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds)) - { - foreach (var featureId in componentFeatureIds) - { - var currentFeatureId = featureId; - while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow)) - { - if (!keptFeatures.ContainsKey(currentFeatureId)) - { - featureTable.Rows.Add(featureRow); - keptFeatures.Add(currentFeatureId, featureRow); - keptRows++; - } - - currentFeatureId = featureRow.FieldAsString(1); - } - } - } - } - - // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept. - foreach (var keptComponentId in keptComponents.Keys) - { - if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList)) - { - foreach (var directoryId in directoryList) - { - if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList)) - { - foreach (var lockPermissionsRow in lockPermissionsRowList) - { - var key = lockPermissionsRow.GetPrimaryKey('/'); - if (keptLockPermissions.Add(key)) - { - lockPermissionsTable.Rows.Add(lockPermissionsRow); - keptRows++; - } - } - } - - if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList)) - { - foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList) - { - var key = msiLockPermissionsExRow.GetPrimaryKey('/'); - if (keptMsiLockPermissionExs.Add(key)) - { - msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow); - keptRows++; - } - } - } - } - } - } - } - } - - keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); - - // Delete tables that are empty. - var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList(); - - foreach (var tableName in tablesToDelete) - { - transform.Tables.Remove(tableName); - } - - return keptRows > 0; - } - - private void Validate(PatchTransform patchTransform) - { - var transformPath = patchTransform.Baseline; // TODO: this is used in error messages, how best to set it? - var transform = patchTransform.Transform; - - // Changing the ProdocutCode in a patch transform is not recommended. - if (transform.TryGetTable("Property", out var propertyTable)) - { - foreach (var row in propertyTable.Rows) - { - // Only interested in modified rows; fast check. - if (RowOperation.Modify == row.Operation && - "ProductCode".Equals(row.FieldAsString(0), StringComparison.Ordinal)) - { - this.Messaging.Write(WarningMessages.MajorUpgradePatchNotRecommended()); - } - } - } - - // If there is nothing in the component table we can return early because the remaining checks are component based. - if (!transform.TryGetTable("Component", out var componentTable)) - { - return; - } - - // Index Feature table row operations - var featureOps = new Dictionary(); - if (transform.TryGetTable("Feature", out var featureTable)) - { - foreach (var row in featureTable.Rows) - { - featureOps[row.FieldAsString(0)] = row.Operation; - } - } - - // Index Component table and check for keypath modifications - var componentKeyPath = new Dictionary(); - var deletedComponent = new Dictionary(); - foreach (var row in componentTable.Rows) - { - var id = row.FieldAsString(0); - var keypath = row.FieldAsString(5) ?? String.Empty; - - componentKeyPath.Add(id, keypath); - - if (RowOperation.Delete == row.Operation) - { - deletedComponent.Add(id, row); - } - else if (RowOperation.Modify == row.Operation) - { - if (row.Fields[1].Modified) - { - // Changing the guid of a component is equal to deleting the old one and adding a new one. - deletedComponent.Add(id, row); - } - - // If the keypath is modified its an error - if (row.Fields[5].Modified) - { - this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, id, transformPath)); - } - } - } - - // Verify changes in the file table - if (transform.TryGetTable("File", out var fileTable)) - { - var componentWithChangedKeyPath = new Dictionary(); - foreach (FileRow row in fileTable.Rows) - { - if (RowOperation.None == row.Operation) - { - continue; - } - - var fileId = row.File; - var componentId = row.Component; - - // If this file is the keypath of a component - if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal)) - { - if (row.Fields[2].Modified) - { - // You can't change the filename of a file that is the keypath of a component. - this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, componentId, transformPath)); - } - - if (!componentWithChangedKeyPath.ContainsKey(componentId)) - { - componentWithChangedKeyPath.Add(componentId, fileId); - } - } - - if (RowOperation.Delete == row.Operation) - { - // If the file is removed from a component that is not deleted. - if (!deletedComponent.ContainsKey(componentId)) - { - var foundRemoveFileEntry = false; - var filename = this.BackendHelper.GetMsiFileName(row.FieldAsString(2), false, true); - - if (transform.TryGetTable("RemoveFile", out var removeFileTable)) - { - foreach (var removeFileRow in removeFileTable.Rows) - { - if (RowOperation.Delete == removeFileRow.Operation) - { - continue; - } - - if (componentId == removeFileRow.FieldAsString(1)) - { - // Check if there is a RemoveFile entry for this file - if (null != removeFileRow[2]) - { - var removeFileName = this.BackendHelper.GetMsiFileName(removeFileRow.FieldAsString(2), false, true); - - // Convert the MSI format for a wildcard string to Regex format. - removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); - - var regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - if (regex.IsMatch(filename)) - { - foundRemoveFileEntry = true; - break; - } - } - } - } - } - - if (!foundRemoveFileEntry) - { - this.Messaging.Write(WarningMessages.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId)); - } - } - } - } - } - - var featureComponentsTable = transform.Tables["FeatureComponents"]; - - if (0 < deletedComponent.Count) - { - // Index FeatureComponents table. - var featureComponents = new Dictionary>(); - - if (null != featureComponentsTable) - { - foreach (var row in featureComponentsTable.Rows) - { - var componentId = row.FieldAsString(1); - - if (!featureComponents.TryGetValue(componentId, out var features)) - { - features = new List(); - featureComponents.Add(componentId, features); - } - - features.Add(row.FieldAsString(0)); - } - } - - // Check to make sure if a component was deleted, the feature was too. - foreach (var entry in deletedComponent) - { - if (featureComponents.TryGetValue(entry.Key, out var features)) - { - foreach (var featureId in features) - { - if (!featureOps.TryGetValue(featureId, out var op) || op != RowOperation.Delete) - { - // The feature was not deleted. - this.Messaging.Write(ErrorMessages.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, transformPath)); - } - } - } - } - } - - // Warn if new components are added to existing features - if (null != featureComponentsTable) - { - foreach (var row in featureComponentsTable.Rows) - { - if (RowOperation.Add == row.Operation) - { - // Check if the feature is in the Feature table - var feature_ = row.FieldAsString(0); - var component_ = row.FieldAsString(1); - - // Features may not be present if not referenced - if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_]) - { - this.Messaging.Write(WarningMessages.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, transformPath)); - } - } - } - } - } - - /// - /// Remove the ProductCode property from the transform. - /// - /// The transform. - /// - /// Changing the ProductCode is not supported in a patch. - /// - private static void RemoveProductCodeFromTransform(WindowsInstallerData transform) - { - if (transform.Tables.TryGetTable("Property", out var propertyTable)) - { - for (var i = 0; i < propertyTable.Rows.Count; ++i) - { - var propertyRow = propertyTable.Rows[i]; - var property = (string)propertyRow[0]; - - if ("ProductCode" == property) - { - propertyTable.Rows.RemoveAt(i); - break; - } - } - } - } - - /// - /// Check if the section is in a PatchFamily. - /// - /// Section id in target wixout - /// Section id in upgrade wixout - /// Dictionary contains section id should be kept in the baseline wixout. - /// Dictionary contains section id should be kept in the upgrade wixout. - /// true if section in patch family - private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary oldSections, Dictionary newSections) - { - var result = false; - - if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection))) - { - result = true; - } - else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(newSection))) - { - result = true; - } - - return result; - } - - /// - /// Reduce the transform sequence tables. - /// - /// ArrayList of tables to be reduced - /// Hashtable contains section id should be kept in the baseline wixout. - /// Hashtable contains section id should be kept in the target wixout. - /// Hashtable contains all the rows in the CustomAction table. - /// Number of rows left - private static int ReduceTransformSequenceTable(List
sequenceList, Dictionary oldSections, Dictionary newSections, Dictionary customAction) - { - var keptRows = 0; - - foreach (var currentTable in sequenceList) - { - for (var i = 0; i < currentTable.Rows.Count; i++) - { - var row = currentTable.Rows[i]; - var actionName = row.Fields[0].Data.ToString(); - var sections = row.SectionId.Split('/'); - var isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0); - - if (row.Operation == RowOperation.None) - { - // Ignore the rows without section id. - if (isSectionIdEmpty) - { - currentTable.Rows.RemoveAt(i); - i--; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - keptRows++; - } - else - { - currentTable.Rows.RemoveAt(i); - i--; - } - } - else if (row.Operation == RowOperation.Modify) - { - var sequenceChanged = row.Fields[2].Modified; - var conditionChanged = row.Fields[1].Modified; - - if (sequenceChanged && !conditionChanged) - { - keptRows++; - } - else if (!sequenceChanged && conditionChanged) - { - if (isSectionIdEmpty) - { - currentTable.Rows.RemoveAt(i); - i--; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - keptRows++; - } - else - { - currentTable.Rows.RemoveAt(i); - i--; - } - } - else if (sequenceChanged && conditionChanged) - { - if (isSectionIdEmpty) - { - row.Fields[1].Modified = false; - keptRows++; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - keptRows++; - } - else - { - row.Fields[1].Modified = false; - keptRows++; - } - } - } - else if (row.Operation == RowOperation.Delete) - { - if (isSectionIdEmpty) - { - // it is a stardard action which is added by wix, we should keep this action. - row.Operation = RowOperation.None; - keptRows++; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - keptRows++; - } - else - { - if (customAction.ContainsKey(actionName)) - { - currentTable.Rows.RemoveAt(i); - i--; - } - else - { - // it is a stardard action, we should keep this action. - row.Operation = RowOperation.None; - keptRows++; - } - } - } - else if (row.Operation == RowOperation.Add) - { - if (isSectionIdEmpty) - { - keptRows++; - } - else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) - { - keptRows++; - } - else - { - if (customAction.ContainsKey(actionName)) - { - currentTable.Rows.RemoveAt(i); - i--; - } - else - { - keptRows++; - } - } - } - } - } - - return keptRows; - } - - /// - /// Create the #transform for the given main transform. - /// - private WindowsInstallerData BuildPairedTransform(Dictionary summaryInfo, Dictionary patchMetadata, WixPatchSymbol patchIdSymbol, WindowsInstallerData mainTransform, MediaSymbol mediaSymbol, WixPatchBaselineSymbol baselineSymbol, out string productCode) - { - productCode = null; - - var pairedTransform = new WindowsInstallerData(null) - { - Type = OutputType.Transform, - Codepage = mainTransform.Codepage - }; - - // lookup productVersion property to correct summaryInformation - var newProductVersion = mainTransform.Tables["Property"]?.Rows.FirstOrDefault(r => r.FieldAsString(0) == "ProductVersion")?.FieldAsString(1); - - var mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; - var mainSummaryRows = mainSummaryTable.Rows.ToDictionary(r => r.FieldAsInteger(0)); - - var baselineValidationFlags = ((int)baselineSymbol.ValidationFlags).ToString(CultureInfo.InvariantCulture); - - if (!mainSummaryRows.ContainsKey((int)SummaryInformationType.TransformValidationFlags)) - { - var mainSummaryRow = mainSummaryTable.CreateRow(baselineSymbol.SourceLineNumbers); - mainSummaryRow[0] = (int)SummaryInformationType.TransformValidationFlags; - mainSummaryRow[1] = baselineValidationFlags; - } - - // copy summary information from core transform - var pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); - - foreach (var mainSummaryRow in mainSummaryTable.Rows) - { - var type = (SummaryInformationType)mainSummaryRow.FieldAsInteger(0); - var value = mainSummaryRow.FieldAsString(1); - switch (type) - { - case SummaryInformationType.TransformProductCodes: - var propertyData = value.Split(';'); - var oldProductVersion = propertyData[0].Substring(38); - var upgradeCode = propertyData[2]; - productCode = propertyData[0].Substring(0, 38); - - if (newProductVersion == null) - { - newProductVersion = oldProductVersion; - } - - // Force mainTranform to 'old;new;upgrade' and pairedTransform to 'new;new;upgrade' - mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); - value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); - break; - case SummaryInformationType.TransformValidationFlags: // use validation flags authored into the patch XML. - value = baselineValidationFlags; - mainSummaryRow[1] = value; - break; - } - - var pairedSummaryRow = pairedSummaryTable.CreateRow(mainSummaryRow.SourceLineNumbers); - pairedSummaryRow[0] = mainSummaryRow[0]; - pairedSummaryRow[1] = value; - } - - if (productCode == null) - { - this.Messaging.Write(ErrorMessages.CouldNotDetermineProductCodeFromTransformSummaryInfo()); - return null; - } - - // Copy File table - if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count) - { - var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); - - foreach (FileRow mainFileRow in mainFileTable.Rows) - { - // Set File.Sequence to non null to satisfy transform bind. - mainFileRow.Sequence = 1; - - // Delete's don't need rows in the paired transform. - if (mainFileRow.Operation == RowOperation.Delete) - { - continue; - } - - var pairedFileRow = (FileRow)pairedFileTable.CreateRow(mainFileRow.SourceLineNumbers); - pairedFileRow.Operation = RowOperation.Modify; - mainFileRow.CopyTo(pairedFileRow); - - // Override authored media for patch bind. - mainFileRow.DiskId = mediaSymbol.DiskId; - - // Suppress any change to File.Sequence to avoid bloat. - mainFileRow.Fields[7].Modified = false; - - // Force File row to appear in the transform. - switch (mainFileRow.Operation) - { - case RowOperation.Modify: - case RowOperation.Add: - pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; - pairedFileRow.Fields[6].Modified = true; - pairedFileRow.Operation = mainFileRow.Operation; - break; - default: - pairedFileRow.Fields[6].Modified = false; - break; - } - } - } - - // Add Media row to pairedTransform - var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); - var pairedMediaRow = (MediaRow)pairedMediaTable.CreateRow(mediaSymbol.SourceLineNumbers); - pairedMediaRow.Operation = RowOperation.Add; - pairedMediaRow.DiskId = mediaSymbol.DiskId; - pairedMediaRow.LastSequence = mediaSymbol.LastSequence ?? 0; - pairedMediaRow.DiskPrompt = mediaSymbol.DiskPrompt; - pairedMediaRow.Cabinet = mediaSymbol.Cabinet; - pairedMediaRow.VolumeLabel = mediaSymbol.VolumeLabel; - pairedMediaRow.Source = mediaSymbol.Source; - - // Add PatchPackage for this Media - var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); - pairedPackageTable.Operation = TableOperation.Add; - var pairedPackageRow = pairedPackageTable.CreateRow(mediaSymbol.SourceLineNumbers); - pairedPackageRow.Operation = RowOperation.Add; - pairedPackageRow[0] = patchIdSymbol.Id.Id; - pairedPackageRow[1] = mediaSymbol.DiskId; - - // Add the property to the patch transform's Property table. - var pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); - pairedPropertyTable.Operation = TableOperation.Add; - - // Add property to both identify client patches and whether those patches are removable or not - patchMetadata.TryGetValue("AllowRemoval", out var allowRemovalSymbol); - - var pairedPropertyRow = pairedPropertyTable.CreateRow(allowRemovalSymbol?.SourceLineNumbers); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".AllowRemoval"); - pairedPropertyRow[1] = allowRemovalSymbol?.Value ?? "0"; - - // Add this patch code GUID to the patch transform to identify - // which patches are installed, including in multi-patch - // installations. - pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".PatchCode"); - pairedPropertyRow[1] = patchIdSymbol.Id.Id; - - // Add PATCHNEWPACKAGECODE to apply to admin layouts. - pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; - pairedPropertyRow[1] = patchIdSymbol.Id.Id; - - // Add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts. - if (summaryInfo.TryGetValue(SummaryInformationType.Subject, out var subjectSymbol)) - { - pairedPropertyRow = pairedPropertyTable.CreateRow(subjectSymbol.SourceLineNumbers); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; - pairedPropertyRow[1] = subjectSymbol.Value; - } - - if (summaryInfo.TryGetValue(SummaryInformationType.Comments, out var commentsSymbol)) - { - pairedPropertyRow = pairedPropertyTable.CreateRow(commentsSymbol.SourceLineNumbers); - pairedPropertyRow.Operation = RowOperation.Add; - pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; - pairedPropertyRow[1] = commentsSymbol.Value; - } - - return pairedTransform; - } - - private static SortedSet FinalizePatchProductCodes(List symbols, SortedSet productCodes) - { - var patchTargetSymbols = symbols.OfType().ToList(); - - if (patchTargetSymbols.Any()) - { - var targets = new SortedSet(); - var replace = true; - foreach (var wixPatchTargetRow in patchTargetSymbols) - { - var target = wixPatchTargetRow.ProductCode.ToUpperInvariant(); - if (target == "*") - { - replace = false; - } - else - { - targets.Add(target); - } - } - - // Replace the target ProductCodes with the authored list. - if (replace) - { - productCodes = targets; - } - else - { - // Copy the authored target ProductCodes into the list. - foreach (var target in targets) - { - productCodes.Add(target); - } - } - } - - return productCodes; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs deleted file mode 100644 index 9f36cd78..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ /dev/null @@ -1,646 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Binds a databse. - /// - internal class BindDatabaseCommand - { - // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. - internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); - - public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, string cubeFile) : this(context, backendExtension, null, cubeFile) - { - } - - public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, string cubeFile) - { - this.ServiceProvider = context.ServiceProvider; - - this.Messaging = context.ServiceProvider.GetService(); - - this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService(); - - this.PathResolver = this.ServiceProvider.GetService(); - - this.CabbingThreadCount = context.CabbingThreadCount; - this.CabCachePath = context.CabCachePath; - this.DefaultCompressionLevel = context.DefaultCompressionLevel; - this.DelayedFields = context.DelayedFields; - this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; - this.FileSystemManager = new FileSystemManager(context.FileSystemExtensions); - this.Intermediate = context.IntermediateRepresentation; - this.IntermediateFolder = context.IntermediateFolder; - this.OutputPath = context.OutputPath; - this.OutputPdbPath = context.PdbPath; - this.PdbType = context.PdbType; - this.ResolvedCodepage = context.ResolvedCodepage; - this.ResolvedSummaryInformationCodepage = context.ResolvedSummaryInformationCodepage; - this.ResolvedLcid = context.ResolvedLcid; - this.SuppressLayout = context.SuppressLayout; - - this.SubStorages = subStorages; - - this.SuppressValidation = context.SuppressValidation; - this.Ices = context.Ices; - this.SuppressedIces = context.SuppressIces; - this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile }; - - this.BackendExtensions = backendExtension; - } - - public IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; } - - private IPathResolver PathResolver { get; } - - private int CabbingThreadCount { get; } - - private string CabCachePath { get; } - - private CompressionLevel? DefaultCompressionLevel { get; } - - public IEnumerable DelayedFields { get; } - - public IEnumerable ExpectedEmbeddedFiles { get; } - - public FileSystemManager FileSystemManager { get; } - - public bool DeltaBinaryPatch { get; set; } - - private IEnumerable BackendExtensions { get; } - - private IEnumerable SubStorages { get; } - - private Intermediate Intermediate { get; } - - private string OutputPath { get; } - - public PdbType PdbType { get; set; } - - private string OutputPdbPath { get; } - - private int? ResolvedCodepage { get; } - - private int? ResolvedSummaryInformationCodepage { get; } - - private int? ResolvedLcid { get; } - - private bool SuppressAddingValidationRows { get; } - - private bool SuppressLayout { get; } - - private string IntermediateFolder { get; } - - private bool SuppressValidation { get; } - - private IEnumerable Ices { get; } - - private IEnumerable SuppressedIces { get; } - - private IEnumerable CubeFiles { get; } - - public IBindResult Execute() - { - if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) - { - this.Messaging.Write(ErrorMessages.IntermediatesMustBeResolved(this.Intermediate.Id)); - } - - var section = this.Intermediate.Sections.Single(); - - var packageSymbol = (section.Type == SectionType.Product) ? this.GetSingleSymbol(section) : null; - var moduleSymbol = (section.Type == SectionType.Module) ? this.GetSingleSymbol(section) : null; - var patchSymbol = (section.Type == SectionType.Patch) ? this.GetSingleSymbol(section) : null; - - var fileTransfers = new List(); - var trackedFiles = new List(); - - var containsMergeModules = false; - - // Load standard tables, authored custom tables, and extension custom tables. - TableDefinitionCollection tableDefinitions; - { - var command = new LoadTableDefinitionsCommand(this.Messaging, section, this.BackendExtensions); - command.Execute(); - - tableDefinitions = command.TableDefinitions; - } - - // Calculate codepage - var codepage = this.CalculateCodepage(packageSymbol, moduleSymbol, patchSymbol); - - // Process properties and create the delayed variable cache if needed. - Dictionary variableCache = null; - string productLanguage = null; - { - var command = new ProcessPropertiesCommand(section, packageSymbol, this.ResolvedLcid ?? 0, this.DelayedFields.Any(), this.WindowsInstallerBackendHelper); - command.Execute(); - - variableCache = command.DelayedVariablesCache; - productLanguage = command.ProductLanguage; - } - - // Process the summary information table after properties are processed. - bool compressed; - bool longNames; - int installerVersion; - Platform platform; - string modularizationSuffix; - { - var branding = this.ServiceProvider.GetService(); - - var command = new BindSummaryInfoCommand(section, this.ResolvedSummaryInformationCodepage, productLanguage, this.WindowsInstallerBackendHelper, branding); - command.Execute(); - - compressed = command.Compressed; - longNames = command.LongNames; - installerVersion = command.InstallerVersion; - platform = command.Platform; - modularizationSuffix = command.ModularizationSuffix; - } - - // Sequence all the actions. - { - var command = new SequenceActionsCommand(this.Messaging, section); - command.Execute(); - } - - if (section.Type == SectionType.Product || section.Type == SectionType.Module) - { - var command = new AddRequiredStandardDirectories(section, platform); - command.Execute(); - } - - { - var command = new CreateSpecialPropertiesCommand(section); - command.Execute(); - } - -#if TODO_PATCHING - ////if (OutputType.Patch == this.Output.Type) - ////{ - //// foreach (SubStorage substorage in this.Output.SubStorages) - //// { - //// Output transform = substorage.Data; - - //// ResolveFieldsCommand command = new ResolveFieldsCommand(); - //// command.Tables = transform.Tables; - //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - //// command.FileManagerCore = this.FileManagerCore; - //// command.FileManagers = this.FileManagers; - //// command.SupportDelayedResolution = false; - //// command.TempFilesLocation = this.TempFilesLocation; - //// command.WixVariableResolver = this.WixVariableResolver; - //// command.Execute(); - - //// this.MergeUnrealTables(transform.Tables); - //// } - ////} -#endif - - if (this.Messaging.EncounteredError) - { - return null; - } - - this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); - this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); - - // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). - { - var extractedFiles = this.WindowsInstallerBackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); - - trackedFiles.AddRange(extractedFiles); - } - - // This must occur after all variables and source paths have been resolved. - List fileFacades; - if (SectionType.Patch == section.Type) - { - var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.SubStorages); - command.Execute(); - - fileFacades = command.FileFacades; - } - else - { - var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper); - command.Execute(); - - fileFacades = command.FileFacades; - } - - // Retrieve file information from merge modules. - if (SectionType.Product == section.Type) - { - var wixMergeSymbols = section.Symbols.OfType().ToList(); - - if (wixMergeSymbols.Any()) - { - containsMergeModules = true; - - var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacades, installerVersion, this.IntermediateFolder, this.SuppressLayout); - command.Execute(); - - fileFacades.AddRange(command.MergeModulesFileFacades); - } - } - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - // Process SoftwareTags in MSI packages. - if (SectionType.Product == section.Type) - { - var softwareTags = section.Symbols.OfType().ToList(); - - if (softwareTags.Any()) - { - var command = new ProcessPackageSoftwareTagsCommand(section, softwareTags, this.IntermediateFolder); - command.Execute(); - } - } - - // Gather information about files that do not come from merge modules. - { - var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true); - command.Execute(); - } - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - // Now that the variable cache is populated, resolve any delayed fields. - if (this.DelayedFields.Any()) - { - this.WindowsInstallerBackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); - } - - // Update symbols that reference text files on disk. - { - var command = new UpdateFromTextFilesCommand(this.Messaging, section); - command.Execute(); - } - - // Add missing CreateFolder symbols to null-keypath components. - { - var command = new AddCreateFoldersCommand(section); - command.Execute(); - } - - // Process dependency references. - if (SectionType.Product == section.Type || SectionType.Module == section.Type) - { - var dependencyRefs = section.Symbols.OfType().ToList(); - - if (dependencyRefs.Any()) - { - var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs); - command.Execute(); - } - } - - // If there are any backend extensions, give them the opportunity to process - // the section now that the fields have all be resolved. - // - if (this.BackendExtensions.Any()) - { - using (new IntermediateFieldContext("wix.bind.finalize")) - { - foreach (var extension in this.BackendExtensions) - { - extension.SymbolsFinalized(section); - } - - var reresolvedFiles = section.Symbols - .OfType() - .Where(s => s.Fields.Any(f => f?.Context == "wix.bind.finalize")) - .ToList(); - - if (reresolvedFiles.Any()) - { - var updatedFacades = reresolvedFiles.Select(f => fileFacades.First(ff => ff.Id == f.Id?.Id)); - - var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, updatedFacades, variableCache, overwriteHash: false); - command.Execute(); - } - } - - if (this.Messaging.EncounteredError) - { - return null; - } - } - - // Set generated component guids and validate all guids. - { - var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); - command.Execute(); - } - - // Assign files to media and update file sequences. - Dictionary> filesByCabinetMedia; - IEnumerable uncompressedFiles; - { - var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, fileFacades); - order.Execute(); - - fileFacades = order.FileFacades; - - var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed); - assign.Execute(); - - filesByCabinetMedia = assign.FileFacadesByCabinetMedia; - uncompressedFiles = assign.UncompressedFileFacades; - - var update = new UpdateMediaSequencesCommand(section, fileFacades); - update.Execute(); - } - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better. - WindowsInstallerData data; - { - var command = new CreateWindowsInstallerDataFromIRCommand(this.Messaging, section, tableDefinitions, codepage, this.BackendExtensions, this.WindowsInstallerBackendHelper); - data = command.Execute(); - } - - IEnumerable suppressedTableNames = null; - if (data.Type == OutputType.Module) - { - // Modularize identifiers. - var modularize = new ModularizeCommand(this.WindowsInstallerBackendHelper, data, modularizationSuffix, section.Symbols.OfType()); - modularize.Execute(); - - // Ensure all sequence tables in place because, mergemod.dll requires them. - var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions); - suppressedTableNames = unsuppress.Execute(); - } - else if (data.Type == OutputType.Patch) - { - foreach (var storage in this.SubStorages) - { - data.SubStorages.Add(storage); - } - } - - // Stop processing if an error previously occurred. - if (this.Messaging.EncounteredError) - { - return null; - } - - // Ensure the intermediate folder is created since delta patches will be - // created there. - Directory.CreateDirectory(this.IntermediateFolder); - - if (SectionType.Patch == section.Type && this.DeltaBinaryPatch) - { - var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType().FirstOrDefault()); - command.Execute(); - } - - // create cabinet files and process uncompressed files - var layoutDirectory = Path.GetDirectoryName(this.OutputPath); - if (!this.SuppressLayout || OutputType.Module == data.Type) - { - this.Messaging.Write(VerboseMessages.CreatingCabinetFiles()); - - var mediaTemplate = section.Symbols.OfType().FirstOrDefault(); - - var command = new CreateCabinetsCommand(this.ServiceProvider, this.WindowsInstallerBackendHelper, mediaTemplate); - command.CabbingThreadCount = this.CabbingThreadCount; - command.CabCachePath = this.CabCachePath; - command.DefaultCompressionLevel = this.DefaultCompressionLevel; - command.Data = data; - command.Messaging = this.Messaging; - command.BackendExtensions = this.BackendExtensions; - command.LayoutDirectory = layoutDirectory; - command.Compressed = compressed; - command.ModularizationSuffix = modularizationSuffix; - command.FileFacadesByCabinet = filesByCabinetMedia; - command.ResolveMedia = this.ResolveMedia; - command.TableDefinitions = tableDefinitions; - command.IntermediateFolder = this.IntermediateFolder; - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - } - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - // We can create instance transforms since Component Guids and Outputs are created. - if (data.Type == OutputType.Product) - { - var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper); - command.Execute(); - } - else if (data.Type == OutputType.Patch) - { - // Copy output data back into the transforms. - var command = new UpdateTransformsWithFileFacades(this.Messaging, data, this.SubStorages, tableDefinitions, fileFacades); - command.Execute(); - } - - // Generate database file. - { - this.Messaging.Write(VerboseMessages.GeneratingDatabase()); - - var trackMsi = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final); - trackedFiles.Add(trackMsi); - - var command = new GenerateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, data, trackMsi.Path, tableDefinitions, this.IntermediateFolder, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false); - command.Execute(); - - trackedFiles.AddRange(command.GeneratedTemporaryFiles); - } - - // Stop processing if an error previously occurred. - if (this.Messaging.EncounteredError) - { - return null; - } - - // Merge modules. - if (containsMergeModules) - { - this.Messaging.Write(VerboseMessages.MergingModules()); - - var command = new MergeModulesCommand(this.Messaging, fileFacades, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder); - command.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return null; - } - - // Validate the output if there are CUBe files and we're not explicitly suppressing validation. - if (this.CubeFiles != null && !this.SuppressValidation) - { - var command = new ValidateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces); - command.Execute(); - - trackedFiles.AddRange(command.TrackedFiles); - } - - if (this.Messaging.EncounteredError) - { - return null; - } - - // Process uncompressed files. - if (!this.SuppressLayout && uncompressedFiles.Any()) - { - var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver); - command.Compressed = compressed; - command.FileFacades = uncompressedFiles; - command.LayoutDirectory = layoutDirectory; - command.LongNamesInImage = longNames; - command.ResolveMedia = this.ResolveMedia; - command.DatabasePath = this.OutputPath; - command.Execute(); - - fileTransfers.AddRange(command.FileTransfers); - trackedFiles.AddRange(command.TrackedFiles); - } - - // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables). - trackedFiles.AddRange(fileFacades.Select(f => this.WindowsInstallerBackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber))); - - var result = this.ServiceProvider.GetService(); - result.FileTransfers = fileTransfers; - result.TrackedFiles = trackedFiles; - result.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, data); - - return result; - } - - private int CalculateCodepage(WixPackageSymbol packageSymbol, WixModuleSymbol moduleSymbol, WixPatchSymbol patchSymbol) - { - var codepage = packageSymbol?.Codepage ?? moduleSymbol?.Codepage ?? patchSymbol?.Codepage; - - if (String.IsNullOrEmpty(codepage)) - { - codepage = this.ResolvedCodepage?.ToString() ?? "65001"; - - if (packageSymbol != null) - { - packageSymbol.Codepage = codepage; - } - else if (moduleSymbol != null) - { - moduleSymbol.Codepage = codepage; - } - else if (patchSymbol != null) - { - patchSymbol.Codepage = codepage; - } - } - - return this.WindowsInstallerBackendHelper.GetValidCodePage(codepage); - } - - private T GetSingleSymbol(IntermediateSection section) where T : IntermediateSymbol - { - var symbols = section.Symbols.OfType().ToList(); - - if (1 != symbols.Count) - { - throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); - } - - return symbols[0]; - } - - private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, WindowsInstallerData data) - { - WixOutput wixout; - - if (String.IsNullOrEmpty(this.OutputPdbPath)) - { - wixout = WixOutput.Create(); - } - else - { - var trackPdb = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); - trackedFiles.Add(trackPdb); - - wixout = WixOutput.Create(trackPdb.Path); - } - - intermediate.Save(wixout); - - data.Save(wixout); - - wixout.Reopen(); - - return wixout; - } - - private string ResolveMedia(MediaSymbol media, string mediaLayoutDirectory, string layoutDirectory) - { - string layout = null; - - foreach (var extension in this.BackendExtensions) - { - layout = extension.ResolveMedia(media, mediaLayoutDirectory, layoutDirectory); - if (!String.IsNullOrEmpty(layout)) - { - break; - } - } - - // If no binder file manager resolved the layout, do the default behavior. - if (String.IsNullOrEmpty(layout)) - { - if (String.IsNullOrEmpty(mediaLayoutDirectory)) - { - layout = layoutDirectory; - } - else if (Path.IsPathRooted(mediaLayoutDirectory)) - { - layout = mediaLayoutDirectory; - } - else - { - layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); - } - } - - return layout; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs deleted file mode 100644 index 41da2a13..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs +++ /dev/null @@ -1,211 +0,0 @@ -// 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.Bind -{ - using System; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - /// - /// Binds the summary information table of a database. - /// - internal class BindSummaryInfoCommand - { - public BindSummaryInfoCommand(IntermediateSection section, int? summaryInformationCodepage, string productLanguage, IBackendHelper backendHelper, IWixBranding branding) - { - this.Section = section; - this.SummaryInformationCodepage = summaryInformationCodepage; - this.ProductLanguage = productLanguage; - this.BackendHelper = backendHelper; - this.Branding = branding; - } - - private IntermediateSection Section { get; } - - private int? SummaryInformationCodepage { get; } - - private string ProductLanguage { get; } - - private IBackendHelper BackendHelper { get; } - - private IWixBranding Branding { get; } - - /// - /// Returns a flag indicating if files are compressed by default. - /// - public bool Compressed { get; private set; } - - /// - /// Returns a flag indicating if uncompressed files use long filenames. - /// - public bool LongNames { get; private set; } - - public int InstallerVersion { get; private set; } - - public Platform Platform { get; private set; } - - /// - /// Modularization guid, or null if the output is not a module. - /// - public string ModularizationSuffix { get; private set; } - - public void Execute() - { - this.Compressed = false; - this.LongNames = false; - this.InstallerVersion = 0; - this.ModularizationSuffix = null; - - SummaryInformationSymbol summaryInformationCodepageSymbol = null; - SummaryInformationSymbol platformAndLanguageSymbol = null; - var foundCreateDateTime = false; - var foundLastSaveDataTime = false; - var foundCreatingApplication = false; - var foundPackageCode = false; - var now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); - - foreach (var summaryInformationSymbol in this.Section.Symbols.OfType()) - { - switch (summaryInformationSymbol.PropertyId) - { - case SummaryInformationType.Codepage: // PID_CODEPAGE - summaryInformationCodepageSymbol = summaryInformationSymbol; - break; - - case SummaryInformationType.PlatformAndLanguage: - platformAndLanguageSymbol = summaryInformationSymbol; - break; - - case SummaryInformationType.PackageCode: // PID_REVNUMBER - foundPackageCode = true; - var packageCode = summaryInformationSymbol.Value; - - if (SectionType.Module == this.Section.Type) - { - this.ModularizationSuffix = "." + packageCode.Substring(1, 36).Replace('-', '_'); - } - break; - case SummaryInformationType.Created: - foundCreateDateTime = true; - break; - case SummaryInformationType.LastSaved: - foundLastSaveDataTime = true; - break; - case SummaryInformationType.WindowsInstallerVersion: - this.InstallerVersion = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber(); - break; - case SummaryInformationType.WordCount: - if (SectionType.Patch == this.Section.Type) - { - this.LongNames = true; - this.Compressed = true; - } - else - { - var attributes = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber(); - this.LongNames = (0 == (attributes & 1)); - this.Compressed = (2 == (attributes & 2)); - } - break; - case SummaryInformationType.CreatingApplication: // PID_APPNAME - foundCreatingApplication = true; - break; - } - } - - // Ensure the codepage is set properly. - if (summaryInformationCodepageSymbol == null) - { - summaryInformationCodepageSymbol = this.Section.AddSymbol(new SummaryInformationSymbol(null) - { - PropertyId = SummaryInformationType.Codepage - }); - } - - var codepage = summaryInformationCodepageSymbol.Value; - - if (String.IsNullOrEmpty(codepage)) - { - codepage = this.SummaryInformationCodepage?.ToString(CultureInfo.InvariantCulture) ?? "1252"; - } - - summaryInformationCodepageSymbol.Value = this.BackendHelper.GetValidCodePage(codepage, onlyAnsi: true).ToString(CultureInfo.InvariantCulture); - - // Ensure the language is set properly and figure out what platform we are targeting. - if (platformAndLanguageSymbol != null) - { - this.Platform = EnsureLanguageAndGetPlatformFromSummaryInformation(platformAndLanguageSymbol, this.ProductLanguage); - } - - // Set the revision number (package/patch code) if it should be automatically generated. - if (!foundPackageCode) - { - this.Section.AddSymbol(new SummaryInformationSymbol(null) - { - PropertyId = SummaryInformationType.PackageCode, - Value = this.BackendHelper.CreateGuid(), - }); - } - - // add a summary information row for the create time/date property if its not already set - if (!foundCreateDateTime) - { - this.Section.AddSymbol(new SummaryInformationSymbol(null) - { - PropertyId = SummaryInformationType.Created, - Value = now, - }); - } - - // add a summary information row for the last save time/date property if its not already set - if (!foundLastSaveDataTime) - { - this.Section.AddSymbol(new SummaryInformationSymbol(null) - { - PropertyId = SummaryInformationType.LastSaved, - Value = now, - }); - } - - // add a summary information row for the creating application property if its not already set - if (!foundCreatingApplication) - { - this.Section.AddSymbol(new SummaryInformationSymbol(null) - { - PropertyId = SummaryInformationType.CreatingApplication, - Value = this.Branding.GetCreatingApplication(), - }); - } - } - - private static Platform EnsureLanguageAndGetPlatformFromSummaryInformation(SummaryInformationSymbol symbol, string language) - { - var value = symbol.Value; - var separatorIndex = value.IndexOf(';'); - var platformValue = separatorIndex > 0 ? value.Substring(0, separatorIndex) : value; - - // If the language was provided and there was language value after the separator - // (or the separator was absent) then use the provided language. - if (!String.IsNullOrEmpty(language) && (separatorIndex < 0 || separatorIndex + 1 == value.Length)) - { - symbol.Value = platformValue + ';' + language; - } - - switch (platformValue) - { - case "x64": - return Platform.X64; - - case "Arm64": - return Platform.ARM64; - - case "Intel": - default: - return Platform.X86; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs deleted file mode 100644 index 3379ec5d..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs +++ /dev/null @@ -1,445 +0,0 @@ -// 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.Bind -{ - using System; - using System.Globalization; - using System.IO; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class BindTransformCommand - { - public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.FileSystemManager = fileSystemManager; - this.IntermediateFolder = intermediateFolder; - this.Transform = transform; - this.OutputPath = outputPath; - this.TableDefinitions = tableDefinitions; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private FileSystemManager FileSystemManager { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - private string IntermediateFolder { get; } - - private WindowsInstallerData Transform { get; } - - private string OutputPath { get; } - - public void Execute() - { - var transformFlags = 0; - - var targetOutput = new WindowsInstallerData(null); - var updatedOutput = new WindowsInstallerData(null); - - // TODO: handle added columns - - // to generate a localized transform, both the target and updated - // databases need to have the same code page. the only reason to - // set different code pages is to support localized primary key - // columns, but that would only support deleting rows. if this - // becomes necessary, define a PreviousCodepage property on the - // Output class and persist this throughout transform generation. - targetOutput.Codepage = this.Transform.Codepage; - updatedOutput.Codepage = this.Transform.Codepage; - - // remove certain Property rows which will be populated from summary information values - string targetUpgradeCode = null; - string updatedUpgradeCode = null; - - if (this.Transform.TryGetTable("Property", out var propertyTable)) - { - for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) - { - Row row = propertyTable.Rows[i]; - - if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) - { - propertyTable.Rows.RemoveAt(i); - - if ("UpgradeCode" == (string)row[0]) - { - updatedUpgradeCode = (string)row[1]; - } - } - } - } - - var targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - var updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - var targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); - var updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); - - // process special summary information values - foreach (var row in this.Transform.Tables["_SummaryInformation"].Rows) - { - var summaryId = row.FieldAsInteger(0); - var summaryData = row.FieldAsString(1); - - if ((int)SummaryInformation.Transform.CodePage == summaryId) - { - // convert from a web name if provided - var codePage = summaryData; - if (null == codePage) - { - codePage = "0"; - } - else - { - codePage = this.BackendHelper.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); - } - - var previousCodePage = row.Fields[1].PreviousData; - if (null == previousCodePage) - { - previousCodePage = "0"; - } - else - { - previousCodePage = this.BackendHelper.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); - } - - var targetCodePageRow = targetSummaryInfo.CreateRow(null); - targetCodePageRow[0] = 1; // PID_CODEPAGE - targetCodePageRow[1] = previousCodePage; - - var updatedCodePageRow = updatedSummaryInfo.CreateRow(null); - updatedCodePageRow[0] = 1; // PID_CODEPAGE - updatedCodePageRow[1] = codePage; - } - else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId || - (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == summaryId) - { - // the target language - var propertyData = summaryData.Split(';'); - var lang = 2 == propertyData.Length ? propertyData[1] : "0"; - - var tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetSummaryInfo : updatedSummaryInfo; - var tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetPropertyTable : updatedPropertyTable; - - var productLanguageRow = tempPropertyTable.CreateRow(null); - productLanguageRow[0] = "ProductLanguage"; - productLanguageRow[1] = lang; - - // set the platform;language on the MSI to be generated - var templateRow = tempSummaryInfo.CreateRow(null); - templateRow[0] = 7; // PID_TEMPLATE - templateRow[1] = summaryData; - } - else if ((int)SummaryInformation.Transform.ProductCodes == summaryId) - { - var propertyData = summaryData.Split(';'); - - var targetProductCodeRow = targetPropertyTable.CreateRow(null); - targetProductCodeRow[0] = "ProductCode"; - targetProductCodeRow[1] = propertyData[0].Substring(0, 38); - - var targetProductVersionRow = targetPropertyTable.CreateRow(null); - targetProductVersionRow[0] = "ProductVersion"; - targetProductVersionRow[1] = propertyData[0].Substring(38); - - var updatedProductCodeRow = updatedPropertyTable.CreateRow(null); - updatedProductCodeRow[0] = "ProductCode"; - updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); - - var updatedProductVersionRow = updatedPropertyTable.CreateRow(null); - updatedProductVersionRow[0] = "ProductVersion"; - updatedProductVersionRow[1] = propertyData[1].Substring(38); - - // UpgradeCode is optional and may not exists in the target - // or upgraded databases, so do not include a null-valued - // UpgradeCode property. - - targetUpgradeCode = propertyData[2]; - if (!String.IsNullOrEmpty(targetUpgradeCode)) - { - var targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); - targetUpgradeCodeRow[0] = "UpgradeCode"; - targetUpgradeCodeRow[1] = targetUpgradeCode; - - // If the target UpgradeCode is specified, an updated - // UpgradeCode is required. - if (String.IsNullOrEmpty(updatedUpgradeCode)) - { - updatedUpgradeCode = targetUpgradeCode; - } - } - - if (!String.IsNullOrEmpty(updatedUpgradeCode)) - { - var updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); - updatedUpgradeCodeRow[0] = "UpgradeCode"; - updatedUpgradeCodeRow[1] = updatedUpgradeCode; - } - } - else if ((int)SummaryInformation.Transform.ValidationFlags == summaryId) - { - transformFlags = Convert.ToInt32(summaryData, CultureInfo.InvariantCulture); - } - else if ((int)SummaryInformation.Transform.Reserved11 == summaryId) - { - // PID_LASTPRINTED should be null for transforms - row.Operation = RowOperation.None; - } - else - { - // add everything else as is - var targetRow = targetSummaryInfo.CreateRow(null); - targetRow[0] = row[0]; - targetRow[1] = row[1]; - - var updatedRow = updatedSummaryInfo.CreateRow(null); - updatedRow[0] = row[0]; - updatedRow[1] = row[1]; - } - } - - // Validate that both databases have an UpgradeCode if the - // authoring transform will validate the UpgradeCode; otherwise, - // MsiCreateTransformSummaryinfo() will fail with 1620. - if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && - (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) - { - this.Messaging.Write(ErrorMessages.BothUpgradeCodesRequired()); - } - - string emptyFile = null; - - foreach (var table in this.Transform.Tables) - { - // Ignore unreal tables when building transforms except the _Stream table. - // These tables are ignored when generating the database so there is no reason - // to process them here. - if (table.Definition.Unreal && "_Streams" != table.Name) - { - continue; - } - - // process table operations - switch (table.Operation) - { - case TableOperation.Add: - updatedOutput.EnsureTable(table.Definition); - break; - case TableOperation.Drop: - targetOutput.EnsureTable(table.Definition); - continue; - default: - targetOutput.EnsureTable(table.Definition); - updatedOutput.EnsureTable(table.Definition); - break; - } - - // process row operations - foreach (var row in table.Rows) - { - switch (row.Operation) - { - case RowOperation.Add: - var updatedTable = updatedOutput.EnsureTable(table.Definition); - updatedTable.Rows.Add(row); - continue; - - case RowOperation.Delete: - var targetTable = targetOutput.EnsureTable(table.Definition); - targetTable.Rows.Add(row); - - // fill-in non-primary key values - foreach (var field in row.Fields) - { - if (!field.Column.PrimaryKey) - { - if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) - { - field.Data = field.Column.MinValue; - } - else if (ColumnType.Object == field.Column.Type) - { - if (null == emptyFile) - { - emptyFile = Path.Combine(this.IntermediateFolder, "empty"); - } - - field.Data = emptyFile; - } - else - { - field.Data = "0"; - } - } - } - continue; - } - - // Assure that the file table's sequence is populated - if ("File" == table.Name) - { - foreach (var fileRow in table.Rows) - { - if (null == fileRow[7]) - { - if (RowOperation.Add == fileRow.Operation) - { - this.Messaging.Write(ErrorMessages.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); - break; - } - - // Set to 1 to prevent invalid IDT file from being generated - fileRow[7] = 1; - } - } - } - - // process modified and unmodified rows - var modifiedRow = false; - var targetRow = table.Definition.CreateRow(null); - var updatedRow = row; - for (var i = 0; i < row.Fields.Length; i++) - { - var updatedField = row.Fields[i]; - - if (updatedField.Modified) - { - // set a different value in the target row to ensure this value will be modified during transform generation - if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) - { - var data = updatedField.AsNullableInteger(); - targetRow[i] = (data == 1) ? 2 : 1; - } - else if (ColumnType.Object == updatedField.Column.Type) - { - if (null == emptyFile) - { - emptyFile = Path.Combine(this.IntermediateFolder, "empty"); - } - - targetRow[i] = emptyFile; - } - else - { - var data = updatedField.AsString(); - targetRow[i] = (data == "0") ? "1" : "0"; - } - - modifiedRow = true; - } - else if (ColumnType.Object == updatedField.Column.Type) - { - var objectField = (ObjectField)updatedField; - - // create an empty file for comparing against - if (null == objectField.PreviousData) - { - if (null == emptyFile) - { - emptyFile = Path.Combine(this.IntermediateFolder, "empty"); - } - - targetRow[i] = emptyFile; - modifiedRow = true; - } - else if (!this.FileSystemManager.CompareFiles(objectField.PreviousData, (string)objectField.Data)) - { - targetRow[i] = objectField.PreviousData; - modifiedRow = true; - } - } - else // unmodified - { - if (null != updatedField.Data) - { - targetRow[i] = updatedField.Data; - } - } - } - - // modified rows and certain special rows go in the target and updated msi databases - if (modifiedRow || - ("Property" == table.Name && - ("ProductCode" == (string)row[0] || - "ProductLanguage" == (string)row[0] || - "ProductVersion" == (string)row[0] || - "UpgradeCode" == (string)row[0]))) - { - var targetTable = targetOutput.EnsureTable(table.Definition); - targetTable.Rows.Add(targetRow); - - var updatedTable = updatedOutput.EnsureTable(table.Definition); - updatedTable.Rows.Add(updatedRow); - } - } - } - - //foreach (BinderExtension extension in this.Extensions) - //{ - // extension.PostBind(this.Context); - //} - - // Any errors encountered up to this point can cause errors during generation. - if (this.Messaging.EncounteredError) - { - return; - } - - var transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); - var targetDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_target.msi")); - var updatedDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_updated.msi")); - - try - { - if (!String.IsNullOrEmpty(emptyFile)) - { - using (var fileStream = File.Create(emptyFile)) - { - } - } - - this.GenerateDatabase(targetOutput, targetDatabaseFile, keepAddedColumns: false); - this.GenerateDatabase(updatedOutput, updatedDatabaseFile, keepAddedColumns: true); - - // make sure the directory exists - Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); - - // create the transform file - using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) - using (var updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) - { - if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) - { - updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); - } - else - { - this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); - } - } - } - finally - { - if (!String.IsNullOrEmpty(emptyFile)) - { - File.Delete(emptyFile); - } - } - } - - private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns) - { - var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true); - command.Execute(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs deleted file mode 100644 index 13b079ad..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs +++ /dev/null @@ -1,171 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - /// - /// Builds cabinets using multiple threads. This implements a thread pool that generates cabinets with multiple - /// threads. Unlike System.Threading.ThreadPool, it waits until all threads are finished. - /// - internal sealed class CabinetBuilder - { - private readonly Queue cabinetWorkItems; - private int threadCount; - - // Address of Binder's callback function for Cabinet Splitting - private readonly IntPtr newCabNamesCallBackAddress; - - /// - /// Instantiate a new CabinetBuilder. - /// - /// - /// number of threads to use - /// Address of Binder's callback function for Cabinet Splitting - public CabinetBuilder(IMessaging messaging, int threadCount, IntPtr newCabNamesCallBackAddress) - { - if (0 >= threadCount) - { - throw new ArgumentOutOfRangeException(nameof(threadCount)); - } - - this.cabinetWorkItems = new Queue(); - this.Messaging = messaging; - this.threadCount = threadCount; - - // Set Address of Binder's callback function for Cabinet Splitting - this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; - } - - private IMessaging Messaging { get; } - - public int MaximumCabinetSizeForLargeFileSplitting { get; set; } - - public int MaximumUncompressedMediaSize { get; set; } - - /// - /// Enqueues a CabinetWorkItem to the queue. - /// - /// cabinet work item - public void Enqueue(CabinetWorkItem cabinetWorkItem) => this.cabinetWorkItems.Enqueue(cabinetWorkItem); - - /// - /// Create the queued cabinets. - /// - /// error message number (zero if no error) - public void CreateQueuedCabinets() - { - // don't create more threads than the number of cabinets to build - var numberOfThreads = Math.Min(this.threadCount, this.cabinetWorkItems.Count); - - if (0 < numberOfThreads) - { - var threads = new Thread[numberOfThreads]; - - for (var i = 0; i < threads.Length; i++) - { - threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems)); - threads[i].Start(); - } - - // wait for all threads to finish - foreach (var thread in threads) - { - thread.Join(); - } - } - } - - /// - /// This function gets called by multiple threads to do actual work. - /// It takes one work item at a time and calls this.CreateCabinet(). - /// It does not return until cabinetWorkItems queue is empty - /// - private void ProcessWorkItems() - { - try - { - while (true) - { - CabinetWorkItem cabinetWorkItem; - - lock (this.cabinetWorkItems) - { - // check if there are any more cabinets to create - if (0 == this.cabinetWorkItems.Count) - { - break; - } - - cabinetWorkItem = this.cabinetWorkItems.Dequeue(); - } - - // create a cabinet - this.CreateCabinet(cabinetWorkItem); - } - } - catch (WixException we) - { - this.Messaging.Write(we.Error); - } - catch (Exception e) - { - this.Messaging.Write(ErrorMessages.UnexpectedException(e)); - } - } - - /// - /// Creates a cabinet using the wixcab.dll interop layer. - /// - /// CabinetWorkItem containing information about the cabinet to create. - private void CreateCabinet(CabinetWorkItem cabinetWorkItem) - { - this.Messaging.Write(VerboseMessages.CreateCabinet(cabinetWorkItem.CabinetFile)); - - var maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting - ulong maxPreCompressedSizeInBytes = 0; - - if (this.MaximumCabinetSizeForLargeFileSplitting != 0) - { - // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize - // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file - if (1 == cabinetWorkItem.FileFacades.Count()) - { - // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs - // Get the Value for Max Uncompressed Media Size - maxPreCompressedSizeInBytes = (ulong)this.MaximumUncompressedMediaSize * 1024 * 1024; - - var facade = cabinetWorkItem.FileFacades.First(); - - // If the file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting - if ((ulong)facade.FileSize >= maxPreCompressedSizeInBytes) - { - maxCabinetSize = this.MaximumCabinetSizeForLargeFileSplitting; - } - } - } - - // create the cabinet file - var cabinetPath = Path.GetFullPath(cabinetWorkItem.CabinetFile); - - var files = cabinetWorkItem.FileFacades - .OrderBy(f => f.Sequence) - .Select(facade => facade.Hash == null ? - new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix) : - new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4)) - .ToList(); - - var cab = new Cabinet(cabinetPath); - cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold); - - // TODO: Handle newCabNamesCallBackAddress from compression. - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs deleted file mode 100644 index 875b46c2..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs +++ /dev/null @@ -1,132 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CabinetResolver - { - public CabinetResolver(IServiceProvider serviceProvider, string cabCachePath, IEnumerable backendExtensions) - { - this.ServiceProvider = serviceProvider; - - this.CabCachePath = cabCachePath; - - this.BackendExtensions = backendExtensions; - } - - private IServiceProvider ServiceProvider { get; } - - private string CabCachePath { get; } - - private IEnumerable BackendExtensions { get; } - - public IResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable fileFacades) - { - var filesWithPath = fileFacades.Select(this.CreateBindFileWithPath).ToList(); - - IResolvedCabinet resolved = null; - - foreach (var extension in this.BackendExtensions) - { - resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); - - if (null != resolved) - { - return resolved; - } - } - - // By default cabinet should be built and moved to the suggested location. - resolved = this.ServiceProvider.GetService(); - resolved.BuildOption = CabinetBuildOption.BuildAndMove; - resolved.Path = cabinetPath; - - // If a cabinet cache path was provided, change the location for the cabinet - // to be built to and check if there is a cabinet that can be reused. - if (!String.IsNullOrEmpty(this.CabCachePath)) - { - var cabinetName = Path.GetFileName(cabinetPath); - resolved.Path = Path.Combine(this.CabCachePath, cabinetName); - - if (CheckFileExists(resolved.Path)) - { - // Assume that none of the following are true: - // 1. any files are added or removed - // 2. order of files changed or names changed - // 3. modified time changed - var cabinetValid = true; - - var cabinet = new Cabinet(resolved.Path); - var fileList = cabinet.Enumerate(); - - if (filesWithPath.Count() != fileList.Count) - { - cabinetValid = false; - } - else - { - var i = 0; - foreach (var file in filesWithPath) - { - // First check that the file identifiers match because that is quick and easy. - var cabFileInfo = fileList[i]; - cabinetValid = (cabFileInfo.FileId == file.Id); - if (cabinetValid) - { - // Still valid so ensure the file sizes are the same. - var fileInfo = new FileInfo(file.Path); - cabinetValid = (cabFileInfo.Size == fileInfo.Length); - if (cabinetValid) - { - // Still valid so ensure the source time stamp hasn't changed. - cabinetValid = cabFileInfo.SameAsDateTime(fileInfo.LastWriteTime); - } - } - - if (!cabinetValid) - { - break; - } - - i++; - } - } - - resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy; - } - } - - return resolved; - } - - private IBindFileWithPath CreateBindFileWithPath(IFileFacade facade) - { - var result = this.ServiceProvider.GetService(); - result.Id = facade.Id; - result.Path = facade.SourcePath; - - return result; - } - - private static bool CheckFileExists(string path) - { - try - { - return File.Exists(path); - } - catch (ArgumentException) - { - throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs deleted file mode 100644 index 1990ea78..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs +++ /dev/null @@ -1,68 +0,0 @@ -// 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.Bind -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - /// - /// A cabinet builder work item. - /// - internal sealed class CabinetWorkItem - { - /// - /// Instantiate a new CabinetWorkItem. - /// - /// The collection of files in this cabinet. - /// The cabinet file. - /// Maximum threshold for each cabinet. - /// The compression level of the cabinet. - /// Modularization suffix used when building a Merge Module. - /// - public CabinetWorkItem(IEnumerable fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix /*, BinderFileManager binderFileManager*/) - { - this.CabinetFile = cabinetFile; - this.CompressionLevel = compressionLevel; - this.ModularizationSuffix = modularizationSuffix; - this.FileFacades = fileFacades; - //this.BinderFileManager = binderFileManager; - this.MaxThreshold = maxThreshold; - } - - /// - /// Gets the cabinet file. - /// - /// The cabinet file. - public string CabinetFile { get; } - - /// - /// Gets the compression level of the cabinet. - /// - /// The compression level of the cabinet. - public CompressionLevel CompressionLevel { get; } - - /// - /// Gets the modularization suffix used when building a Merge Module. - /// - public string ModularizationSuffix { get; } - - /// - /// Gets the collection of files in this cabinet. - /// - /// The collection of files in this cabinet. - public IEnumerable FileFacades { get; } - - // - // Gets the binder file manager. - // - // The binder file manager. - //public BinderFileManager BinderFileManager { get; private set; } - - /// - /// Gets the max threshold. - /// - /// The maximum threshold for a folder in a cabinet. - public int MaxThreshold { get; } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs deleted file mode 100644 index 83a4949e..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs +++ /dev/null @@ -1,455 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Creates cabinet files. - /// - internal class CreateCabinetsCommand - { - public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB - public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) - - private readonly List fileTransfers; - - private readonly List trackedFiles; - - private readonly FileSplitCabNamesCallback newCabNamesCallBack; - - private Dictionary lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence - - public CreateCabinetsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, WixMediaTemplateSymbol mediaTemplate) - { - this.fileTransfers = new List(); - - this.trackedFiles = new List(); - - this.newCabNamesCallBack = this.NewCabNamesCallBack; - - this.ServiceProvider = serviceProvider; - - this.BackendHelper = backendHelper; - - this.MediaTemplate = mediaTemplate; - } - - private IServiceProvider ServiceProvider { get; } - - private IBackendHelper BackendHelper { get; } - - private WixMediaTemplateSymbol MediaTemplate { get; } - - /// - /// Sets the number of threads to use for cabinet creation. - /// - public int CabbingThreadCount { private get; set; } - - public string CabCachePath { private get; set; } - - public IMessaging Messaging { private get; set; } - - public string IntermediateFolder { private get; set; } - - /// - /// Sets the default compression level to use for cabinets - /// that don't have their compression level explicitly set. - /// - public CompressionLevel? DefaultCompressionLevel { private get; set; } - - public IEnumerable BackendExtensions { private get; set; } - - public WindowsInstallerData Data { private get; set; } - - public string LayoutDirectory { private get; set; } - - public bool Compressed { private get; set; } - - public string ModularizationSuffix { private get; set; } - - public Dictionary> FileFacadesByCabinet { private get; set; } - - public Func ResolveMedia { private get; set; } - - public TableDefinitionCollection TableDefinitions { private get; set; } - - public IEnumerable FileTransfers => this.fileTransfers; - - public IEnumerable TrackedFiles => this.trackedFiles; - - public void Execute() - { - this.lastCabinetAddedToMediaTable = new Dictionary(); - - // If the cabbing thread count wasn't provided, default the number of cabbing threads to the number of processors. - if (this.CabbingThreadCount <= 0) - { - this.CabbingThreadCount = this.CalculateCabbingThreadCount(); - - this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); - } - - // Send Binder object to Facilitate NewCabNamesCallBack Callback - var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); - - // Supply Compile MediaTemplate Attributes to Cabinet Builder - this.GetMediaTemplateAttributes(out var maximumCabinetSizeForLargeFileSplitting, out var maximumUncompressedMediaSize); - cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting; - cabinetBuilder.MaximumUncompressedMediaSize = maximumUncompressedMediaSize; - - foreach (var entry in this.FileFacadesByCabinet) - { - var mediaSymbol = entry.Key; - var files = entry.Value; - var compressionLevel = mediaSymbol.CompressionLevel ?? this.DefaultCompressionLevel ?? CompressionLevel.Medium; - var cabinetDir = this.ResolveMedia(mediaSymbol, mediaSymbol.Layout, this.LayoutDirectory); - - var cabinetWorkItem = this.CreateCabinetWorkItem(this.Data, cabinetDir, mediaSymbol, compressionLevel, files); - if (null != cabinetWorkItem) - { - cabinetBuilder.Enqueue(cabinetWorkItem); - } - } - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return; - } - - // create queued cabinets with multiple threads - cabinetBuilder.CreateQueuedCabinets(); - if (this.Messaging.EncounteredError) - { - return; - } - } - - private int CalculateCabbingThreadCount() - { - var cabbingThreadCount = Environment.ProcessorCount; - - if (cabbingThreadCount <= 0) - { - cabbingThreadCount = 1; // reset to 1 when the environment variable is invalid. - - this.Messaging.Write(WarningMessages.InvalidEnvironmentVariable("NUMBER_OF_PROCESSORS", Environment.ProcessorCount.ToString(), cabbingThreadCount.ToString())); - } - - return cabbingThreadCount; - } - - /// - /// Creates a work item to create a cabinet. - /// - /// Windows Installer data for the current database. - /// Directory to create cabinet in. - /// Media symbol containing information about the cabinet. - /// Desired compression level. - /// Collection of files in this cabinet. - /// created CabinetWorkItem object - private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable fileFacades) - { - CabinetWorkItem cabinetWorkItem = null; - var tempCabinetFileX = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet); - - // check for an empty cabinet - if (!fileFacades.Any()) - { - // Remove the leading '#' from the embedded cabinet name to make the warning easier to understand - var cabinetName = mediaSymbol.Cabinet.TrimStart('#'); - - // If building a patch, remind them to run -p for torch. - if (OutputType.Patch == data.Type) - { - this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, true)); - } - else - { - this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName)); - } - } - - var cabinetResolver = new CabinetResolver(this.ServiceProvider, this.CabCachePath, this.BackendExtensions); - - var resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades); - - // create a cabinet work item if it's not being skipped - if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) - { - // Default to the threshold for best smartcabbing (makes smallest cabinet). - cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold: 0, compressionLevel, this.ModularizationSuffix /*, this.FileManager*/); - } - else // reuse the cabinet from the cabinet cache. - { - this.Messaging.Write(VerboseMessages.ReusingCabCache(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet, resolvedCabinet.Path)); - - try - { - // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The - // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that - // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from - // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output) - // causing the project to look like it perpetually needs a rebuild until all of the reused - // cabinets get newer timestamps. - File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now); - } - catch (Exception e) - { - this.Messaging.Write(WarningMessages.CannotUpdateCabCache(mediaSymbol.SourceLineNumbers, resolvedCabinet.Path, e.Message)); - } - } - - var trackResolvedCabinet = this.BackendHelper.TrackFile(resolvedCabinet.Path, TrackedFileType.Intermediate, mediaSymbol.SourceLineNumbers); - this.trackedFiles.Add(trackResolvedCabinet); - - if (mediaSymbol.Cabinet.StartsWith("#", StringComparison.Ordinal)) - { - var streamsTable = data.EnsureTable(this.TableDefinitions["_Streams"]); - - var streamRow = streamsTable.CreateRow(mediaSymbol.SourceLineNumbers); - streamRow[0] = mediaSymbol.Cabinet.Substring(1); - streamRow[1] = resolvedCabinet.Path; - } - else - { - var trackDestination = this.BackendHelper.TrackFile(Path.Combine(cabinetDir, mediaSymbol.Cabinet), TrackedFileType.Final, mediaSymbol.SourceLineNumbers); - this.trackedFiles.Add(trackDestination); - - var transfer = this.BackendHelper.CreateFileTransfer(resolvedCabinet.Path, trackDestination.Path, resolvedCabinet.BuildOption == CabinetBuildOption.BuildAndMove, mediaSymbol.SourceLineNumbers); - this.fileTransfers.Add(transfer); - } - - return cabinetWorkItem; - } - - //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable fileFacades) - //{ - // ResolvedCabinet resolved = null; - - // List filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); - - // foreach (var extension in this.BackendExtensions) - // { - // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); - // if (null != resolved) - // { - // break; - // } - // } - - // return resolved; - //} - - /// - /// Delegate for Cabinet Split Callback - /// - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken); - - /// - /// Call back to Add File Transfer for new Cab and add new Cab to Media table - /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe - /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored - /// - /// The name of splitting cabinet without extention e.g. "cab1". - /// The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab" - /// The file token of the first file present in the splitting cabinet - internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabinetName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) - { - throw new NotImplementedException(); -#if TODO_CAB_SPANNING - // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads - var mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); - try - { - if (!mutex.WaitOne(0, false)) // Check if you can get the lock - { - // Cound not get the Lock - this.Messaging.Write(VerboseMessages.CabinetsSplitInParallel()); - mutex.WaitOne(); // Wait on other thread - } - - var firstCabinetName = firstCabName + ".cab"; - var transferAdded = false; // Used for Error Handling - - // Create File Transfer for new Cabinet using transfer of Base Cabinet - foreach (var transfer in this.FileTransfers) - { - if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) - { - var newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); - var newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); - - var trackSource = this.BackendHelper.TrackFile(newCabSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers); - this.trackedFiles.Add(trackSource); - - var trackTarget = this.BackendHelper.TrackFile(newCabTargetPath, TrackedFileType.Final, transfer.SourceLineNumbers); - this.trackedFiles.Add(trackTarget); - - var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers); - this.fileTransfers.Add(newTransfer); - - transferAdded = true; - break; - } - } - - // Check if File Transfer was added - if (!transferAdded) - { - throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); - } - - // Add the new Cabinets to media table using LastSequence of Base Cabinet - var mediaTable = this.Output.Tables["Media"]; - var wixFileTable = this.Output.Tables["WixFile"]; - var diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain - var lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain - var lastSplitCabinetFound = false; // Used for Error Handling - - var lastCabinetOfThisSequence = String.Empty; - // Get the Value of Last Cabinet Added in this split Sequence from Dictionary - if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence)) - { - // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence - lastCabinetOfThisSequence = firstCabinetName; - } - - foreach (MediaRow mediaRow in mediaTable.Rows) - { - // Get details for the Last Cabinet Added in this Split Sequence - if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) - { - lastSequenceForLastSplitCabAdded = mediaRow.LastSequence; - diskIDForLastSplitCabAdded = mediaRow.DiskId; - lastSplitCabinetFound = true; - } - - // Check for Name Collision for the new Cabinet added - if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) - { - // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row - throw new WixException(ErrorMessages.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); - } - } - - // Check if the last Split Cabinet was found in the Media Table - if (!lastSplitCabinetFound) - { - throw new WixException(ErrorMessages.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence)); - } - - // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort - // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with - // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction - MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null); - newMediaRow.Cabinet = newCabinetName; - newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion - newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded; - - // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique - foreach (MediaRow mediaRow in mediaTable.Rows) - { - // Check if this row comes after inserted row and it is not the new cabinet inserted row - if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) - { - mediaRow.DiskId++; // Increment DiskID - } - } - - // Now Increment DiskID for All files Rows so that they refer to the right Media Row - foreach (WixFileRow wixFileRow in wixFileTable.Rows) - { - // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet - // This check will work as we have only one large file in every splitting cabinet - // If we want to support splitting cabinet with more large files we need to update this code - if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase)) - { - wixFileRow.DiskId++; // Increment DiskID - } - } - - // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback - this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName; - - mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails - } - finally - { - // Releasing the Mutex here - mutex.ReleaseMutex(); - } -#endif - } - - - /// - /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides - /// - private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) - { - // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size - var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); - var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); - - // Supply Compile MediaTemplate Attributes to Cabinet Builder - if (this.MediaTemplate != null) - { - // Get the Value for Max Cab Size for File Splitting - var maxCabSizeForLargeFileInMB = 0; - try - { - // Override authored mcslfs value if environment variable is authored. - maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : this.MediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting; - - var testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; - maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; - } - catch (FormatException) - { - throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString)); - } - catch (OverflowException) - { - throw new WixException(ErrorMessages.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, MaxValueOfMaxCabSizeForLargeFileSplitting)); - } - - var maxPreCompressedSizeInMB = 0; - try - { - // Override authored mums value if environment variable is authored. - maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : this.MediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; - - var testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; - maxUncompressedMediaSize = maxPreCompressedSizeInMB; - } - catch (FormatException) - { - throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); - } - catch (OverflowException) - { - throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB)); - } - } - else - { - maxCabSizeForLargeFileSplitting = 0; - maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs deleted file mode 100644 index 47d8399f..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs +++ /dev/null @@ -1,81 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - - /// - /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. - /// - internal class CreateDeltaPatchesCommand - { - public CreateDeltaPatchesCommand(List fileFacades, string intermediateFolder, WixPatchSymbol wixPatchId) - { - this.FileFacades = fileFacades; - this.IntermediateFolder = intermediateFolder; - this.WixPatchId = wixPatchId; - } - - private IEnumerable FileFacades { get; } - - private WixPatchSymbol WixPatchId { get; } - - private string IntermediateFolder { get; } - - public void Execute() - { - var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false; - var apiPatchingSymbolFlags = (PatchSymbolFlags)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0); - -#if TODO_PATCHING_DELTA - foreach (FileFacade facade in this.FileFacades) - { - if (RowOperation.Modify == facade.File.Operation && - 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile)) - { - string deltaBase = String.Concat("delta_", facade.File.File); - string deltaFile = Path.Combine(this.IntermediateFolder, String.Concat(deltaBase, ".dpf")); - string headerFile = Path.Combine(this.IntermediateFolder, String.Concat(deltaBase, ".phd")); - - bool retainRangeWarning = false; - - if (PatchAPI.PatchInterop.CreateDelta( - deltaFile, - facade.WixFile.Source, - facade.DeltaPatchFile.Symbols, - facade.DeltaPatchFile.RetainOffsets, - new[] { facade.WixFile.PreviousSource }, - facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }), - facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }), - facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }), - facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }), - facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }), - apiPatchingSymbolFlags, - optimizePatchSizeForLargeFiles, - out retainRangeWarning)) - { - PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile); - - facade.WixFile.Source = deltaFile; - facade.WixFile.DeltaPatchHeaderSource = headerFile; - } - - if (retainRangeWarning) - { - // TODO: get patch family to add to warning message for PatchWiz parity. - Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File)); - } - } - } -#endif - - throw new NotImplementedException(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs deleted file mode 100644 index ff03413c..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs +++ /dev/null @@ -1,250 +0,0 @@ -// 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.Bind -{ - using System; - using System.Globalization; - using System.IO; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class CreateIdtFileCommand - { - public CreateIdtFileCommand(IMessaging messaging, Table table, int codepage, string intermediateFolder, bool keepAddedColumns) - { - this.Messaging = messaging; - this.Table = table; - this.Codepage = codepage; - this.IntermediateFolder = intermediateFolder; - this.KeepAddedColumns = keepAddedColumns; - } - - private IMessaging Messaging { get; } - - private Table Table { get; } - - private int Codepage { get; set; } - - private string IntermediateFolder { get; } - - private bool KeepAddedColumns { get; } - - public string IdtPath { get; private set; } - - public void Execute() - { - // write out the table to an IDT file - var encoding = GetCodepageEncoding(this.Codepage); - - this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt")); - - using (var idtWriter = new StreamWriter(this.IdtPath, false, encoding)) - { - this.TableToIdtDefinition(this.Table, idtWriter, this.KeepAddedColumns); - } - } - - private void TableToIdtDefinition(Table table, StreamWriter writer, bool keepAddedColumns) - { - if (table.Definition.Unreal) - { - return; - } - - if (TableDefinition.MaxColumnsInRealTable < table.Definition.Columns.Length) - { - throw new WixException(ErrorMessages.TooManyColumnsInRealTable(table.Definition.Name, table.Definition.Columns.Length, TableDefinition.MaxColumnsInRealTable)); - } - - // Tack on the table header, and flush before we start writing bytes directly to the stream. - var header = this.TableDefinitionToIdtDefinition(table.Definition, keepAddedColumns); - writer.Write(header); - writer.Flush(); - - using (var binary = new BinaryWriter(writer.BaseStream, writer.Encoding, true)) - { - // Create an encoding that replaces characters with question marks, and doesn't throw. We'll - // use this in case of errors - Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage); - - foreach (Row row in table.Rows) - { - if (row.Redundant) - { - continue; - } - - string rowString = this.RowToIdtDefinition(row, keepAddedColumns); - byte[] rowBytes; - - try - { - // GetBytes will throw an exception if any character doesn't match our current encoding - rowBytes = writer.Encoding.GetBytes(rowString); - } - catch (EncoderFallbackException) - { - this.Messaging.Write(ErrorMessages.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture))); - - rowBytes = convertEncoding.GetBytes(rowString); - } - - binary.Write(rowBytes, 0, rowBytes.Length); - } - } - } - - private string TableDefinitionToIdtDefinition(TableDefinition definition, bool keepAddedColumns) - { - var first = true; - var columnString = new StringBuilder(); - var dataString = new StringBuilder(); - var tableString = new StringBuilder(); - - tableString.Append(definition.Name); - foreach (var column in definition.Columns) - { - // Conditionally keep columns added in a transform; otherwise, - // break because columns can only be added at the end. - if (column.Added && !keepAddedColumns) - { - break; - } - - if (column.Unreal) - { - continue; - } - - if (!first) - { - columnString.Append('\t'); - dataString.Append('\t'); - } - - columnString.Append(column.Name); - dataString.Append(ColumnIdtType(column)); - - if (column.PrimaryKey) - { - tableString.AppendFormat("\t{0}", column.Name); - } - - first = false; - } - columnString.Append("\r\n"); - columnString.Append(dataString); - columnString.Append("\r\n"); - columnString.Append(tableString); - columnString.Append("\r\n"); - - return columnString.ToString(); - } - - private string RowToIdtDefinition(Row row, bool keepAddedColumns) - { - var first = true; - var sb = new StringBuilder(); - - foreach (var field in row.Fields) - { - // Conditionally keep columns added in a transform; otherwise, - // break because columns can only be added at the end. - if (field.Column.Added && !keepAddedColumns) - { - break; - } - - if (field.Column.Unreal) - { - continue; - } - - if (first) - { - first = false; - } - else - { - sb.Append('\t'); - } - - sb.Append(this.FieldToIdtValue(field)); - } - sb.Append("\r\n"); - - return sb.ToString(); - } - - private string FieldToIdtValue(Field field) - { - var data = field.AsString(); - - if (String.IsNullOrEmpty(data)) - { - return data; - } - - // Special field value idt-specific escaping. - return data.Replace('\t', '\x10') - .Replace('\r', '\x11') - .Replace('\n', '\x19'); - } - - private static Encoding GetCodepageEncoding(int codepage) - { - Encoding encoding; - - // If UTF8 encoding, use the UTF8-specific constructor to avoid writing - // the byte order mark at the beginning of the file - if (codepage == Encoding.UTF8.CodePage) - { - encoding = new UTF8Encoding(false, true); - } - else - { - if (codepage == 0) - { - codepage = Encoding.ASCII.CodePage; - } - - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - - encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); - } - - return encoding; - } - - /// - /// Gets the type of the column in IDT format. - /// - /// IDT format for column type. - private static string ColumnIdtType(ColumnDefinition column) - { - char typeCharacter; - switch (column.Type) - { - case ColumnType.Number: - typeCharacter = column.Nullable ? 'I' : 'i'; - break; - case ColumnType.Preserved: - case ColumnType.String: - typeCharacter = column.Nullable ? 'S' : 's'; - break; - case ColumnType.Localized: - typeCharacter = column.Nullable ? 'L' : 'l'; - break; - case ColumnType.Object: - typeCharacter = column.Nullable ? 'V' : 'v'; - break; - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_UnknownColumnType, column.Type)); - } - - return String.Concat(typeCharacter, column.Length); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs deleted file mode 100644 index d0e25571..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs +++ /dev/null @@ -1,260 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Services; - - internal class CreateInstanceTransformsCommand - { - public CreateInstanceTransformsCommand(IntermediateSection section, WindowsInstallerData output, TableDefinitionCollection tableDefinitions, IBackendHelper backendHelper) - { - this.Section = section; - this.Output = output; - this.TableDefinitions = tableDefinitions; - this.BackendHelper = backendHelper; - } - - private IntermediateSection Section { get; } - - private WindowsInstallerData Output { get; } - - public TableDefinitionCollection TableDefinitions { get; } - - private IBackendHelper BackendHelper { get; } - - public void Execute() - { - // Create and add substorages for instance transforms. - var wixInstanceTransformsSymbols = this.Section.Symbols.OfType(); - - if (wixInstanceTransformsSymbols.Any()) - { - string targetProductCode = null; - string targetUpgradeCode = null; - string targetProductVersion = null; - - var targetSummaryInformationTable = this.Output.Tables["_SummaryInformation"]; - var targetPropertyTable = this.Output.Tables["Property"]; - - // Get the data from target database - foreach (var propertyRow in targetPropertyTable.Rows) - { - if ("ProductCode" == (string)propertyRow[0]) - { - targetProductCode = (string)propertyRow[1]; - } - else if ("ProductVersion" == (string)propertyRow[0]) - { - targetProductVersion = (string)propertyRow[1]; - } - else if ("UpgradeCode" == (string)propertyRow[0]) - { - targetUpgradeCode = (string)propertyRow[1]; - } - } - - // Index the Instance Component Rows, we'll get the Components rows from the real Component table. - var targetInstanceComponentTable = this.Section.Symbols.OfType(); - var instanceComponentGuids = targetInstanceComponentTable.ToDictionary(t => t.Id.Id, t => (ComponentRow)null); - - if (instanceComponentGuids.Any()) - { - var targetComponentTable = this.Output.Tables["Component"]; - foreach (ComponentRow componentRow in targetComponentTable.Rows) - { - var component = (string)componentRow[0]; - if (instanceComponentGuids.ContainsKey(component)) - { - instanceComponentGuids[component] = componentRow; - } - } - } - - // Generate the instance transforms - foreach (var instanceSymbol in wixInstanceTransformsSymbols) - { - var instanceId = instanceSymbol.Id.Id; - - var instanceTransform = new WindowsInstallerData(instanceSymbol.SourceLineNumbers); - instanceTransform.Type = OutputType.Transform; - instanceTransform.Codepage = this.Output.Codepage; - - var instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - string targetPlatformAndLanguage = null; - - foreach (var summaryInformationRow in targetSummaryInformationTable.Rows) - { - if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE - { - targetPlatformAndLanguage = (string)summaryInformationRow[1]; - } - - // Copy the row's data to the transform. - var copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(summaryInformationRow.SourceLineNumbers); - copyOfSummaryRow[0] = summaryInformationRow[0]; - copyOfSummaryRow[1] = summaryInformationRow[1]; - } - - // Modify the appropriate properties. - var propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); - - // Change the ProductCode property - var productCode = instanceSymbol.ProductCode; - if ("*" == productCode) - { - productCode = this.BackendHelper.CreateGuid(); - } - - var productCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); - productCodeRow.Operation = RowOperation.Modify; - productCodeRow.Fields[1].Modified = true; - productCodeRow[0] = "ProductCode"; - productCodeRow[1] = productCode; - - // Change the instance property - var instanceIdRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); - instanceIdRow.Operation = RowOperation.Modify; - instanceIdRow.Fields[1].Modified = true; - instanceIdRow[0] = instanceSymbol.PropertyId; - instanceIdRow[1] = instanceId; - - if (!String.IsNullOrEmpty(instanceSymbol.ProductName)) - { - // Change the ProductName property - var productNameRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); - productNameRow.Operation = RowOperation.Modify; - productNameRow.Fields[1].Modified = true; - productNameRow[0] = "ProductName"; - productNameRow[1] = instanceSymbol.ProductName; - } - - if (!String.IsNullOrEmpty(instanceSymbol.UpgradeCode)) - { - // Change the UpgradeCode property - var upgradeCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); - upgradeCodeRow.Operation = RowOperation.Modify; - upgradeCodeRow.Fields[1].Modified = true; - upgradeCodeRow[0] = "UpgradeCode"; - upgradeCodeRow[1] = instanceSymbol.UpgradeCode; - - // Change the Upgrade table - var targetUpgradeTable = this.Output.Tables["Upgrade"]; - if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) - { - var upgradeId = instanceSymbol.UpgradeCode; - var upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); - foreach (var row in targetUpgradeTable.Rows) - { - // In case they are upgrading other codes to this new product, leave the ones that don't match the - // Product.UpgradeCode intact. - if (targetUpgradeCode == (string)row[0]) - { - var upgradeRow = upgradeTable.CreateRow(row.SourceLineNumbers); - upgradeRow.Operation = RowOperation.Add; - upgradeRow.Fields[0].Modified = true; - // I was hoping to be able to RowOperation.Modify, but that didn't appear to function. - // upgradeRow.Fields[0].PreviousData = (string)row[0]; - - // Inserting a new Upgrade record with the updated UpgradeCode - upgradeRow[0] = upgradeId; - upgradeRow[1] = row[1]; - upgradeRow[2] = row[2]; - upgradeRow[3] = row[3]; - upgradeRow[4] = row[4]; - upgradeRow[5] = row[5]; - upgradeRow[6] = row[6]; - - // Delete the old row - var upgradeRemoveRow = upgradeTable.CreateRow(row.SourceLineNumbers); - upgradeRemoveRow.Operation = RowOperation.Delete; - upgradeRemoveRow[0] = row[0]; - upgradeRemoveRow[1] = row[1]; - upgradeRemoveRow[2] = row[2]; - upgradeRemoveRow[3] = row[3]; - upgradeRemoveRow[4] = row[4]; - upgradeRemoveRow[5] = row[5]; - upgradeRemoveRow[6] = row[6]; - } - } - } - } - - // If there are instance Components generate new GUIDs for them. - if (0 < instanceComponentGuids.Count) - { - var componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); - foreach (var targetComponentRow in instanceComponentGuids.Values) - { - var guid = targetComponentRow.Guid; - if (!String.IsNullOrEmpty(guid)) - { - var instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); - instanceComponentRow.Operation = RowOperation.Modify; - instanceComponentRow.Fields[1].Modified = true; - instanceComponentRow[0] = targetComponentRow[0]; - instanceComponentRow[1] = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)); - instanceComponentRow[2] = targetComponentRow[2]; - instanceComponentRow[3] = targetComponentRow[3]; - instanceComponentRow[4] = targetComponentRow[4]; - instanceComponentRow[5] = targetComponentRow[5]; - } - } - } - - // Update the summary information - var summaryRows = new Dictionary(instanceSummaryInformationTable.Rows.Count); - foreach (var row in instanceSummaryInformationTable.Rows) - { - summaryRows[(int)row[0]] = row; - - if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) - { - row[1] = targetPlatformAndLanguage; - } - else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) - { - row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode); - } - else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) - { - row[1] = 0; - } - else if ((int)SummaryInformation.Transform.Security == (int)row[0]) - { - row[1] = "4"; - } - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) - { - var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); - summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; - summaryRow[1] = targetPlatformAndLanguage; - } - else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) - { - var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); - summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; - summaryRow[1] = "0"; - } - else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) - { - var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); - summaryRow[0] = (int)SummaryInformation.Transform.Security; - summaryRow[1] = "4"; - } - - this.Output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs deleted file mode 100644 index 5c993f63..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs +++ /dev/null @@ -1,93 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class CreatePatchTransformsCommand - { - public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, string intermediateFolder) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.Intermediate = intermediate; - this.IntermediateFolder = intermediateFolder; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private Intermediate Intermediate { get; } - - private string IntermediateFolder { get; } - - public IEnumerable PatchTransforms { get; private set; } - - public IEnumerable Execute() - { - var patchTransforms = new List(); - - var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).OfType(); - - foreach (var symbol in symbols) - { - WindowsInstallerData transform; - - if (symbol.TransformFile is null) - { - var baselineData = this.GetData(symbol.BaselineFile.Path); - var updateData = this.GetData(symbol.UpdateFile.Path); - - var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false); - transform = command.Execute(); - } - else - { - var exportBasePath = Path.Combine(this.IntermediateFolder, "_trans"); // TODO: come up with a better path. - - var command = new UnbindTransformCommand(this.Messaging, this.BackendHelper, symbol.TransformFile.Path, exportBasePath, this.IntermediateFolder); - transform = command.Execute(); - } - - patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform)); - } - - this.PatchTransforms = patchTransforms; - - return this.PatchTransforms; - } - - private WindowsInstallerData GetData(string path) - { - var ext = Path.GetExtension(path); - - if (".msi".Equals(ext, StringComparison.OrdinalIgnoreCase)) - { - using (var database = new Database(path, OpenDatabase.ReadOnly)) - { - var exportBasePath = Path.Combine(this.IntermediateFolder, "_msi"); // TODO: come up with a better path. - - var isAdminImage = false; // TODO: need a better way to set this - - var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, database, path, OutputType.Product, exportBasePath, this.IntermediateFolder, isAdminImage, suppressDemodularization: true, skipSummaryInfo: true); - return command.Execute(); - } - } - else // assume .wixpdb (or .wixout) - { - var data = WindowsInstallerData.Load(path, true); - return data; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs deleted file mode 100644 index ba7c03a0..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class CreateSpecialPropertiesCommand - { - public CreateSpecialPropertiesCommand(IntermediateSection section) - { - this.Section = section; - } - - private IntermediateSection Section { get; } - - public void Execute() - { - // Create lists of the properties that contribute to the special lists of properties. - var adminProperties = new SortedSet(); - var secureProperties = new SortedSet(); - var hiddenProperties = new SortedSet(); - - foreach (var wixPropertyRow in this.Section.Symbols.OfType()) - { - if (wixPropertyRow.Admin) - { - adminProperties.Add(wixPropertyRow.PropertyRef); - } - - if (wixPropertyRow.Hidden) - { - hiddenProperties.Add(wixPropertyRow.PropertyRef); - } - - if (wixPropertyRow.Secure) - { - secureProperties.Add(wixPropertyRow.PropertyRef); - } - } - - // Hide properties for in-script custom actions that have HideTarget set. - var hideTargetCustomActions = this.Section.Symbols.OfType().Where( - ca => ca.Hidden - && (ca.ExecutionType == CustomActionExecutionType.Deferred - || ca.ExecutionType == CustomActionExecutionType.Commit - || ca.ExecutionType == CustomActionExecutionType.Rollback)) - .Select(ca => ca.Id.Id); - hiddenProperties.UnionWith(hideTargetCustomActions); - - // Ensure upgrade action properties are secure. - var actionProperties = this.Section.Symbols.OfType().Select(u => u.ActionProperty); - secureProperties.UnionWith(actionProperties); - - if (0 < adminProperties.Count) - { - this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "AdminProperties")) - { - Value = String.Join(";", adminProperties), - }); - } - - if (0 < secureProperties.Count) - { - this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "SecureCustomProperties")) - { - Value = String.Join(";", secureProperties), - }); - } - - if (0 < hiddenProperties.Count) - { - this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "MsiHiddenProperties")) - { - Value = String.Join(";", hiddenProperties) - }); - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs deleted file mode 100644 index d34ca3fe..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs +++ /dev/null @@ -1,1621 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class CreateWindowsInstallerDataFromIRCommand - { - private static readonly char[] PathSeparatorChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - - public CreateWindowsInstallerDataFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, int codepage, IEnumerable backendExtensions, IWindowsInstallerBackendHelper backendHelper) - { - this.Messaging = messaging; - this.Section = section; - this.TableDefinitions = tableDefinitions; - this.Codepage = codepage; - this.BackendExtensions = backendExtensions; - this.BackendHelper = backendHelper; - this.GeneratedShortNames = new Dictionary>(); - } - - private IEnumerable BackendExtensions { get; } - - private IWindowsInstallerBackendHelper BackendHelper { get; } - - private IMessaging Messaging { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - private int Codepage { get; } - - private IntermediateSection Section { get; } - - private Dictionary> GeneratedShortNames { get; } - - public WindowsInstallerData Data { get; private set; } - - public WindowsInstallerData Execute() - { - this.Data = new WindowsInstallerData(this.Section.Symbols.First().SourceLineNumbers) - { - Codepage = this.Codepage, - Type = SectionTypeToOutputType(this.Section.Type) - }; - - this.AddSectionToData(); - - return this.Data; - } - - private void AddSectionToData() - { - var cellsByTableAndRowId = new Dictionary>(); - - foreach (var symbol in this.Section.Symbols) - { - var unknownSymbol = false; - switch (symbol.Definition.Type) - { - case SymbolDefinitionType.AppSearch: - this.AddSymbolDefaultly(symbol); - this.Data.EnsureTable(this.TableDefinitions["Signature"]); - break; - - case SymbolDefinitionType.Assembly: - this.AddAssemblySymbol((AssemblySymbol)symbol); - break; - - case SymbolDefinitionType.BBControl: - this.AddBBControlSymbol((BBControlSymbol)symbol); - break; - - case SymbolDefinitionType.Class: - this.AddClassSymbol((ClassSymbol)symbol); - break; - - case SymbolDefinitionType.Control: - this.AddControlSymbol((ControlSymbol)symbol); - break; - - case SymbolDefinitionType.ControlEvent: - this.AddControlEventSymbol((ControlEventSymbol)symbol); - break; - - case SymbolDefinitionType.Component: - this.AddComponentSymbol((ComponentSymbol)symbol); - break; - - case SymbolDefinitionType.CustomAction: - this.AddCustomActionSymbol((CustomActionSymbol)symbol); - break; - - case SymbolDefinitionType.Dialog: - this.AddDialogSymbol((DialogSymbol)symbol); - break; - - case SymbolDefinitionType.Directory: - this.AddDirectorySymbol((DirectorySymbol)symbol); - break; - - case SymbolDefinitionType.DuplicateFile: - this.AddDuplicateFileSymbol((DuplicateFileSymbol)symbol); - break; - - case SymbolDefinitionType.Environment: - this.AddEnvironmentSymbol((EnvironmentSymbol)symbol); - break; - - case SymbolDefinitionType.Error: - this.AddErrorSymbol((ErrorSymbol)symbol); - break; - - case SymbolDefinitionType.Feature: - this.AddFeatureSymbol((FeatureSymbol)symbol); - break; - - case SymbolDefinitionType.File: - this.AddFileSymbol((FileSymbol)symbol); - break; - - case SymbolDefinitionType.IniFile: - this.AddIniFileSymbol((IniFileSymbol)symbol); - break; - - case SymbolDefinitionType.IniLocator: - this.AddIniLocatorSymbol((IniLocatorSymbol)symbol); - break; - - case SymbolDefinitionType.Media: - this.AddMediaSymbol((MediaSymbol)symbol); - break; - - case SymbolDefinitionType.ModuleConfiguration: - this.AddModuleConfigurationSymbol((ModuleConfigurationSymbol)symbol); - this.EnsureModuleIgnoredTable(symbol, "ModuleConfiguration"); - break; - - case SymbolDefinitionType.ModuleSubstitution: - this.EnsureModuleIgnoredTable(symbol, "ModuleSubstitution"); - break; - - case SymbolDefinitionType.MsiEmbeddedUI: - this.AddMsiEmbeddedUISymbol((MsiEmbeddedUISymbol)symbol); - break; - - case SymbolDefinitionType.MsiServiceConfig: - this.AddMsiServiceConfigSymbol((MsiServiceConfigSymbol)symbol); - break; - - case SymbolDefinitionType.MsiServiceConfigFailureActions: - this.AddMsiServiceConfigFailureActionsSymbol((MsiServiceConfigFailureActionsSymbol)symbol); - break; - - case SymbolDefinitionType.MoveFile: - this.AddMoveFileSymbol((MoveFileSymbol)symbol); - break; - - case SymbolDefinitionType.ProgId: - this.AddSymbolDefaultly(symbol); - this.Data.EnsureTable(this.TableDefinitions["Extension"]); - break; - - case SymbolDefinitionType.Property: - this.AddPropertySymbol((PropertySymbol)symbol); - break; - - case SymbolDefinitionType.RemoveFile: - this.AddRemoveFileSymbol((RemoveFileSymbol)symbol); - break; - - case SymbolDefinitionType.Registry: - this.AddRegistrySymbol((RegistrySymbol)symbol); - break; - - case SymbolDefinitionType.RegLocator: - this.AddRegLocatorSymbol((RegLocatorSymbol)symbol); - break; - - case SymbolDefinitionType.RemoveRegistry: - this.AddRemoveRegistrySymbol((RemoveRegistrySymbol)symbol); - break; - - case SymbolDefinitionType.ServiceControl: - this.AddServiceControlSymbol((ServiceControlSymbol)symbol); - break; - - case SymbolDefinitionType.ServiceInstall: - this.AddServiceInstallSymbol((ServiceInstallSymbol)symbol); - break; - - case SymbolDefinitionType.Shortcut: - this.AddShortcutSymbol((ShortcutSymbol)symbol); - break; - - case SymbolDefinitionType.TextStyle: - this.AddTextStyleSymbol((TextStyleSymbol)symbol); - break; - - case SymbolDefinitionType.Upgrade: - this.AddUpgradeSymbol((UpgradeSymbol)symbol); - break; - - case SymbolDefinitionType.WixAction: - this.AddWixActionSymbol((WixActionSymbol)symbol); - break; - - case SymbolDefinitionType.WixCustomTableCell: - this.IndexCustomTableCellSymbol((WixCustomTableCellSymbol)symbol, cellsByTableAndRowId); - break; - - case SymbolDefinitionType.WixEnsureTable: - this.AddWixEnsureTableSymbol((WixEnsureTableSymbol)symbol); - break; - - case SymbolDefinitionType.WixPackage: - this.AddWixPackageSymbol((WixPackageSymbol)symbol); - break; - - // Symbols used internally and are not added to the output. - case SymbolDefinitionType.WixBuildInfo: - case SymbolDefinitionType.WixBindUpdatedFiles: - case SymbolDefinitionType.WixComponentGroup: - case SymbolDefinitionType.WixComplexReference: - case SymbolDefinitionType.WixDeltaPatchFile: - case SymbolDefinitionType.WixDeltaPatchSymbolPaths: - case SymbolDefinitionType.WixFragment: - case SymbolDefinitionType.WixFeatureGroup: - case SymbolDefinitionType.WixInstanceComponent: - case SymbolDefinitionType.WixInstanceTransforms: - case SymbolDefinitionType.WixFeatureModules: - case SymbolDefinitionType.WixGroup: - case SymbolDefinitionType.WixMediaTemplate: - case SymbolDefinitionType.WixMerge: - case SymbolDefinitionType.WixOrdering: - case SymbolDefinitionType.WixPatchBaseline: - case SymbolDefinitionType.WixPatchFamilyGroup: - case SymbolDefinitionType.WixPatch: - case SymbolDefinitionType.WixPatchRef: - case SymbolDefinitionType.WixPatchTarget: - case SymbolDefinitionType.WixProperty: - case SymbolDefinitionType.WixProductTag: - case SymbolDefinitionType.WixSimpleReference: - case SymbolDefinitionType.WixSuppressAction: - case SymbolDefinitionType.WixSuppressModularization: - case SymbolDefinitionType.WixUI: - case SymbolDefinitionType.WixVariable: - break; - - // Already processed by LoadTableDefinitions. - case SymbolDefinitionType.WixCustomTable: - case SymbolDefinitionType.WixCustomTableColumn: - break; - - case SymbolDefinitionType.MustBeFromAnExtension: - unknownSymbol = !this.AddSymbolFromExtension(symbol); - break; - - default: - unknownSymbol = !this.AddSymbolDefaultly(symbol); - break; - } - - if (unknownSymbol) - { - this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol)); - } - } - - this.AddIndexedCellSymbols(cellsByTableAndRowId); - this.EnsureRequiredTables(); - this.ReportGeneratedShortFileNameConflicts(); - this.ReportIllegalTables(); - this.ReportMismatchedModularizations(); - this.ReportWindowsInstallerDataInconsistencies(); - } - - private void AddAssemblySymbol(AssemblySymbol symbol) - { - var attributes = symbol.Type == AssemblyType.Win32Assembly ? 1 : (int?)null; - - var row = this.CreateRow(symbol, "MsiAssembly"); - row[0] = symbol.ComponentRef; - row[1] = symbol.FeatureRef; - row[2] = symbol.ManifestFileRef; - row[3] = symbol.ApplicationFileRef; - row[4] = attributes; - } - - private void AddBBControlSymbol(BBControlSymbol symbol) - { - var attributes = symbol.Attributes; - attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; - attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0; - attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0; - attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0; - attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0; - attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0; - attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; - attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; - - var row = this.CreateRow(symbol, "BBControl"); - row[0] = symbol.BillboardRef; - row[1] = symbol.BBControl; - row[2] = symbol.Type; - row[3] = symbol.X; - row[4] = symbol.Y; - row[5] = symbol.Width; - row[6] = symbol.Height; - row[7] = attributes; - row[8] = symbol.Text; - } - - private void AddClassSymbol(ClassSymbol symbol) - { - var row = this.CreateRow(symbol, "Class"); - row[0] = symbol.CLSID; - row[1] = symbol.Context; - row[2] = symbol.ComponentRef; - row[3] = symbol.DefaultProgIdRef; - row[4] = symbol.Description; - row[5] = symbol.AppIdRef; - row[6] = symbol.FileTypeMask; - row[7] = symbol.IconRef; - row[8] = symbol.IconIndex; - row[9] = symbol.DefInprocHandler; - row[10] = symbol.Argument; - row[11] = symbol.FeatureRef; - row[12] = symbol.RelativePath ? (int?)1 : null; - } - - private void AddControlSymbol(ControlSymbol symbol) - { - var text = symbol.Text; - var attributes = symbol.Attributes; - attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; - attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0; - attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0; - attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0; - attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0; - attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0; - attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; - attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; - - // If we're tracking disk space, and this is a non-FormatSize Text control, - // and the text attribute starts with '[' and ends with ']', add a space. - // It is not necessary for the whole string to be a property, just those - // two characters matter. - if (symbol.TrackDiskSpace && - "Text" == symbol.Type && - WindowsInstallerConstants.MsidbControlAttributesFormatSize != (attributes & WindowsInstallerConstants.MsidbControlAttributesFormatSize) && - null != text && text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) - { - text = String.Concat(text, " "); - } - - var row = this.CreateRow(symbol, "Control"); - row[0] = symbol.DialogRef; - row[1] = symbol.Control; - row[2] = symbol.Type; - row[3] = symbol.X; - row[4] = symbol.Y; - row[5] = symbol.Width; - row[6] = symbol.Height; - row[7] = attributes; - row[8] = symbol.Property; - row[9] = text; - row[10] = symbol.NextControlRef; - row[11] = symbol.Help; - } - - private void AddControlEventSymbol(ControlEventSymbol symbol) - { - var row = this.CreateRow(symbol, "ControlEvent"); - row[0] = symbol.DialogRef; - row[1] = symbol.ControlRef; - row[2] = symbol.Event; - row[3] = symbol.Argument; - row[4] = String.IsNullOrEmpty(symbol.Condition) ? "1" : symbol.Condition; - row[5] = symbol.Ordering; - } - - private void AddComponentSymbol(ComponentSymbol symbol) - { - var attributes = ComponentLocation.Either == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesOptional : 0; - attributes |= ComponentLocation.SourceOnly == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesSourceOnly : 0; - attributes |= ComponentKeyPathType.Registry == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath : 0; - attributes |= ComponentKeyPathType.OdbcDataSource == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource : 0; - attributes |= symbol.DisableRegistryReflection ? WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection : 0; - attributes |= symbol.NeverOverwrite ? WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite : 0; - attributes |= symbol.Permanent ? WindowsInstallerConstants.MsidbComponentAttributesPermanent : 0; - attributes |= symbol.SharedDllRefCount ? WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount : 0; - attributes |= symbol.Shared ? WindowsInstallerConstants.MsidbComponentAttributesShared : 0; - attributes |= symbol.Transitive ? WindowsInstallerConstants.MsidbComponentAttributesTransitive : 0; - attributes |= symbol.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence : 0; - attributes |= symbol.Win64 ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0; - - var row = this.CreateRow(symbol, "Component"); - row[0] = symbol.Id.Id; - row[1] = symbol.ComponentId; - row[2] = symbol.DirectoryRef; - row[3] = attributes; - row[4] = symbol.Condition; - row[5] = symbol.KeyPath; - } - - private void AddCustomActionSymbol(CustomActionSymbol symbol) - { - var type = symbol.Win64 ? WindowsInstallerConstants.MsidbCustomActionType64BitScript : 0; - type |= symbol.IgnoreResult ? WindowsInstallerConstants.MsidbCustomActionTypeContinue : 0; - type |= symbol.Hidden ? WindowsInstallerConstants.MsidbCustomActionTypeHideTarget : 0; - type |= symbol.Async ? WindowsInstallerConstants.MsidbCustomActionTypeAsync : 0; - type |= CustomActionExecutionType.FirstSequence == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence : 0; - type |= CustomActionExecutionType.OncePerProcess == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess : 0; - type |= CustomActionExecutionType.ClientRepeat == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat : 0; - type |= CustomActionExecutionType.Deferred == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript : 0; - type |= CustomActionExecutionType.Rollback == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeRollback : 0; - type |= CustomActionExecutionType.Commit == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeCommit : 0; - type |= CustomActionSourceType.File == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeSourceFile : 0; - type |= CustomActionSourceType.Directory == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeDirectory : 0; - type |= CustomActionSourceType.Property == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeProperty : 0; - type |= CustomActionTargetType.Dll == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeDll : 0; - type |= CustomActionTargetType.Exe == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeExe : 0; - type |= CustomActionTargetType.TextData == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeTextData : 0; - type |= CustomActionTargetType.JScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeJScript : 0; - type |= CustomActionTargetType.VBScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeVBScript : 0; - - if (WindowsInstallerConstants.MsidbCustomActionTypeInScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeInScript)) - { - type |= symbol.Impersonate ? 0 : WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate; - type |= symbol.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0; - } - - var row = this.CreateRow(symbol, "CustomAction"); - row[0] = symbol.Id.Id; - row[1] = type; - row[2] = symbol.Source; - row[3] = symbol.Target; - row[4] = symbol.PatchUninstall ? (int?)WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall : null; - - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); - this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); - } - } - - private void AddDialogSymbol(DialogSymbol symbol) - { - var attributes = symbol.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0; - attributes |= symbol.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; - attributes |= symbol.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0; - attributes |= symbol.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette : 0; - attributes |= symbol.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0; - attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0; - attributes |= symbol.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0; - attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0; - attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0; - attributes |= symbol.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; - attributes |= symbol.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; - - var row = this.CreateRow(symbol, "Dialog"); - row[0] = symbol.Id.Id; - row[1] = symbol.HCentering; - row[2] = symbol.VCentering; - row[3] = symbol.Width; - row[4] = symbol.Height; - row[5] = attributes; - row[6] = symbol.Title; - row[7] = symbol.FirstControlRef; - row[8] = symbol.DefaultControlRef; - row[9] = symbol.CancelControlRef; - - this.Data.EnsureTable(this.TableDefinitions["ListBox"]); - } - - private void AddDirectorySymbol(DirectorySymbol symbol) - { - (var name, var parentDir) = this.AddDirectorySubdirectories(symbol); - - var shortName = symbol.ShortName; - var sourceShortname = symbol.SourceShortName; - - if (String.IsNullOrEmpty(shortName) && name != null && name != "." && name != "SourceDir" && !this.BackendHelper.IsValidShortFilename(name, false)) - { - shortName = this.CreateShortName(name, false, "Directory", symbol.ParentDirectoryRef); - } - - if (String.IsNullOrEmpty(sourceShortname) && !String.IsNullOrEmpty(symbol.SourceName) && !this.BackendHelper.IsValidShortFilename(symbol.SourceName, false)) - { - sourceShortname = this.CreateShortName(symbol.SourceName, false, "Directory", symbol.ParentDirectoryRef); - } - - var sourceName = CreateMsiFilename(sourceShortname, symbol.SourceName); - var targetName = CreateMsiFilename(shortName, name); - - if (String.IsNullOrEmpty(targetName)) - { - targetName = "."; - } - - var defaultDir = String.IsNullOrEmpty(sourceName) || sourceName == targetName ? targetName : targetName + ":" + sourceName; - - var row = this.CreateRow(symbol, "Directory"); - row[0] = symbol.Id.Id; - row[1] = parentDir; - row[2] = defaultDir; - - if (OutputType.Module == this.Data.Type) - { - var directoryId = symbol.Id.Id; - - if (WindowsInstallerStandard.IsStandardDirectory(directoryId)) - { - // If the directory table contains references to standard windows folders - // mergemod.dll will add customactions to set the MSM directory to - // the same directory as the standard windows folder and will add references to - // custom action to all the standard sequence tables. A problem will occur - // if the MSI does not have these tables as mergemod.dll does not add these - // tables to the MSI if absent. This code adds the tables in case mergemod.dll - // needs them. - this.Data.EnsureTable(this.TableDefinitions["CustomAction"]); - this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); - this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); - this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); - } - else - { - foreach (var standardDirectory in WindowsInstallerStandard.StandardDirectories()) - { - if (directoryId.StartsWith(standardDirectory.Id.Id, StringComparison.Ordinal)) - { - this.Messaging.Write(WarningMessages.StandardDirectoryConflictInMergeModule(symbol.SourceLineNumbers, directoryId, standardDirectory.Id.Id)); - } - } - } - } - } - - private void AddDuplicateFileSymbol(DuplicateFileSymbol symbol) - { - var name = symbol.DestinationName; - if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.DestinationShortName = this.CreateShortName(name, true, "CopyFile", symbol.ComponentRef, symbol.FileRef); - } - - var row = this.CreateRow(symbol, "DuplicateFile"); - row[0] = symbol.Id.Id; - row[1] = symbol.ComponentRef; - row[2] = symbol.FileRef; - row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName); - row[4] = symbol.DestinationFolder; - } - - private void AddEnvironmentSymbol(EnvironmentSymbol symbol) - { - var action = String.Empty; - var system = symbol.System ? "*" : String.Empty; - var uninstall = symbol.Permanent ? String.Empty : "-"; - var value = symbol.Value; - - switch (symbol.Action) - { - case EnvironmentActionType.Create: - action = "+"; - break; - case EnvironmentActionType.Set: - action = "="; - break; - case EnvironmentActionType.Remove: - action = "!"; - break; - } - - switch (symbol.Part) - { - case EnvironmentPartType.First: - value = String.Concat(value, symbol.Separator, "[~]"); - break; - case EnvironmentPartType.Last: - value = String.Concat("[~]", symbol.Separator, value); - break; - } - - var row = this.CreateRow(symbol, "Environment"); - row[0] = symbol.Id.Id; - row[1] = String.Concat(action, uninstall, system, symbol.Name); - row[2] = value; - row[3] = symbol.ComponentRef; - } - - private void AddErrorSymbol(ErrorSymbol symbol) - { - var row = this.CreateRow(symbol, "Error"); - row[0] = Convert.ToInt32(symbol.Id.Id); - row[1] = symbol.Message; - } - - private void AddFeatureSymbol(FeatureSymbol symbol) - { - var attributes = symbol.DisallowAbsent ? WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent : 0; - attributes |= symbol.DisallowAdvertise ? WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise : 0; - attributes |= FeatureInstallDefault.FollowParent == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFollowParent : 0; - attributes |= FeatureInstallDefault.Source == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorSource : 0; - attributes |= FeatureTypicalDefault.Advertise == symbol.TypicalDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise : 0; - - var row = this.CreateRow(symbol, "Feature"); - row[0] = symbol.Id.Id; - row[1] = symbol.ParentFeatureRef; - row[2] = symbol.Title; - row[3] = symbol.Description; - row[4] = symbol.Display; - row[5] = symbol.Level; - row[6] = symbol.DirectoryRef; - row[7] = attributes; - } - - private void AddFileSymbol(FileSymbol symbol) - { - var name = symbol.Name; - if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.ShortName = this.CreateShortName(name, true, "File", symbol.DirectoryRef); - - if (!this.GeneratedShortNames.TryGetValue(symbol.ShortName, out var potentialConflicts)) - { - potentialConflicts = new List(); - this.GeneratedShortNames.Add(symbol.ShortName, potentialConflicts); - } - - potentialConflicts.Add(symbol); - } - - var row = (FileRow)this.CreateRow(symbol, "File"); - row.File = symbol.Id.Id; - row.Component = symbol.ComponentRef; - row.FileName = CreateMsiFilename(symbol.ShortName, name); - row.FileSize = symbol.FileSize; - row.Version = symbol.Version; - row.Language = symbol.Language; - row.DiskId = symbol.DiskId ?? 1; // TODO: is 1 the correct thing to default here - row.Sequence = symbol.Sequence; - row.Source = symbol.Source.Path; - - var attributes = (symbol.Attributes & FileSymbolAttributes.Checksum) == FileSymbolAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed ? WindowsInstallerConstants.MsidbFileAttributesNoncompressed : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.Hidden) == FileSymbolAttributes.Hidden ? WindowsInstallerConstants.MsidbFileAttributesHidden : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.ReadOnly) == FileSymbolAttributes.ReadOnly ? WindowsInstallerConstants.MsidbFileAttributesReadOnly : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.System) == FileSymbolAttributes.System ? WindowsInstallerConstants.MsidbFileAttributesSystem : 0; - attributes |= (symbol.Attributes & FileSymbolAttributes.Vital) == FileSymbolAttributes.Vital ? WindowsInstallerConstants.MsidbFileAttributesVital : 0; - row.Attributes = attributes; - - if (symbol.FontTitle != null) - { - var fontRow = this.CreateRow(symbol, "Font"); - fontRow[0] = symbol.Id.Id; - fontRow[1] = symbol.FontTitle; - } - - if (symbol.SelfRegCost.HasValue) - { - var selfRegRow = this.CreateRow(symbol, "SelfReg"); - selfRegRow[0] = symbol.Id.Id; - selfRegRow[1] = symbol.SelfRegCost.Value; - } - } - - private void AddIniFileSymbol(IniFileSymbol symbol) - { - var tableName = (IniFileActionType.AddLine == symbol.Action || IniFileActionType.AddTag == symbol.Action || IniFileActionType.CreateLine == symbol.Action) ? "IniFile" : "RemoveIniFile"; - - var name = symbol.FileName; - if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.ShortFileName = this.CreateShortName(name, true, "IniFile", symbol.ComponentRef); - } - - var row = this.CreateRow(symbol, tableName); - row[0] = symbol.Id.Id; - row[1] = CreateMsiFilename(symbol.ShortFileName, name); - row[2] = symbol.DirProperty; - row[3] = symbol.Section; - row[4] = symbol.Key; - row[5] = symbol.Value; - row[6] = symbol.Action; - row[7] = symbol.ComponentRef; - } - - private void AddIniLocatorSymbol(IniLocatorSymbol symbol) - { - var name = symbol.FileName; - if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.ShortFileName = this.CreateShortName(name, true, "IniFileSearch"); - } - - var row = this.CreateRow(symbol, "IniLocator"); - row[0] = symbol.Id.Id; - row[1] = CreateMsiFilename(symbol.ShortFileName, name); - row[2] = symbol.Section; - row[3] = symbol.Key; - row[4] = symbol.Field; - row[5] = symbol.Type; - } - - private void AddMediaSymbol(MediaSymbol symbol) - { - if (this.Section.Type != SectionType.Module) - { - var row = (MediaRow)this.CreateRow(symbol, "Media"); - row.DiskId = symbol.DiskId; - row.LastSequence = symbol.LastSequence ?? 0; - row.DiskPrompt = symbol.DiskPrompt; - row.Cabinet = symbol.Cabinet; - row.VolumeLabel = symbol.VolumeLabel; - row.Source = symbol.Source; - } - } - - private void AddModuleConfigurationSymbol(ModuleConfigurationSymbol symbol) - { - var row = this.CreateRow(symbol, "ModuleConfiguration"); - row[0] = symbol.Id.Id; - row[1] = symbol.Format; - row[2] = symbol.Type; - row[3] = symbol.ContextData; - row[4] = symbol.DefaultValue; - row[5] = (symbol.KeyNoOrphan ? WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan : 0) | - (symbol.NonNullable ? WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable : 0); - row[6] = symbol.DisplayName; - row[7] = symbol.Description; - row[8] = symbol.HelpLocation; - row[9] = symbol.HelpKeyword; - } - - private void AddMsiEmbeddedUISymbol(MsiEmbeddedUISymbol symbol) - { - var attributes = symbol.EntryPoint ? WindowsInstallerConstants.MsidbEmbeddedUI : 0; - attributes |= symbol.SupportsBasicUI ? WindowsInstallerConstants.MsidbEmbeddedHandlesBasic : 0; - - var row = this.CreateRow(symbol, "MsiEmbeddedUI"); - row[0] = symbol.Id.Id; - row[1] = symbol.FileName; - row[2] = attributes; - row[3] = symbol.MessageFilter; - row[4] = symbol.Source; - } - - private void AddMsiServiceConfigSymbol(MsiServiceConfigSymbol symbol) - { - var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; - events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; - events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; - - var row = this.CreateRow(symbol, "MsiServiceConfigFailureActions"); - row[0] = symbol.Id.Id; - row[1] = symbol.Name; - row[2] = events; - row[3] = symbol.ConfigType; - row[4] = symbol.Argument; - row[5] = symbol.ComponentRef; - } - - private void AddMsiServiceConfigFailureActionsSymbol(MsiServiceConfigFailureActionsSymbol symbol) - { - var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; - events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; - events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; - - var row = this.CreateRow(symbol, "MsiServiceConfig"); - row[0] = symbol.Id.Id; - row[1] = symbol.Name; - row[2] = events; - row[3] = symbol.ResetPeriod.HasValue ? symbol.ResetPeriod : null; - row[4] = symbol.RebootMessage ?? "[~]"; - row[5] = symbol.Command ?? "[~]"; - row[6] = symbol.Actions; - row[7] = symbol.DelayActions; - row[8] = symbol.ComponentRef; - } - - private void AddMoveFileSymbol(MoveFileSymbol symbol) - { - var name = symbol.DestinationName; - if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.DestinationShortName = this.CreateShortName(name, true, "MoveFile", symbol.ComponentRef); - } - - var row = this.CreateRow(symbol, "MoveFile"); - row[0] = symbol.Id.Id; - row[1] = symbol.ComponentRef; - row[2] = symbol.SourceName; - row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName); - row[4] = symbol.SourceFolder; - row[5] = symbol.DestFolder; - row[6] = symbol.Delete ? WindowsInstallerConstants.MsidbMoveFileOptionsMove : 0; - } - - private void AddPropertySymbol(PropertySymbol symbol) - { - if (String.IsNullOrEmpty(symbol.Value)) - { - return; - } - - var row = (PropertyRow)this.CreateRow(symbol, "Property"); - row.Property = symbol.Id.Id; - row.Value = symbol.Value; - } - - private void AddRemoveFileSymbol(RemoveFileSymbol symbol) - { - var name = symbol.FileName; - if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.ShortFileName = this.CreateShortName(name, true, "RemoveFile", symbol.ComponentRef); - } - - var installMode = symbol.OnInstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall : 0; - installMode |= symbol.OnUninstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove : 0; - - var row = this.CreateRow(symbol, "RemoveFile"); - row[0] = symbol.Id.Id; - row[1] = symbol.ComponentRef; - row[2] = CreateMsiFilename(symbol.ShortFileName, symbol.FileName); - row[3] = symbol.DirPropertyRef; - row[4] = installMode; - } - - private void AddRegistrySymbol(RegistrySymbol symbol) - { - var value = symbol.Value; - - switch (symbol.ValueType) - { - case RegistryValueType.Binary: - value = String.Concat("#x", value); - break; - case RegistryValueType.Expandable: - value = String.Concat("#%", value); - break; - case RegistryValueType.Integer: - value = String.Concat("#", value); - break; - case RegistryValueType.MultiString: - switch (symbol.ValueAction) - { - case RegistryValueActionType.Append: - value = String.Concat("[~]", value); - break; - case RegistryValueActionType.Prepend: - value = String.Concat(value, "[~]"); - break; - case RegistryValueActionType.Write: - default: - if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) - { - value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); - } - break; - } - break; - case RegistryValueType.String: - // escape the leading '#' character for string registry keys - if (null != value && value.StartsWith("#", StringComparison.Ordinal)) - { - value = String.Concat("#", value); - } - break; - } - - var row = this.CreateRow(symbol, "Registry"); - row[0] = symbol.Id.Id; - row[1] = symbol.Root; - row[2] = symbol.Key; - row[3] = symbol.Name; - row[4] = value; - row[5] = symbol.ComponentRef; - } - - private void AddRegLocatorSymbol(RegLocatorSymbol symbol) - { - var type = (int)symbol.Type; - type |= symbol.Win64 ? WindowsInstallerConstants.MsidbLocatorType64bit : 0; - - var row = this.CreateRow(symbol, "RegLocator"); - row[0] = symbol.Id.Id; - row[1] = symbol.Root; - row[2] = symbol.Key; - row[3] = symbol.Name; - row[4] = type; - } - - private void AddRemoveRegistrySymbol(RemoveRegistrySymbol symbol) - { - if (symbol.Action == RemoveRegistryActionType.RemoveOnInstall) - { - var row = this.CreateRow(symbol, "RemoveRegistry"); - row[0] = symbol.Id.Id; - row[1] = symbol.Root; - row[2] = symbol.Key; - row[3] = symbol.Name; - row[4] = symbol.ComponentRef; - } - else // Registry table is used to remove registry keys on uninstall. - { - var row = this.CreateRow(symbol, "Registry"); - row[0] = symbol.Id.Id; - row[1] = symbol.Root; - row[2] = symbol.Key; - row[3] = symbol.Name; - row[5] = symbol.ComponentRef; - } - } - - private void AddServiceControlSymbol(ServiceControlSymbol symbol) - { - var events = symbol.InstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventDelete : 0; - events |= symbol.UninstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete : 0; - events |= symbol.InstallStart ? WindowsInstallerConstants.MsidbServiceControlEventStart : 0; - events |= symbol.UninstallStart ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStart : 0; - events |= symbol.InstallStop ? WindowsInstallerConstants.MsidbServiceControlEventStop : 0; - events |= symbol.UninstallStop ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStop : 0; - - var row = this.CreateRow(symbol, "ServiceControl"); - row[0] = symbol.Id.Id; - row[1] = symbol.Name; - row[2] = events; - row[3] = symbol.Arguments; - if (symbol.Wait.HasValue) - { - row[4] = symbol.Wait.Value ? 1 : 0; - } - row[5] = symbol.ComponentRef; - } - - private void AddServiceInstallSymbol(ServiceInstallSymbol symbol) - { - var errorControl = (int)symbol.ErrorControl; - errorControl |= symbol.Vital ? WindowsInstallerConstants.MsidbServiceInstallErrorControlVital : 0; - - var serviceType = (int)symbol.ServiceType; - serviceType |= symbol.Interactive ? WindowsInstallerConstants.MsidbServiceInstallInteractive : 0; - - var row = this.CreateRow(symbol, "ServiceInstall"); - row[0] = symbol.Id.Id; - row[1] = symbol.Name; - row[2] = symbol.DisplayName; - row[3] = serviceType; - row[4] = (int)symbol.StartType; - row[5] = errorControl; - row[6] = symbol.LoadOrderGroup; - row[7] = symbol.Dependencies; - row[8] = symbol.StartName; - row[9] = symbol.Password; - row[10] = symbol.Arguments; - row[11] = symbol.ComponentRef; - row[12] = symbol.Description; - } - - private void AddShortcutSymbol(ShortcutSymbol symbol) - { - var name = symbol.Name; - if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) - { - symbol.ShortName = this.CreateShortName(name, true, "Shortcut", symbol.ComponentRef, symbol.DirectoryRef); - } - - var row = this.CreateRow(symbol, "Shortcut"); - row[0] = symbol.Id.Id; - row[1] = symbol.DirectoryRef; - row[2] = CreateMsiFilename(symbol.ShortName, name); - row[3] = symbol.ComponentRef; - row[4] = symbol.Target; - row[5] = symbol.Arguments; - row[6] = symbol.Description; - row[7] = symbol.Hotkey; - row[8] = symbol.IconRef; - row[9] = symbol.IconIndex; - row[10] = (int?)symbol.Show; - row[11] = symbol.WorkingDirectory; - row[12] = symbol.DisplayResourceDll; - row[13] = symbol.DisplayResourceId; - row[14] = symbol.DescriptionResourceDll; - row[15] = symbol.DescriptionResourceId; - } - - private void AddTextStyleSymbol(TextStyleSymbol symbol) - { - var styleBits = symbol.Bold ? WindowsInstallerConstants.MsidbTextStyleStyleBitsBold : 0; - styleBits |= symbol.Italic ? WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic : 0; - styleBits |= symbol.Strike ? WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike : 0; - styleBits |= symbol.Underline ? WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline : 0; - - long? color = null; - - if (symbol.Red.HasValue || symbol.Green.HasValue || symbol.Blue.HasValue) - { - color = symbol.Red ?? 0; - color += (long)(symbol.Green ?? 0) * 256; - color += (long)(symbol.Blue ?? 0) * 65536; - } - - var row = this.CreateRow(symbol, "TextStyle"); - row[0] = symbol.Id.Id; - row[1] = symbol.FaceName; - row[2] = symbol.Size; - row[3] = color; - row[4] = styleBits == 0 ? null : (int?)styleBits; - } - - private void AddUpgradeSymbol(UpgradeSymbol symbol) - { - var row = (UpgradeRow)this.CreateRow(symbol, "Upgrade"); - row.UpgradeCode = symbol.UpgradeCode; - row.VersionMin = symbol.VersionMin; - row.VersionMax = symbol.VersionMax; - row.Language = symbol.Language; - row.Remove = symbol.Remove; - row.ActionProperty = symbol.ActionProperty; - - var attributes = symbol.MigrateFeatures ? WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures : 0; - attributes |= symbol.OnlyDetect ? WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect : 0; - attributes |= symbol.IgnoreRemoveFailures ? WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure : 0; - attributes |= symbol.VersionMinInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive : 0; - attributes |= symbol.VersionMaxInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive : 0; - attributes |= symbol.ExcludeLanguages ? WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive : 0; - row.Attributes = attributes; - } - - private void AddWixActionSymbol(WixActionSymbol symbol) - { - // Get the table definition for the action (and ensure the proper table exists for a module). - string sequenceTableName = null; - switch (symbol.SequenceTable) - { - case SequenceTable.AdminExecuteSequence: - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); - sequenceTableName = "ModuleAdminExecuteSequence"; - } - else - { - sequenceTableName = "AdminExecuteSequence"; - } - break; - case SequenceTable.AdminUISequence: - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); - sequenceTableName = "ModuleAdminUISequence"; - } - else - { - sequenceTableName = "AdminUISequence"; - } - break; - case SequenceTable.AdvertiseExecuteSequence: - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); - sequenceTableName = "ModuleAdvtExecuteSequence"; - } - else - { - sequenceTableName = "AdvtExecuteSequence"; - } - break; - case SequenceTable.InstallExecuteSequence: - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); - sequenceTableName = "ModuleInstallExecuteSequence"; - } - else - { - sequenceTableName = "InstallExecuteSequence"; - } - break; - case SequenceTable.InstallUISequence: - if (OutputType.Module == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); - sequenceTableName = "ModuleInstallUISequence"; - } - else - { - sequenceTableName = "InstallUISequence"; - } - break; - } - - // create the action sequence row in the output - var row = this.CreateRow(symbol, sequenceTableName); - - if (SectionType.Module == this.Section.Type) - { - row[0] = symbol.Action; - if (0 != symbol.Sequence) - { - row[1] = symbol.Sequence; - } - else - { - var after = (null == symbol.Before); - row[2] = after ? symbol.After : symbol.Before; - row[3] = after ? 1 : 0; - } - row[4] = symbol.Condition; - } - else - { - row[0] = symbol.Action; - row[1] = symbol.Condition; - row[2] = symbol.Sequence; - } - } - - private void IndexCustomTableCellSymbol(WixCustomTableCellSymbol wixCustomTableCellSymbol, Dictionary> cellsByTableAndRowId) - { - var tableAndRowId = wixCustomTableCellSymbol.TableRef + "/" + wixCustomTableCellSymbol.RowId; - if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells)) - { - cells = new List(); - cellsByTableAndRowId.Add(tableAndRowId, cells); - } - - cells.Add(wixCustomTableCellSymbol); - } - - private void AddIndexedCellSymbols(Dictionary> cellsByTableAndRowId) - { - foreach (var rowOfCells in cellsByTableAndRowId.Values) - { - var firstCellSymbol = rowOfCells[0]; - var customTableDefinition = this.TableDefinitions[firstCellSymbol.TableRef]; - - if (customTableDefinition.Unreal) - { - continue; - } - - var customRow = this.CreateRow(firstCellSymbol, customTableDefinition); - var customRowFieldsByColumnName = customRow.Fields.ToDictionary(f => f.Column.Name); - -#if TODO // SectionId seems like a good thing to preserve. - customRow.SectionId = symbol.SectionId; -#endif - foreach (var cell in rowOfCells) - { - var data = cell.Data; - - if (customRowFieldsByColumnName.TryGetValue(cell.ColumnRef, out var rowField)) - { - if (!String.IsNullOrEmpty(data)) - { - if (rowField.Column.Type == ColumnType.Number) - { - try - { - rowField.Data = Convert.ToInt32(data, CultureInfo.InvariantCulture); - } - catch (FormatException) - { - this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); - } - catch (OverflowException) - { - this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); - } - } - else if (rowField.Column.Category == ColumnCategory.Identifier) - { - if (this.BackendHelper.IsValidIdentifier(data) || this.BackendHelper.IsValidBinderVariable(data) || ColumnCategory.Formatted == rowField.Column.Category) - { - rowField.Data = data; - } - else - { - this.Messaging.Write(ErrorMessages.IllegalIdentifier(cell.SourceLineNumbers, "Data", data)); - } - } - else - { - rowField.Data = data; - } - } - } - else - { - this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(cell.SourceLineNumbers, cell.ColumnRef)); - } - } - - for (var i = 0; i < customTableDefinition.Columns.Length; ++i) - { - if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) - { - this.Messaging.Write(ErrorMessages.NoDataForColumn(firstCellSymbol.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); - } - } - } - } - - private void AddWixEnsureTableSymbol(WixEnsureTableSymbol symbol) - { - var tableDefinition = this.TableDefinitions[symbol.Table]; - this.Data.EnsureTable(tableDefinition); - } - - private void AddWixPackageSymbol(WixPackageSymbol symbol) - { - // TODO: Remove the following from the compiler and do it here instead. - //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); - //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); - //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); - //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); - //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); - //if (null != upgradeCode) - //{ - // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); - //} - - //if (isPerMachine) - //{ - // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); - //} - } - - private bool AddSymbolFromExtension(IntermediateSymbol symbol) - { - foreach (var extension in this.BackendExtensions) - { - if (extension.TryProcessSymbol(this.Section, symbol, this.Data, this.TableDefinitions)) - { - return true; - } - } - - return false; - } - - private bool AddSymbolDefaultly(IntermediateSymbol symbol) => - this.BackendHelper.TryAddSymbolToMatchingTableDefinitions(this.Section, symbol, this.Data, this.TableDefinitions); - - private void EnsureModuleIgnoredTable(IntermediateSymbol symbol, string ignoredTable) - { - var tableDefinition = this.TableDefinitions["ModuleIgnoreTable"]; - var table = this.Data.EnsureTable(tableDefinition); - if (!table.Rows.Any(r => r.FieldAsString(0) == ignoredTable)) - { - var row = this.CreateRow(symbol, tableDefinition); - row[0] = ignoredTable; - } - } - - private (string, string) AddDirectorySubdirectories(DirectorySymbol symbol) - { - var directory = symbol.Name.Trim(PathSeparatorChars); - var parentDir = symbol.ParentDirectoryRef ?? (symbol.Id.Id == "TARGETDIR" ? null : "TARGETDIR"); - - var start = 0; - var end = directory.IndexOfAny(PathSeparatorChars); - var path = String.Empty; - - while (start <= end) - { - var subdirectoryName = directory.Substring(start, end - start); - - if (!String.IsNullOrEmpty(subdirectoryName)) - { - path = Path.Combine(path, subdirectoryName); - - var id = this.BackendHelper.GenerateIdentifier("d", symbol.ParentDirectoryRef, path); - var shortnameSubdirectory = this.BackendHelper.IsValidShortFilename(subdirectoryName, false) ? null : this.CreateShortName(subdirectoryName, false, "Directory", symbol.ParentDirectoryRef); - - var subdirectoryRow = this.CreateRow(symbol, "Directory"); - subdirectoryRow[0] = id; - subdirectoryRow[1] = parentDir; - subdirectoryRow[2] = CreateMsiFilename(shortnameSubdirectory, subdirectoryName); - - parentDir = id; - } - - start = end + 1; - end = symbol.Name.IndexOfAny(PathSeparatorChars, start); - } - - var name = (start == 0) ? directory : directory.Substring(start); - - return (name, parentDir); - } - - private void EnsureRequiredTables() - { - // check for missing table and add them or display an error as appropriate - switch (this.Data.Type) - { - case OutputType.Module: - this.Data.EnsureTable(this.TableDefinitions["Component"]); - this.Data.EnsureTable(this.TableDefinitions["Directory"]); - this.Data.EnsureTable(this.TableDefinitions["FeatureComponents"]); - this.Data.EnsureTable(this.TableDefinitions["File"]); - this.Data.EnsureTable(this.TableDefinitions["ModuleComponents"]); - this.Data.EnsureTable(this.TableDefinitions["ModuleSignature"]); - break; - - case OutputType.PatchCreation: - var imageFamiliesCount = this.Data.Tables["ImageFamilies"]?.Rows.Count ?? 0; - var targetImagesCount = this.Data.Tables["TargetImages"]?.Rows.Count ?? 0; - var upgradedImagesCount = this.Data.Tables["UpgradedImages"]?.Rows.Count ?? 0; - - if (imageFamiliesCount < 1) - { - this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("ImageFamilies")); - } - - if (targetImagesCount < 1) - { - this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("TargetImages")); - } - - if (upgradedImagesCount < 1) - { - this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("UpgradedImages")); - } - - this.Data.EnsureTable(this.TableDefinitions["Properties"]); - break; - - case OutputType.Product: - this.Data.EnsureTable(this.TableDefinitions["File"]); - this.Data.EnsureTable(this.TableDefinitions["Media"]); - break; - } - } - - private void ReportGeneratedShortFileNameConflicts() - { - foreach (var conflicts in this.GeneratedShortNames.Values.Where(l => l.Count > 1)) - { - this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict(conflicts[0].SourceLineNumbers, conflicts[0].ShortName)); - for (var i = 1; i < conflicts.Count; ++i) - { - this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict2(conflicts[i].SourceLineNumbers)); - } - } - } - - private void ReportIllegalTables() - { - foreach (var table in this.Data.Tables) - { - switch (this.Data.Type) - { - case OutputType.Module: - if ("BBControl" == table.Name || - "Billboard" == table.Name || - "CCPSearch" == table.Name || - "Feature" == table.Name || - "LaunchCondition" == table.Name || - "Media" == table.Name || - "Patch" == table.Name || - "Upgrade" == table.Name || - "WixMerge" == table.Name) - { - foreach (Row row in table.Rows) - { - this.Messaging.Write(ErrorMessages.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name)); - } - } - else if ("Error" == table.Name) - { - foreach (var row in table.Rows) - { - this.Messaging.Write(WarningMessages.DangerousTableInMergeModule(row.SourceLineNumbers, table.Name)); - } - } - break; - - case OutputType.PatchCreation: - if (!table.Definition.Unreal && - "_SummaryInformation" != table.Name && - "ExternalFiles" != table.Name && - "FamilyFileRanges" != table.Name && - "ImageFamilies" != table.Name && - "PatchMetadata" != table.Name && - "PatchSequence" != table.Name && - "Properties" != table.Name && - "TargetFiles_OptionalData" != table.Name && - "TargetImages" != table.Name && - "UpgradedFiles_OptionalData" != table.Name && - "UpgradedFilesToIgnore" != table.Name && - "UpgradedImages" != table.Name) - { - foreach (var row in table.Rows) - { - this.Messaging.Write(ErrorMessages.UnexpectedTableInPatchCreationPackage(row.SourceLineNumbers, table.Name)); - } - } - break; - - case OutputType.Patch: - if (!table.Definition.Unreal && - "_SummaryInformation" != table.Name && - "Media" != table.Name && - "MsiFileHash" != table.Name && - "MsiPatchMetadata" != table.Name && - "MsiPatchSequence" != table.Name) - { - foreach (var row in table.Rows) - { - this.Messaging.Write(ErrorMessages.UnexpectedTableInPatch(row.SourceLineNumbers, table.Name)); - } - } - break; - - case OutputType.Product: - if ("ModuleAdminExecuteSequence" == table.Name || - "ModuleAdminUISequence" == table.Name || - "ModuleAdvtExecuteSequence" == table.Name || - "ModuleAdvtUISequence" == table.Name || - "ModuleComponents" == table.Name || - "ModuleConfiguration" == table.Name || - "ModuleDependency" == table.Name || - "ModuleExclusion" == table.Name || - "ModuleIgnoreTable" == table.Name || - "ModuleInstallExecuteSequence" == table.Name || - "ModuleInstallUISequence" == table.Name || - "ModuleSignature" == table.Name || - "ModuleSubstitution" == table.Name) - { - foreach (var row in table.Rows) - { - this.Messaging.Write(WarningMessages.UnexpectedTableInProduct(row.SourceLineNumbers, table.Name)); - } - } - break; - } - } - } - - private void ReportMismatchedModularizations() - { - // verify that modularization types match for foreign key relationships - foreach (var tableDefinition in this.TableDefinitions) - { - foreach (var columnDefinition in tableDefinition.Columns) - { - if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.KeyColumn.HasValue) - { - if (this.TableDefinitions.TryGet(columnDefinition.KeyTable, out var keyTableDefinition)) - { - var keyColumnIndex = columnDefinition.KeyColumn ?? -1; - - if (keyColumnIndex <= 0 || keyColumnIndex > keyTableDefinition.Columns.Length) - { - this.Messaging.Write(ErrorMessages.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex)); - } - else if (keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType) - { - this.Messaging.Write(WarningMessages.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType.ToString())); - } - } - // else - ignore missing table definitions as that error is caught in other places - } - } - } - } - - private void ReportWindowsInstallerDataInconsistencies() - { - // Get the output's minimum installer version - var outputInstallerVersion = Int32.MaxValue; - - if (this.Data.Tables.TryGetTable("_SummaryInformation", out var summaryInformationTable)) - { - outputInstallerVersion = summaryInformationTable.Rows.FirstOrDefault(r => 14 == r.FieldAsInteger(0))?.FieldAsInteger(1) ?? Int32.MaxValue; - } - - // Ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40). - if (outputInstallerVersion <= 100 && OutputType.Product == this.Data.Type) - { - this.Data.EnsureTable(this.TableDefinitions["Error"]); - } - - // Check for the presence of tables/rows/columns that require MSI 1.1 or later. - if (outputInstallerVersion < 110) - { - if (this.Data.Tables.TryGetTable("IsolatedComponent", out var isolatedComponentTable)) - { - foreach (var row in isolatedComponentTable.Rows) - { - this.Messaging.Write(WarningMessages.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion)); - } - } - } - - // Check for the presence of tables/rows/columns that require MSI 4.0 or later - if (outputInstallerVersion < 400) - { - if (this.Data.Tables.TryGetTable("Shortcut", out var shortcutTable)) - { - foreach (var row in shortcutTable.Rows) - { - if (null != row[12] || null != row[13] || null != row[14] || null != row[15]) - { - this.Messaging.Write(WarningMessages.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion)); - } - } - } - } - } - - private static OutputType SectionTypeToOutputType(SectionType type) - { - switch (type) - { - case SectionType.Bundle: - return OutputType.Bundle; - case SectionType.Module: - return OutputType.Module; - case SectionType.Product: - return OutputType.Product; - case SectionType.PatchCreation: - return OutputType.PatchCreation; - case SectionType.Patch: - return OutputType.Patch; - - default: - throw new ArgumentOutOfRangeException(nameof(type)); - } - } - - private Row CreateRow(IntermediateSymbol symbol, string tableDefinitionName) => - this.CreateRow(symbol, this.TableDefinitions[tableDefinitionName]); - - private Row CreateRow(IntermediateSymbol symbol, TableDefinition tableDefinition) => - this.BackendHelper.CreateRow(this.Section, symbol, this.Data, tableDefinition); - - - private string CreateShortName(string longName, bool keepExtension, params string[] args) - { - longName = longName.ToLowerInvariant(); - - // collect all the data - var strings = new List(1 + args.Length); - strings.Add(longName); - strings.AddRange(args); - - // prepare for hashing - var stringData = String.Join("|", strings); - var data = Encoding.UTF8.GetBytes(stringData); - - // hash the data - byte[] hash; - using (var sha1 = new SHA1CryptoServiceProvider()) - { - hash = sha1.ComputeHash(data); - } - - // generate the short file/directory name without an extension - var shortName = new StringBuilder(Convert.ToBase64String(hash)); - shortName.Length = 8; - shortName.Replace('+', '-').Replace('/', '_'); - - if (keepExtension) - { - var extension = Path.GetExtension(longName); - - if (4 < extension.Length) - { - extension = extension.Substring(0, 4); - } - - shortName.Append(extension); - - // check the generated short name to ensure its still legal (the extension may not be legal) - if (!this.BackendHelper.IsValidShortFilename(shortName.ToString(), false)) - { - // remove the extension (by truncating the generated file name back to the generated characters) - shortName.Length -= extension.Length; - } - } - - return shortName.ToString().ToLowerInvariant(); - } - - private static string CreateMsiFilename(string shortName, string longName) - { - if (String.IsNullOrEmpty(shortName) || String.Equals(shortName, longName, StringComparison.OrdinalIgnoreCase)) - { - return longName; - } - else - { - return shortName + "|" + longName; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs deleted file mode 100644 index 7c1e085c..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs +++ /dev/null @@ -1,221 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using WixToolset.Core.Native; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.Native.Msm; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Retrieve files information and extract them from merge modules. - /// - internal class ExtractMergeModuleFilesCommand - { - public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable wixMergeSymbols, IEnumerable fileFacades, int installerVersion, string intermediateFolder, bool suppressLayout) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.WixMergeSymbols = wixMergeSymbols; - this.FileFacades = fileFacades; - this.OutputInstallerVersion = installerVersion; - this.IntermediateFolder = intermediateFolder; - this.SuppressLayout = suppressLayout; - } - - private IMessaging Messaging { get; } - - private IWindowsInstallerBackendHelper BackendHelper { get; } - - private IEnumerable WixMergeSymbols { get; } - - private IEnumerable FileFacades { get; } - - private int OutputInstallerVersion { get; } - - private string IntermediateFolder { get; } - - private bool SuppressLayout { get; } - - public IEnumerable MergeModulesFileFacades { get; private set; } - - public void Execute() - { - var mergeModulesFileFacades = new List(); - - var merge = MsmInterop.GetMsmMerge(); - - // Index all of the file rows to be able to detect collisions with files in the Merge Modules. - // It may seem a bit expensive to build up this index solely for the purpose of checking collisions - // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out - // there are other cases where we need all the file rows indexed, however they are not common cases. - // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let - // this case be slightly more expensive because the cost of maintaining an indexed file row collection - // is a lot more costly for the common cases. - var indexedFileFacades = this.FileFacades.ToDictionary(f => f.Id, StringComparer.Ordinal); - - foreach (var wixMergeRow in this.WixMergeSymbols) - { - var containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); - - // If the module has files and creating layout - if (containsFiles && !this.SuppressLayout) - { - this.ExtractFilesFromMergeModule(merge, wixMergeRow); - } - } - - this.MergeModulesFileFacades = mergeModulesFileFacades; - } - - private bool CreateFacadesForMergeModuleFiles(WixMergeSymbol wixMergeRow, List mergeModulesFileFacades, Dictionary indexedFileFacades) - { - var containsFiles = false; - - try - { - // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. - using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) - { - if (db.TableExists("File") && db.TableExists("Component")) - { - var uniqueModuleFileIdentifiers = new Dictionary(StringComparer.OrdinalIgnoreCase); - - using (var view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) - { - // add each file row from the merge module into the file row collection (check for errors along the way) - foreach (var record in view.Records) - { - // NOTE: this is very tricky - the merge module file rows are not added to the - // file table because they should not be created via idt import. Instead, these - // rows are created by merging in the actual modules. - var fileSymbol = new FileSymbol(wixMergeRow.SourceLineNumbers, new Identifier(AccessModifier.Section, record[1])); - fileSymbol.Attributes = wixMergeRow.FileAttributes; - fileSymbol.DirectoryRef = record[2]; - fileSymbol.DiskId = wixMergeRow.DiskId; - fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) }; - - var mergeModuleFileFacade = this.BackendHelper.CreateFileFacadeFromMergeModule(fileSymbol); - - // If case-sensitive collision with another merge module or a user-authored file identifier. - if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade)) - { - this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.Id)); - } - else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module - { - this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.Id, collidingFacade.Id)); - } - else // no collision - { - mergeModulesFileFacades.Add(mergeModuleFileFacade); - - // Keep updating the indexes as new rows are added. - indexedFileFacades.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); - uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); - } - - containsFiles = true; - } - } - } - - // Get the summary information to detect the Schema - using (var summaryInformation = new SummaryInformation(db)) - { - var moduleInstallerVersionString = summaryInformation.GetProperty(14); - - try - { - var moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); - if (moduleInstallerVersion > this.OutputInstallerVersion) - { - this.Messaging.Write(WarningMessages.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleInstallerVersion, this.OutputInstallerVersion)); - } - } - catch (FormatException) - { - throw new WixException(ErrorMessages.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); - } - } - } - } - catch (FileNotFoundException) - { - throw new WixException(ErrorMessages.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); - } - catch (Win32Exception) - { - throw new WixException(ErrorMessages.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile)); - } - - return containsFiles; - } - - private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeSymbol wixMergeRow) - { - var moduleOpen = false; - short mergeLanguage; - - var mergeId = wixMergeRow.Id.Id; - - try - { - mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); - } - catch (FormatException) - { - this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, mergeId, wixMergeRow.Language.ToString())); - return; - } - - try - { - merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); - moduleOpen = true; - - // extract the module cabinet, then explode all of the files to a temp directory - var moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab"); - merge.ExtractCAB(moduleCabPath); - - var mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId); - Directory.CreateDirectory(mergeIdPath); - - try - { - var cabinet = new Cabinet(moduleCabPath); - cabinet.Extract(mergeIdPath); - } - catch (FileNotFoundException) - { - throw new WixException(ErrorMessages.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); - } - catch - { - throw new WixException(ErrorMessages.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); - } - } - catch (COMException ce) - { - throw new WixException(ErrorMessages.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); - } - finally - { - if (moduleOpen) - { - merge.CloseModule(); - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs b/src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs deleted file mode 100644 index fe65ccef..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs +++ /dev/null @@ -1,77 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Extensibility; - - internal class FileSystemManager - { - public FileSystemManager(IEnumerable fileSystemExtensions) - { - this.Extensions = fileSystemExtensions; - } - - private IEnumerable Extensions { get; } - - public bool CompareFiles(string firstPath, string secondPath) - { - foreach (var extension in this.Extensions) - { - var compared = extension.CompareFiles(firstPath, secondPath); - if (compared.HasValue) - { - return compared.Value; - } - } - - return BuiltinCompareFiles(firstPath, secondPath); - } - - private static bool BuiltinCompareFiles(string firstPath, string secondPath) - { - if (String.Equals(firstPath, secondPath, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - using (var firstStream = File.OpenRead(firstPath)) - using (var secondStream = File.OpenRead(secondPath)) - { - if (firstStream.Length != secondStream.Length) - { - return false; - } - - // Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance. - // The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined. - var firstBuffer = new byte[16 * 1024]; - var secondBuffer = new byte[16 * 1024]; - - var firstReadLength = 0; - do - { - firstReadLength = firstStream.Read(firstBuffer, 0, firstBuffer.Length); - var secondReadLength = secondStream.Read(secondBuffer, 0, secondBuffer.Length); - - if (firstReadLength != secondReadLength) - { - return false; - } - - for (var i = 0; i < firstReadLength; ++i) - { - if (firstBuffer[i] != secondBuffer[i]) - { - return false; - } - } - } while (0 < firstReadLength); - } - - return true; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs deleted file mode 100644 index 3cdc0c28..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs +++ /dev/null @@ -1,262 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Set the guids for components with generatable guids and validate all are appropriately unique. - /// - internal class FinalizeComponentGuids - { - internal FinalizeComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform) - { - this.Messaging = messaging; - this.BackendHelper = helper; - this.PathResolver = pathResolver; - this.Section = section; - this.Platform = platform; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private IPathResolver PathResolver { get; } - - private IntermediateSection Section { get; } - - private Platform Platform { get; } - - private Dictionary ComponentIdGenSeeds { get; set; } - - private ILookup FilesByComponentId { get; set; } - - private Dictionary RegistrySymbolsById { get; set; } - - private Dictionary TargetPathsByDirectoryId { get; set; } - - public void Execute() - { - var componentGuidConditions = new Dictionary>(StringComparer.OrdinalIgnoreCase); - var guidCollisions = new HashSet(StringComparer.OrdinalIgnoreCase); - - foreach (var componentSymbol in this.Section.Symbols.OfType()) - { - if (componentSymbol.ComponentId == "*") - { - this.GenerateComponentGuid(componentSymbol); - } - - // Now check for GUID collisions, but we don't care about unmanaged components and - // if there's a * GUID remaining, there's already an error that explained why it - // was not replaced with a real GUID. - if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && componentSymbol.ComponentId != "*") - { - if (!componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var components)) - { - components = new List(); - componentGuidConditions.Add(componentSymbol.ComponentId, components); - } - - components.Add(componentSymbol); - if (components.Count > 1) - { - guidCollisions.Add(componentSymbol.ComponentId); - } - } - } - - if (guidCollisions.Count > 0) - { - this.ReportGuidCollisions(guidCollisions, componentGuidConditions); - } - } - - private void GenerateComponentGuid(ComponentSymbol componentSymbol) - { - if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType) - { - this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers)); - return; - } - - if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType) - { - if (this.RegistrySymbolsById is null) - { - this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - } - - if (this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol)) - { - var bitness = componentSymbol.Win64 ? "64" : String.Empty; - var regkey = String.Concat(bitness, registrySymbol.Root, "\\", registrySymbol.Key, "\\", registrySymbol.Name); - componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); - } - } - else // must be a File KeyPath. - { - // If the directory table hasn't been loaded into an indexed hash - // of directory ids to target names do that now. - if (this.TargetPathsByDirectoryId is null) - { - this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); - } - - // If the component id generation seeds have not been indexed - // from the Directory symbols do that now. - if (this.ComponentIdGenSeeds is null) - { - // If there are any Directory symbols, build up the Component Guid - // generation seeds indexed by Directory/@Id. - this.ComponentIdGenSeeds = this.Section.Symbols.OfType() - .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) - .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed); - } - - // If the file symbols have not been indexed by File's ComponentRef yet - // then do that now. - if (this.FilesByComponentId is null) - { - this.FilesByComponentId = this.Section.Symbols.OfType().ToLookup(f => f.ComponentRef); - } - - // validate component meets all the conditions to have a generated guid - var currentComponentFiles = this.FilesByComponentId[componentSymbol.Id.Id]; - var numFilesInComponent = currentComponentFiles.Count(); - string path = null; - - foreach (var fileSymbol in currentComponentFiles) - { - if (fileSymbol.Id.Id == componentSymbol.KeyPath) - { - // calculate the key file's canonical target path - var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, this.ComponentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform); - var fileName = this.BackendHelper.GetMsiFileName(fileSymbol.Name, false, true).ToLowerInvariant(); - path = Path.Combine(directoryPath, fileName); - - // find paths that are not canonicalized - if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || - path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || - path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || - path.StartsWith("TARGETDIR", StringComparison.Ordinal) || - path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || - path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) - { - this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileSymbol.ComponentRef, path)); - } - - // if component has more than one file, the key path must be versioned - if (1 < numFilesInComponent && String.IsNullOrEmpty(fileSymbol.Version)) - { - this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.SourceLineNumbers)); - } - } - else - { - // not a key path, so it must be an unversioned file if component has more than one file - if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileSymbol.Version)) - { - this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers)); - } - } - } - - // if the rules were followed, reward with a generated guid - if (!this.Messaging.EncounteredError) - { - componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path); - } - } - } - - private void ReportGuidCollisions(HashSet guidCollisions, Dictionary> componentGuidConditions) - { - Dictionary fileSymbolsById = null; - - foreach (var guid in guidCollisions) - { - var collidingComponents = componentGuidConditions[guid]; - var allComponentsHaveConditions = collidingComponents.All(c => !String.IsNullOrEmpty(c.Condition)); - - foreach (var componentSymbol in collidingComponents) - { - string path; - string type; - - if (componentSymbol.KeyPathType == ComponentKeyPathType.File) - { - if (fileSymbolsById is null) - { - fileSymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - } - - path = fileSymbolsById.TryGetValue(componentSymbol.KeyPath, out var fileSymbol) ? fileSymbol.Source.Path : componentSymbol.KeyPath; - type = "source path"; - } - else if (componentSymbol.KeyPathType == ComponentKeyPathType.Registry) - { - if (this.RegistrySymbolsById is null) - { - this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - } - - path = this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol) ? String.Concat(registrySymbol.Key, "\\", registrySymbol.Name) : componentSymbol.KeyPath; - type = "registry path"; - } - else - { - if (this.TargetPathsByDirectoryId is null) - { - this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); - } - - path = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, componentIdGenSeeds: null, componentSymbol.DirectoryRef, this.Platform); - type = "directory"; - } - - if (allComponentsHaveConditions) - { - this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); - } - } - } - } - - private Dictionary ResolveDirectoryTargetPaths() - { - var directories = this.Section.Symbols.OfType().ToList(); - - var targetPathsByDirectoryId = new Dictionary(directories.Count); - - // Get the target paths for all directories. - foreach (var directory in directories) - { - // If the directory Id already exists, we will skip it here since - // checking for duplicate primary keys is done later when importing tables - // into database - if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) - { - continue; - } - - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); - targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); - } - - return targetPathsByDirectoryId; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs deleted file mode 100644 index b8cca752..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs +++ /dev/null @@ -1,408 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.IO; - using System.Linq; - using System.Text; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class GenerateDatabaseCommand - { - private const string IdtsSubFolder = "_idts"; - - public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.FileSystemManager = fileSystemManager; - this.Data = data; - this.OutputPath = outputPath; - this.TableDefinitions = tableDefinitions; - this.IntermediateFolder = intermediateFolder; - this.KeepAddedColumns = keepAddedColumns; - this.SuppressAddingValidationRows = suppressAddingValidationRows; - this.UseSubDirectory = useSubdirectory; - } - - private IBackendHelper BackendHelper { get; } - - private FileSystemManager FileSystemManager { get; } - - /// - /// Whether to keep columns added in a transform. - /// - private bool KeepAddedColumns { get; } - - private IMessaging Messaging { get; } - - private WindowsInstallerData Data { get; } - - private string OutputPath { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - private string IntermediateFolder { get; } - - public List GeneratedTemporaryFiles { get; } = new List(); - - /// - /// Whether to use a subdirectory based on the database file name for intermediate files. - /// - private bool SuppressAddingValidationRows { get; } - - private bool UseSubDirectory { get; } - - public void Execute() - { - // Add the _Validation rows. - if (!this.SuppressAddingValidationRows) - { - this.AddValidationRows(); - } - - var baseDirectory = this.IntermediateFolder; - - if (this.UseSubDirectory) - { - var filename = Path.GetFileNameWithoutExtension(this.OutputPath); - baseDirectory = Path.Combine(baseDirectory, filename); - } - - var idtFolder = Path.Combine(baseDirectory, IdtsSubFolder); - - var type = OpenDatabase.CreateDirect; - - if (OutputType.Patch == this.Data.Type) - { - type |= OpenDatabase.OpenPatchFile; - } - - try - { - Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); - - Directory.CreateDirectory(idtFolder); - - using (var db = new Database(this.OutputPath, type)) - { - // 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 != this.Data.Codepage) - { - this.SetDatabaseCodepage(db, this.Data.Codepage, idtFolder); - } - - this.ImportTables(db, idtFolder); - - // Insert substorages (usually transforms inside a patch or instance transforms in a package). - this.ImportSubStorages(db); - - // We're good, commit the changes to the new database. - db.Commit(); - } - } - catch (IOException e) - { - // TODO: this error message doesn't seem specific enough - throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e); - } - } - - private void AddValidationRows() - { - var validationTable = this.Data.EnsureTable(this.TableDefinitions["_Validation"]); - - // Add the validation rows for real tables and columns. - foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) - { - foreach (var columnDef in table.Definition.Columns.Where(c => !c.Unreal)) - { - var row = validationTable.CreateRow(null); - - row[0] = table.Name; - - row[1] = columnDef.Name; - - if (columnDef.Nullable) - { - row[2] = "Y"; - } - else - { - row[2] = "N"; - } - - if (columnDef.MinValue.HasValue) - { - row[3] = columnDef.MinValue.Value; - } - - if (columnDef.MaxValue.HasValue) - { - row[4] = columnDef.MaxValue.Value; - } - - row[5] = columnDef.KeyTable; - - if (columnDef.KeyColumn.HasValue) - { - row[6] = columnDef.KeyColumn.Value; - } - - if (ColumnCategory.Unknown != columnDef.Category) - { - row[7] = columnDef.Category.ToString(); - } - - row[8] = columnDef.Possibilities; - - row[9] = columnDef.Description; - } - } - } - - private void ImportTables(Database db, string idtDirectory) - { - foreach (var table in this.Data.Tables) - { - var importTable = table; - var hasBinaryColumn = false; - - // Skip all unreal tables other than _Streams. - if (table.Definition.Unreal && "_Streams" != table.Name) - { - continue; - } - - // Do not put the _Validation table in patches, it is not needed. - if (OutputType.Patch == this.Data.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 (var columnDefinition in table.Definition.Columns) - { - if (ColumnType.Object == columnDefinition.Type) - { - importTable = new Table(table.Definition); - hasBinaryColumn = true; - break; - } - } - - // Create the table via IDT import. - if ("_Streams" != importTable.Name) - { - try - { - var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Data.Codepage, idtDirectory, this.KeepAddedColumns); - command.Execute(); - - var trackIdt = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary); - this.GeneratedTemporaryFiles.Add(trackIdt); - - db.Import(command.IdtPath); - } - 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) - { - var query = new StringBuilder("SELECT "); - - // Build the query for the view. - var firstColumn = true; - foreach (var columnDefinition in table.Definition.Columns) - { - if (columnDefinition.Unreal) - { - continue; - } - - if (!firstColumn) - { - query.Append(","); - } - - query.AppendFormat(" `{0}`", columnDefinition.Name); - firstColumn = false; - } - query.AppendFormat(" FROM `{0}`", table.Name); - - using (var tableView = db.OpenExecuteView(query.ToString())) - { - // Import each row containing a stream - foreach (var row in table.Rows) - { - using (var record = new Record(table.Definition.Columns.Length)) - { - // Stream names are created by concatenating the name of the table with the values - // of the primary key (delimited by periods). - var streamName = new StringBuilder(); - - // the _Streams table doesn't prepend the table name (or a period) - if ("_Streams" != table.Name) - { - streamName.Append(table.Name); - } - - var needStream = false; - - for (var i = 0; i < table.Definition.Columns.Length; i++) - { - var columnDefinition = table.Definition.Columns[i]; - - if (columnDefinition.Unreal) - { - continue; - } - - switch (columnDefinition.Type) - { - case ColumnType.Localized: - case ColumnType.Preserved: - case ColumnType.String: - var str = row.FieldAsString(i); - - if (columnDefinition.PrimaryKey) - { - if (0 < streamName.Length) - { - streamName.Append("."); - } - - streamName.Append(str); - } - - record.SetString(i + 1, str); - break; - case ColumnType.Number: - record.SetInteger(i + 1, row.FieldAsInteger(i)); - break; - - case ColumnType.Object: - var path = row.FieldAsString(i); - if (null != path) - { - needStream = true; - try - { - record.SetStream(i + 1, path); - } - catch (Win32Exception e) - { - if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME - { - throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, path)); - } - else - { - throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); - } - } - } - break; - } - } - - // check for a stream name that is more than 62 characters long (the maximum allowed length) - if (needStream && Database.MsiMaxStreamNameLength < streamName.Length) - { - this.Messaging.Write(ErrorMessages.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(); - } - } - } - } - - private void ImportSubStorages(Database db) - { - if (0 < this.Data.SubStorages.Count) - { - using (var storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) - { - foreach (var subStorage in this.Data.SubStorages) - { - var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst")); - - // Bind the transform. - var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions); - command.Execute(); - - if (this.Messaging.EncounteredError) - { - continue; - } - - // Add the storage to the database. - using (var record = new Record(2)) - { - record.SetString(1, subStorage.Name); - record.SetStream(2, transformFile); - storagesView.Modify(ModifyView.Assign, record); - } - } - } - } - } - - private void SetDatabaseCodepage(Database db, int codepage, string idtFolder) - { - // Write out the _ForceCodepage IDT file. - var idtPath = Path.Combine(idtFolder, "_ForceCodepage.idt"); - using (var idtFile = new StreamWriter(idtPath, false, Encoding.ASCII)) - { - idtFile.WriteLine(); // dummy column name record - idtFile.WriteLine(); // dummy column definition record - idtFile.Write(codepage); - idtFile.WriteLine("\t_ForceCodepage"); - } - - var trackIdt = this.BackendHelper.TrackFile(idtPath, TrackedFileType.Temporary); - this.GeneratedTemporaryFiles.Add(trackIdt); - - // Try to import the table into the MSI. - try - { - db.Import(idtPath); - } - catch (WixInvalidIdtException) - { - // The IDT should always be generated correctly, so an invalid code page was given. - throw new WixException(ErrorMessages.IllegalCodepage(codepage)); - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs deleted file mode 100644 index faa03762..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs +++ /dev/null @@ -1,582 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - /// - /// Creates a transform by diffing two outputs. - /// - internal class GenerateTransformCommand - { - private const char sectionDelimiter = '/'; - private readonly IMessaging messaging; - private SummaryInformationStreams transformSummaryInfo; - - /// - /// Instantiates a new Differ class. - /// - public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool preserveUnchangedRows, bool showPedanticMessages) - { - this.messaging = messaging; - this.TargetOutput = targetOutput; - this.UpdatedOutput = updatedOutput; - this.PreserveUnchangedRows = preserveUnchangedRows; - this.ShowPedanticMessages = showPedanticMessages; - } - - private WindowsInstallerData TargetOutput { get; } - - private WindowsInstallerData UpdatedOutput { get; } - - private TransformFlags ValidationFlags { get; } - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - private bool ShowPedanticMessages { get; } - - /// - /// Gets or sets the option to suppress keeping special rows. - /// - /// The option to suppress keeping special rows. - private bool SuppressKeepingSpecialRows { get; } - - /// - /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. - /// - /// The option to keep all rows including unchanged rows. - private bool PreserveUnchangedRows { get; } - - public WindowsInstallerData Transform { get; private set; } - - /// - /// Creates a transform by diffing two outputs. - /// - public WindowsInstallerData Execute() - { - var targetOutput = this.TargetOutput; - var updatedOutput = this.UpdatedOutput; - var validationFlags = this.ValidationFlags; - - var transform = new WindowsInstallerData(null) - { - Type = OutputType.Transform, - Codepage = updatedOutput.Codepage - }; - - this.transformSummaryInfo = new SummaryInformationStreams(); - - // compare the codepages - if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) - { - this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); - if (null != updatedOutput.SourceLineNumbers) - { - this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); - } - } - - // compare the output types - if (targetOutput.Type != updatedOutput.Type) - { - throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); - } - - // compare the contents of the tables - foreach (var targetTable in targetOutput.Tables) - { - var updatedTable = updatedOutput.Tables[targetTable.Name]; - var operation = TableOperation.None; - - var rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); - - if (TableOperation.Drop == operation) - { - var droppedTable = transform.EnsureTable(targetTable.Definition); - droppedTable.Operation = TableOperation.Drop; - } - else if (TableOperation.None == operation) - { - var modifiedTable = transform.EnsureTable(updatedTable.Definition); - foreach (var row in rows) - { - modifiedTable.Rows.Add(row); - } - } - } - - // added tables - foreach (var updatedTable in updatedOutput.Tables) - { - if (null == targetOutput.Tables[updatedTable.Name]) - { - var addedTable = transform.EnsureTable(updatedTable.Definition); - addedTable.Operation = TableOperation.Add; - - foreach (var updatedRow in updatedTable.Rows) - { - updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; - addedTable.Rows.Add(updatedRow); - } - } - } - - // set summary information properties - if (!this.SuppressKeepingSpecialRows) - { - var summaryInfoTable = transform.Tables["_SummaryInformation"]; - this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); - } - - this.Transform = transform; - return this.Transform; - } - - /// - /// Add a row to the using the primary key. - /// - /// The indexed rows. - /// The row to index. - private void AddIndexedRow(Dictionary index, Row row) - { - var primaryKey = row.GetPrimaryKey(); - - if (null != primaryKey) - { - if (index.TryGetValue(primaryKey, out var collisionRow)) - { -#if TODO_PATCH // This case doesn't seem like it can happen any longer. - // Overriding WixActionRows have a primary key defined and take precedence in the index. - if (row is WixActionRow actionRow) - { - // If the current row is not overridable, see if the indexed row is. - if (!actionRow.Overridable) - { - if (collisionRow is WixActionRow indexedRow && indexedRow.Overridable) - { - // The indexed key is overridable and should be replaced. - index[primaryKey] = actionRow; - } - } - - // If we got this far, the row does not need to be indexed. - return; - } -#endif - - if (this.ShowPedanticMessages) - { - this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); - } - } - else - { - index.Add(primaryKey, row); - } - } - else // use the string representation of the row as its primary key (it may not be unique) - { - // this is provided for compatibility with unreal tables with no primary key - // all real tables must specify at least one column as the primary key - primaryKey = row.ToString(); - index[primaryKey] = row; - } - } - - private bool CompareRows(Table targetTable, Row targetRow, Row updatedRow, out Row comparedRow) - { - comparedRow = null; - - var keepRow = false; - - if (null == targetRow ^ null == updatedRow) - { - if (null == targetRow) - { - updatedRow.Operation = RowOperation.Add; - comparedRow = updatedRow; - } - else if (null == updatedRow) - { - targetRow.Operation = RowOperation.Delete; - targetRow.SectionId += sectionDelimiter; - - comparedRow = targetRow; - keepRow = true; - } - } - else // possibly modified - { - updatedRow.Operation = RowOperation.None; - if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) - { - // ignore rows that shouldn't be in a transform - if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) - { - updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; - comparedRow = updatedRow; - keepRow = true; - } - } - else - { - if (this.PreserveUnchangedRows) - { - keepRow = true; - } - - for (var i = 0; i < updatedRow.Fields.Length; i++) - { - var columnDefinition = updatedRow.Fields[i].Column; - - if (!columnDefinition.PrimaryKey) - { - var modified = false; - - if (i >= targetRow.Fields.Length) - { - columnDefinition.Added = true; - modified = true; - } - else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) - { - if (null == targetRow[i] ^ null == updatedRow[i]) - { - modified = true; - } - else if (null != targetRow[i] && null != updatedRow[i]) - { - modified = (targetRow.FieldAsInteger(i) != updatedRow.FieldAsInteger(i)); - } - } - else if (ColumnType.Preserved == columnDefinition.Type) - { - updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); - - // keep rows containing preserved fields so the historical data is available to the binder - keepRow = !this.SuppressKeepingSpecialRows; - } - else if (ColumnType.Object == columnDefinition.Type) - { - var targetObjectField = (ObjectField)targetRow.Fields[i]; - var updatedObjectField = (ObjectField)updatedRow.Fields[i]; - - updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex; - updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri; - - // always keep a copy of the previous data even if they are identical - // This makes diff.wixmst clean and easier to control patch logic - updatedObjectField.PreviousData = (string)targetObjectField.Data; - - // always remember the unresolved data for target build - updatedObjectField.UnresolvedPreviousData = targetObjectField.UnresolvedData; - - // keep rows containing object fields so the files can be compared in the binder - keepRow = !this.SuppressKeepingSpecialRows; - } - else - { - modified = (targetRow.FieldAsString(i) != updatedRow.FieldAsString(i)); - } - - if (modified) - { - if (null != updatedRow.Fields[i].PreviousData) - { - updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); - } - - updatedRow.Fields[i].Modified = true; - updatedRow.Operation = RowOperation.Modify; - keepRow = true; - } - } - } - - if (keepRow) - { - comparedRow = updatedRow; - comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; - } - } - } - - return keepRow; - } - - private List CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) - { - var rows = new List(); - operation = TableOperation.None; - - // dropped tables - if (null == updatedTable ^ null == targetTable) - { - if (null == targetTable) - { - operation = TableOperation.Add; - rows.AddRange(updatedTable.Rows); - } - else if (null == updatedTable) - { - operation = TableOperation.Drop; - } - } - else // possibly modified tables - { - var updatedPrimaryKeys = new Dictionary(); - var targetPrimaryKeys = new Dictionary(); - - // compare the table definitions - if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) - { - // continue to the next table; may be more mismatches - this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); - } - else - { - this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); - - // diff the target and updated rows - foreach (var targetPrimaryKeyEntry in targetPrimaryKeys) - { - var targetPrimaryKey = targetPrimaryKeyEntry.Key; - var targetRow = targetPrimaryKeyEntry.Value; - updatedPrimaryKeys.TryGetValue(targetPrimaryKey, out var updatedRow); - - var keepRow = this.CompareRows(targetTable, targetRow, updatedRow, out var compared); - - if (keepRow) - { - rows.Add(compared); - } - } - - // find the inserted rows - foreach (var updatedPrimaryKeyEntry in updatedPrimaryKeys) - { - var updatedPrimaryKey = updatedPrimaryKeyEntry.Key; - - if (!targetPrimaryKeys.ContainsKey(updatedPrimaryKey)) - { - var updatedRow = updatedPrimaryKeyEntry.Value; - - updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; - rows.Add(updatedRow); - } - } - } - } - - return rows; - } - - private void IndexPrimaryKeys(Table targetTable, Dictionary targetPrimaryKeys, Table updatedTable, Dictionary updatedPrimaryKeys) - { - // index the target rows - foreach (var row in targetTable.Rows) - { - this.AddIndexedRow(targetPrimaryKeys, row); - - if ("Property" == targetTable.Name) - { - var id = row.FieldAsString(0); - - if ("ProductCode" == id) - { - this.transformSummaryInfo.TargetProductCode = row.FieldAsString(1); - - if ("*" == this.transformSummaryInfo.TargetProductCode) - { - this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); - } - } - else if ("ProductVersion" == id) - { - this.transformSummaryInfo.TargetProductVersion = row.FieldAsString(1); - } - else if ("UpgradeCode" == id) - { - this.transformSummaryInfo.TargetUpgradeCode = row.FieldAsString(1); - } - } - else if ("_SummaryInformation" == targetTable.Name) - { - var id = row.FieldAsInteger(0); - - if (1 == id) // PID_CODEPAGE - { - this.transformSummaryInfo.TargetSummaryInfoCodepage = row.FieldAsString(1); - } - else if (7 == id) // PID_TEMPLATE - { - this.transformSummaryInfo.TargetPlatformAndLanguage = row.FieldAsString(1); - } - else if (14 == id) // PID_PAGECOUNT - { - this.transformSummaryInfo.TargetMinimumVersion = row.FieldAsString(1); - } - } - } - - // index the updated rows - foreach (var row in updatedTable.Rows) - { - this.AddIndexedRow(updatedPrimaryKeys, row); - - if ("Property" == updatedTable.Name) - { - var id = row.FieldAsString(0); - - if ("ProductCode" == id) - { - this.transformSummaryInfo.UpdatedProductCode = row.FieldAsString(1); - - if ("*" == this.transformSummaryInfo.UpdatedProductCode) - { - this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); - } - } - else if ("ProductVersion" == id) - { - this.transformSummaryInfo.UpdatedProductVersion = row.FieldAsString(1); - } - } - else if ("_SummaryInformation" == updatedTable.Name) - { - var id = row.FieldAsInteger(0); - - if (1 == id) // PID_CODEPAGE - { - this.transformSummaryInfo.UpdatedSummaryInfoCodepage = row.FieldAsString(1); - } - else if (7 == id) // PID_TEMPLATE - { - this.transformSummaryInfo.UpdatedPlatformAndLanguage = row.FieldAsString(1); - } - else if (14 == id) // PID_PAGECOUNT - { - this.transformSummaryInfo.UpdatedMinimumVersion = row.FieldAsString(1); - } - } - } - } - - private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) - { - // calculate the minimum version of MSI required to process the transform - var minimumVersion = 100; - - if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out var targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out var updatedMin)) - { - minimumVersion = Math.Max(targetMin, updatedMin); - } - - var summaryRows = new Dictionary(summaryInfoTable.Rows.Count); - - foreach (var row in summaryInfoTable.Rows) - { - var id = row.FieldAsInteger(0); - - summaryRows[id] = row; - - if ((int)SummaryInformation.Transform.CodePage == id) - { - row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; - row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; - } - else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == id) - { - row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; - } - else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == id) - { - row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; - } - else if ((int)SummaryInformation.Transform.ProductCodes == id) - { - row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); - } - else if ((int)SummaryInformation.Transform.InstallerRequirement == id) - { - row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); - } - else if ((int)SummaryInformation.Transform.Security == id) - { - row[1] = "4"; - } - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) - { - var summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; - summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) - { - var summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; - summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) - { - var summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; - summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.InstallerRequirement)) - { - var summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; - summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); - } - - if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) - { - var summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.Security; - summaryRow[1] = "4"; - } - } - - private class SummaryInformationStreams - { - public string TargetSummaryInfoCodepage { get; set; } - - public string TargetPlatformAndLanguage { get; set; } - - public string TargetProductCode { get; set; } - - public string TargetProductVersion { get; set; } - - public string TargetUpgradeCode { get; set; } - - public string TargetMinimumVersion { get; set; } - - public string UpdatedSummaryInfoCodepage { get; set; } - - public string UpdatedPlatformAndLanguage { get; set; } - - public string UpdatedProductCode { get; set; } - - public string UpdatedProductVersion { get; set; } - - public string UpdatedMinimumVersion { get; set; } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs deleted file mode 100644 index 949d5e18..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs +++ /dev/null @@ -1,157 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class GetFileFacadesCommand - { - public GetFileFacadesCommand(IntermediateSection section, IWindowsInstallerBackendHelper backendHelper) - { - this.Section = section; - this.BackendHelper = backendHelper; - } - - private IntermediateSection Section { get; } - - private IWindowsInstallerBackendHelper BackendHelper { get; } - - public List FileFacades { get; private set; } - - public void Execute() - { - var facades = new List(); - - var assemblyFile = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); -#if TODO_PATCHING_DELTA - //var deltaPatchFiles = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); -#endif - - foreach (var file in this.Section.Symbols.OfType()) - { - assemblyFile.TryGetValue(file.Id.Id, out var assembly); - -#if TODO_PATCHING_DELTA - //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile); - // TODO: should we be passing along delta information to the file facade? Probably, right? -#endif - var fileFacade = this.BackendHelper.CreateFileFacade(file, assembly); - - facades.Add(fileFacade); - } - -#if TODO_PATCHING_DELTA - this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); -#endif - - this.FileFacades = facades; - } - -#if TODO_PATCHING_DELTA - /// - /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. - /// - public void ResolveDeltaPatchSymbolPaths(Dictionary deltaPatchFiles, IEnumerable facades) - { - ILookup filesByComponent = null; - ILookup filesByDirectory = null; - ILookup filesByDiskId = null; - - foreach (var row in this.Section.Symbols.OfType().OrderBy(r => r.SymbolType)) - { - switch (row.SymbolType) - { - case SymbolPathType.File: - this.MergeSymbolPaths(row, deltaPatchFiles[row.SymbolId]); - break; - - case SymbolPathType.Component: - if (null == filesByComponent) - { - filesByComponent = facades.ToLookup(f => f.File.ComponentRef); - } - - foreach (var facade in filesByComponent[row.SymbolId]) - { - this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); - } - break; - - case SymbolPathType.Directory: - if (null == filesByDirectory) - { - filesByDirectory = facades.ToLookup(f => f.File.DirectoryRef); - } - - foreach (var facade in filesByDirectory[row.SymbolId]) - { - this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); - } - break; - - case SymbolPathType.Media: - if (null == filesByDiskId) - { - filesByDiskId = facades.ToLookup(f => f.File.DiskId.ToString(CultureInfo.InvariantCulture)); - } - - foreach (var facade in filesByDiskId[row.SymbolId]) - { - this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); - } - break; - - case SymbolPathType.Product: - foreach (var fileRow in deltaPatchFiles.Values) - { - this.MergeSymbolPaths(row, fileRow); - } - break; - - default: - // error - break; - } - } - } - - /// - /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row. - /// - /// Row from the WixPatchSymbolsPaths table. - /// FileRow into which to set symbol information. - /// This includes PreviousData as well. - private void MergeSymbolPaths(WixDeltaPatchSymbolPathsSymbol row, WixDeltaPatchFileSymbol file) - { - if (file.SymbolPaths is null) - { - file.SymbolPaths = row.SymbolPaths; - } - else - { - file.SymbolPaths = String.Concat(file.SymbolPaths, ";", row.SymbolPaths); - } - - Field field = row.Fields[2]; - if (null != field.PreviousData) - { - if (null == file.PreviousSymbols) - { - file.PreviousSymbols = field.PreviousData; - } - else - { - file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData); - } - } - } -#endif - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs deleted file mode 100644 index 2ac563ac..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs +++ /dev/null @@ -1,174 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class GetFileFacadesFromTransforms - { - public GetFileFacadesFromTransforms(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, FileSystemManager fileSystemManager, IEnumerable subStorages) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.FileSystemManager = fileSystemManager; - this.SubStorages = subStorages; - } - - private IMessaging Messaging { get; } - - private IWindowsInstallerBackendHelper BackendHelper { get; } - - private FileSystemManager FileSystemManager { get; } - - private IEnumerable SubStorages { get; } - - public List FileFacades { get; private set; } - - public void Execute() - { - var allFileRows = new List(); - - var patchMediaFileRows = new Dictionary>(); - - //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); - - // Index paired transforms by name without their "#" prefix. - var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); - - // Enumerate through main transforms. - foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) - { - var mainTransform = substorage.Data; - var mainFileTable = mainTransform.Tables["File"]; - - if (null == mainFileTable) - { - continue; - } - - // Index File table of pairedTransform - var pairedTransform = pairedTransforms["#" + substorage.Name]; - var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); - - foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete)) - { - var mainFileId = mainFileRow.File; - - // We need compare the underlying files and include all file changes. - var objectField = (ObjectField)mainFileRow.Fields[9]; - var pairedFileRow = pairedFileRows.Get(mainFileId); - - // If the file is new, we always need to add it to the patch. - if (mainFileRow.Operation == RowOperation.Add) - { - if (null != pairedFileRow) // RowOperation.Add - { - // Always patch-added, but never non-compressed. - pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; - pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - pairedFileRow.Fields[6].Modified = true; - pairedFileRow.Operation = RowOperation.Add; - } - } - else - { - // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. - if (null == objectField.PreviousData) - { - if (mainFileRow.Operation == RowOperation.None) - { - continue; - } - } - else - { - // TODO: should this entire condition be placed in the binder file manager? - if (/*(0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&*/ - !this.FileSystemManager.CompareFiles(objectField.PreviousData, objectField.Data.ToString())) - { - // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. - mainFileRow.Operation = RowOperation.Modify; - if (null != pairedFileRow) - { - // Always patch-added, but never non-compressed. - pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; - pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - pairedFileRow.Fields[6].Modified = true; - pairedFileRow.Operation = RowOperation.Modify; - } - } - else - { - // The File is same. We need mark all the attributes as unchanged. - mainFileRow.Operation = RowOperation.None; - foreach (var field in mainFileRow.Fields) - { - field.Modified = false; - } - - if (null != pairedFileRow) - { - pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded; - pairedFileRow.Fields[6].Modified = false; - pairedFileRow.Operation = RowOperation.None; - } - continue; - } - } - } - - // index patch files by diskId+fileId - var diskId = mainFileRow.DiskId; - - if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) - { - mediaFileRows = new RowDictionary(); - patchMediaFileRows.Add(diskId, mediaFileRows); - } - - var patchFileRow = mediaFileRows.Get(mainFileId); - - if (null == patchFileRow) - { - //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); - patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers); - mainFileRow.CopyTo(patchFileRow); - - mediaFileRows.Add(patchFileRow); - -#if TODO_PATCHING_DELTA - // TODO: should we be passing along delta information to the file facade? Probably, right? -#endif - var fileFacade = this.BackendHelper.CreateFileFacade(patchFileRow); - - allFileRows.Add(fileFacade); - } - else - { - // TODO: confirm the rest of data is identical? - - // make sure Source is same. Otherwise we are silently ignoring a file. - if (0 != String.Compare(patchFileRow.Source, mainFileRow.Source, StringComparison.OrdinalIgnoreCase)) - { - this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, mainFileId, patchFileRow.Source, mainFileRow.Source)); - } - -#if TODO_PATCHING_DELTA - // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. - patchFileRow.AppendPreviousDataFrom(mainFileRow); -#endif - } - } - } - - this.FileFacades = allFileRows; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs deleted file mode 100644 index 2eb95bc5..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs +++ /dev/null @@ -1,215 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class LoadTableDefinitionsCommand - { - public LoadTableDefinitionsCommand(IMessaging messaging, IntermediateSection section, IEnumerable backendExtensions) - { - this.Messaging = messaging; - this.Section = section; - this.BackendExtensions = backendExtensions; - } - - public IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - private IEnumerable BackendExtensions { get; } - - public TableDefinitionCollection TableDefinitions { get; private set; } - - public TableDefinitionCollection Execute() - { - var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); - var customColumnsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - - if (customColumnsById.Any()) - { - foreach (var symbol in this.Section.Symbols.OfType()) - { - var customTableDefinition = this.CreateCustomTable(symbol, customColumnsById); - tableDefinitions.Add(customTableDefinition); - } - } - - foreach (var backendExtension in this.BackendExtensions) - { - foreach (var tableDefinition in backendExtension.TableDefinitions) - { - if (tableDefinitions.Contains(tableDefinition.Name)) - { - this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(backendExtension.GetType().Assembly.Location, tableDefinition.Name)); - } - - tableDefinitions.Add(tableDefinition); - } - } - - this.TableDefinitions = tableDefinitions; - return this.TableDefinitions; - } - - private TableDefinition CreateCustomTable(WixCustomTableSymbol symbol, Dictionary customColumnsById) - { - var columnNames = symbol.ColumnNamesSeparated; - var columns = new List(columnNames.Length); - - foreach (var name in columnNames) - { - var column = customColumnsById[symbol.Id.Id + "/" + name]; - - var type = ColumnType.Unknown; - - if (column.Type == IntermediateFieldType.String) - { - type = column.Localizable ? ColumnType.Localized : ColumnType.String; - } - else if (column.Type == IntermediateFieldType.Number) - { - type = ColumnType.Number; - } - else if (column.Type == IntermediateFieldType.Path) - { - type = ColumnType.Object; - } - - var category = ColumnCategory.Unknown; - switch (column.Category) - { - case WixCustomTableColumnCategoryType.Text: - category = ColumnCategory.Text; - break; - case WixCustomTableColumnCategoryType.UpperCase: - category = ColumnCategory.UpperCase; - break; - case WixCustomTableColumnCategoryType.LowerCase: - category = ColumnCategory.LowerCase; - break; - case WixCustomTableColumnCategoryType.Integer: - category = ColumnCategory.Integer; - break; - case WixCustomTableColumnCategoryType.DoubleInteger: - category = ColumnCategory.DoubleInteger; - break; - case WixCustomTableColumnCategoryType.TimeDate: - category = ColumnCategory.TimeDate; - break; - case WixCustomTableColumnCategoryType.Identifier: - category = ColumnCategory.Identifier; - break; - case WixCustomTableColumnCategoryType.Property: - category = ColumnCategory.Property; - break; - case WixCustomTableColumnCategoryType.Filename: - category = ColumnCategory.Filename; - break; - case WixCustomTableColumnCategoryType.WildCardFilename: - category = ColumnCategory.WildCardFilename; - break; - case WixCustomTableColumnCategoryType.Path: - category = ColumnCategory.Path; - break; - case WixCustomTableColumnCategoryType.Paths: - category = ColumnCategory.Paths; - break; - case WixCustomTableColumnCategoryType.AnyPath: - category = ColumnCategory.AnyPath; - break; - case WixCustomTableColumnCategoryType.DefaultDir: - category = ColumnCategory.DefaultDir; - break; - case WixCustomTableColumnCategoryType.RegPath: - category = ColumnCategory.RegPath; - break; - case WixCustomTableColumnCategoryType.Formatted: - category = ColumnCategory.Formatted; - break; - case WixCustomTableColumnCategoryType.FormattedSddl: - category = ColumnCategory.FormattedSDDLText; - break; - case WixCustomTableColumnCategoryType.Template: - category = ColumnCategory.Template; - break; - case WixCustomTableColumnCategoryType.Condition: - category = ColumnCategory.Condition; - break; - case WixCustomTableColumnCategoryType.Guid: - category = ColumnCategory.Guid; - break; - case WixCustomTableColumnCategoryType.Version: - category = ColumnCategory.Version; - break; - case WixCustomTableColumnCategoryType.Language: - category = ColumnCategory.Language; - break; - case WixCustomTableColumnCategoryType.Binary: - category = ColumnCategory.Binary; - break; - case WixCustomTableColumnCategoryType.CustomSource: - category = ColumnCategory.CustomSource; - break; - case WixCustomTableColumnCategoryType.Cabinet: - category = ColumnCategory.Cabinet; - break; - case WixCustomTableColumnCategoryType.Shortcut: - category = ColumnCategory.Shortcut; - break; - case null: - default: - break; - } - - var modularization = ColumnModularizeType.None; - - switch (column.Modularize) - { - case null: - case WixCustomTableColumnModularizeType.None: - modularization = ColumnModularizeType.None; - break; - case WixCustomTableColumnModularizeType.Column: - modularization = ColumnModularizeType.Column; - break; - case WixCustomTableColumnModularizeType.CompanionFile: - modularization = ColumnModularizeType.CompanionFile; - break; - case WixCustomTableColumnModularizeType.Condition: - modularization = ColumnModularizeType.Condition; - break; - case WixCustomTableColumnModularizeType.ControlEventArgument: - modularization = ColumnModularizeType.ControlEventArgument; - break; - case WixCustomTableColumnModularizeType.ControlText: - modularization = ColumnModularizeType.ControlText; - break; - case WixCustomTableColumnModularizeType.Icon: - modularization = ColumnModularizeType.Icon; - break; - case WixCustomTableColumnModularizeType.Property: - modularization = ColumnModularizeType.Property; - break; - case WixCustomTableColumnModularizeType.SemicolonDelimited: - modularization = ColumnModularizeType.SemicolonDelimited; - break; - } - - var columnDefinition = new ColumnDefinition(name, type, column.Width, column.PrimaryKey, column.Nullable, category, column.MinValue, column.MaxValue, column.KeyTable, column.KeyColumn, column.Set, column.Description, modularization, ColumnType.Localized == type, useCData: true, column.Unreal); - columns.Add(columnDefinition); - } - - var customTable = new TableDefinition(symbol.Id.Id, null, columns, symbol.Unreal); - return customTable; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs deleted file mode 100644 index 6446692e..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs +++ /dev/null @@ -1,331 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Text; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.Native.Msm; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Merge modules into the database at output path. - /// - internal class MergeModulesCommand - { - public MergeModulesCommand(IMessaging messaging, IEnumerable fileFacades, IntermediateSection section, IEnumerable suppressedTableNames, string outputPath, string intermediateFolder) - { - this.Messaging = messaging; - this.FileFacades = fileFacades; - this.Section = section; - this.SuppressedTableNames = suppressedTableNames ?? Array.Empty(); - this.OutputPath = outputPath; - this.IntermediateFolder = intermediateFolder; - } - - private IMessaging Messaging { get; } - - private IEnumerable FileFacades { get; } - - private IntermediateSection Section { get; } - - private IEnumerable SuppressedTableNames { get; } - - private string OutputPath { get; } - - private string IntermediateFolder { get; } - - public void Execute() - { - var wixMergeSymbols = this.Section.Symbols.OfType().ToList(); - if (!wixMergeSymbols.Any()) - { - return; - } - - IMsmMerge2 merge = null; - var commit = true; - var logOpen = false; - var databaseOpen = false; - var logPath = Path.Combine(this.IntermediateFolder, "merge.log"); - - try - { - merge = MsmInterop.GetMsmMerge(); - - merge.OpenLog(logPath); - logOpen = true; - - merge.OpenDatabase(this.OutputPath); - databaseOpen = true; - - var featureModulesByMergeId = this.Section.Symbols.OfType().GroupBy(t => t.WixMergeRef).ToDictionary(g => g.Key); - - // process all the merge rows - foreach (var wixMergeRow in wixMergeSymbols) - { - var moduleOpen = false; - - try - { - short mergeLanguage; - - try - { - mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); - } - catch (FormatException) - { - this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.Language.ToString())); - continue; - } - - this.Messaging.Write(VerboseMessages.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.Messaging.Write(VerboseMessages.MergingMergeModule(wixMergeRow.SourceFile)); - merge.MergeEx(wixMergeRow.FeatureRef, wixMergeRow.DirectoryRef, callback); - - // Connect any non-primary features. - if (featureModulesByMergeId.TryGetValue(wixMergeRow.Id.Id, out var featureModules)) - { - foreach (var featureModule in featureModules) - { - this.Messaging.Write(VerboseMessages.ConnectingMergeModule(wixMergeRow.SourceFile, featureModule.FeatureRef)); - merge.Connect(featureModule.FeatureRef); - } - } - } - catch (COMException) - { - commit = false; - } - finally - { - var mergeErrors = merge.Errors; - - // display all the errors encountered during the merge operations for this module - for (var i = 1; i <= mergeErrors.Count; i++) - { - var mergeError = mergeErrors[i]; - var databaseKeys = new StringBuilder(); - var moduleKeys = new StringBuilder(); - - // build a string of the database keys - for (var 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 (var 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.Messaging.Write(ErrorMessages.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleKeys.ToString())); - break; - case MsmErrorType.msmErrorFeatureRequired: - this.Messaging.Write(ErrorMessages.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id.Id)); - break; - case MsmErrorType.msmErrorLanguageFailed: - this.Messaging.Write(ErrorMessages.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); - break; - case MsmErrorType.msmErrorLanguageUnsupported: - this.Messaging.Write(ErrorMessages.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); - break; - case MsmErrorType.msmErrorResequenceMerge: - this.Messaging.Write(WarningMessages.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.Messaging.Write(WarningMessages.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); - } - break; - case MsmErrorType.msmErrorPlatformMismatch: - this.Messaging.Write(ErrorMessages.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); - break; - default: - this.Messaging.Write(ErrorMessages.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, "Encountered an unexpected merge error of type '{0}' for which there is currently no error message to display. More information about the merge and the failure can be found in the merge log: '{1}'", Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); - break; - } - } - - if (0 >= mergeErrors.Count && !commit) - { - this.Messaging.Write(ErrorMessages.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, "Encountered an unexpected error while merging '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'", 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.Messaging.EncounteredError) - { - return; - } - - using (var db = new Database(this.OutputPath, OpenDatabase.Direct)) - { - // Suppress individual actions. - foreach (var suppressAction in this.Section.Symbols.OfType()) - { - var tableName = suppressAction.SequenceTable.WindowsInstallerTableName(); - if (db.TableExists(tableName)) - { - var query = $"SELECT * FROM {tableName} WHERE `Action` = '{suppressAction.Action}'"; - - using (var view = db.OpenExecuteView(query)) - using (var record = view.Fetch()) - { - if (null != record) - { - this.Messaging.Write(WarningMessages.SuppressMergedAction(suppressAction.Action, tableName)); - view.Modify(ModifyView.Delete, record); - } - } - } - } - - // Query for merge module actions in suppressed sequences and drop them. - foreach (var tableName in this.SuppressedTableNames) - { - if (!db.TableExists(tableName)) - { - continue; - } - - using (var view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) - { - foreach (var resultRecord in view.Records) - { - this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName)); - } - } - - // drop suppressed sequences - using (var view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) - { - } - - // delete the validation rows - using (var view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) - using (var record = new Record(1)) - { - record.SetString(1, tableName); - view.Execute(record); - } - } - - // now update the Attributes column for the files from the Merge Modules - this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles()); - using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) - { - foreach (var file in this.FileFacades) - { - if (!file.FromModule) - { - continue; - } - - using (var record = new Record(1)) - { - record.SetString(1, file.Id); - view.Execute(record); - } - - using (var 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.Sequence); - - // Update the file attributes to match the compression specified - // on the Merge element or on the Package element. - var attributes = 0; - - // Get the current value if its not null. - if (!recordUpdate.IsNull(2)) - { - attributes = recordUpdate.GetInteger(2); - } - - if (file.Compressed) - { - attributes |= WindowsInstallerConstants.MsidbFileAttributesCompressed; - attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - } - else if (file.Uncompressed) - { - attributes |= WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; - } - else // clear all compression bits. - { - attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; - attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - } - - recordUpdate.SetInteger(2, attributes); - - view.Modify(ModifyView.Update, recordUpdate); - } - } - } - - db.Commit(); - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs deleted file mode 100644 index 04f1b771..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs +++ /dev/null @@ -1,236 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class ModularizeCommand - { - public ModularizeCommand(IBackendHelper backendHelper, WindowsInstallerData output, string modularizationSuffix, IEnumerable suppressSymbols) - { - this.BackendHelper = backendHelper; - this.Output = output; - this.ModularizationSuffix = modularizationSuffix; - - // Gather all the unique suppress modularization identifiers. - this.SuppressModularizationIdentifiers = new HashSet(suppressSymbols.Select(s => s.SuppressIdentifier)); - } - - private IBackendHelper BackendHelper { get; } - - private WindowsInstallerData Output { get; } - - private string ModularizationSuffix { get; } - - private HashSet SuppressModularizationIdentifiers { get; } - - public void Execute() - { - foreach (var table in this.Output.Tables) - { - this.ModularizeTable(table); - } - } - - private void ModularizeTable(Table table) - { - var modularizedColumns = new List(); - - // find the modularized columns - for (var i = 0; i < table.Definition.Columns.Length; ++i) - { - if (ColumnModularizeType.None != table.Definition.Columns[i].ModularizeType) - { - modularizedColumns.Add(i); - } - } - - if (0 < modularizedColumns.Count) - { - foreach (var row in table.Rows) - { - foreach (var modularizedColumn in modularizedColumns) - { - var field = row.Fields[modularizedColumn]; - - if (field.Data != null) - { - field.Data = this.ModularizedRowFieldValue(row, field); - } - } - } - } - } - - private string ModularizedRowFieldValue(Row row, Field field) - { - var fieldData = field.AsString(); - - if (!(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData))) - { - var modularizeType = field.Column.ModularizeType; - - // special logic for the ControlEvent table's Argument column - // this column requires different modularization methods depending upon the value of the Event column - if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType) - { - switch (row[2].ToString()) - { - case "CheckExistingTargetPath": // redirectable property name - case "CheckTargetPath": - case "DoAction": // custom action name - case "NewDialog": // dialog name - case "SelectionBrowse": - case "SetTargetPath": - case "SpawnDialog": - case "SpawnWaitDialog": - if (this.BackendHelper.IsValidIdentifier(fieldData)) - { - modularizeType = ColumnModularizeType.Column; - } - else - { - modularizeType = ColumnModularizeType.Property; - } - break; - default: // formatted - modularizeType = ColumnModularizeType.Property; - break; - } - } - else if (ColumnModularizeType.ControlText == field.Column.ModularizeType) - { - // icons are stored in the Binary table, so they get column-type modularization - if (("Bitmap" == row[2].ToString() || "Icon" == row[2].ToString()) && this.BackendHelper.IsValidIdentifier(fieldData)) - { - modularizeType = ColumnModularizeType.Column; - } - else - { - modularizeType = ColumnModularizeType.Property; - } - } - - switch (modularizeType) - { - case ColumnModularizeType.Column: - // ensure the value is an identifier (otherwise it shouldn't be modularized this way) - if (!this.BackendHelper.IsValidIdentifier(fieldData)) - { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData)); - } - - // if we're not supposed to suppress modularization of this identifier - if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) - { - fieldData = String.Concat(fieldData, this.ModularizationSuffix); - } - break; - - case ColumnModularizeType.Property: - case ColumnModularizeType.Condition: - Regex regex; - if (ColumnModularizeType.Property == modularizeType) - { - regex = new Regex(@"\[(?[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture); - } - else - { - Debug.Assert(ColumnModularizeType.Condition == modularizeType); - - // This heinous looking regular expression is actually quite an elegant way - // to shred the entire condition into the identifiers that need to be - // modularized. Let's break it down piece by piece: - // - // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the - // regular expression is case insensitive so we don't have to worry about - // all the permutations of these strings. - // 2. Look for quoted strings. Quoted strings are just text and are ignored - // outright. - // 3. Look for environment variables. These look like identifiers we might - // otherwise be interested in but start with a percent sign. Like quoted - // strings these enviroment variable references are ignored outright. - // 4. Match all identifiers that are things that need to be modularized. Note - // the special characters (!, $, ?, &) that denote Component and Feature states. - regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); - - // less performant version of the above with captures showing where everything lives - // regex = new Regex(@"(?NOT|EQV|XOR|OR|AND|IMP)|(?"".*?"")|(?%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); - } - - var matches = regex.Matches(fieldData); - - var sb = new StringBuilder(fieldData); - - // Notice how this code walks backward through the list - // because it modifies the string as we through it. - for (var i = matches.Count - 1; 0 <= i; i--) - { - var group = matches[i].Groups["identifier"]; - if (group.Success) - { - var identifier = group.Value; - if (!WindowsInstallerStandard.IsStandardProperty(identifier) && !this.SuppressModularizationIdentifiers.Contains(identifier)) - { - sb.Insert(group.Index + group.Length, this.ModularizationSuffix); - } - } - } - - fieldData = sb.ToString(); - break; - - case ColumnModularizeType.CompanionFile: - // if we're not supposed to ignore this identifier and the value does not start with - // a digit, we must have a companion file so modularize it - if (!this.SuppressModularizationIdentifiers.Contains(fieldData) && - 0 < fieldData.Length && !Char.IsDigit(fieldData, 0)) - { - fieldData = String.Concat(fieldData, this.ModularizationSuffix); - } - break; - - case ColumnModularizeType.Icon: - if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) - { - var start = fieldData.LastIndexOf(".", StringComparison.Ordinal); - if (-1 == start) - { - fieldData = String.Concat(fieldData, this.ModularizationSuffix); - } - else - { - fieldData = String.Concat(fieldData.Substring(0, start), this.ModularizationSuffix, fieldData.Substring(start)); - } - } - break; - - case ColumnModularizeType.SemicolonDelimited: - var keys = fieldData.Split(';'); - for (var i = 0; i < keys.Length; ++i) - { - if (!String.IsNullOrEmpty(keys[i])) - { - keys[i] = String.Concat(keys[i], this.ModularizationSuffix); - } - } - - fieldData = String.Join(";", keys); - break; - } - } - - return fieldData; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs deleted file mode 100644 index 5dd4d3ea..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs +++ /dev/null @@ -1,119 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class OptimizeFileFacadesOrderCommand - { - public OptimizeFileFacadesOrderCommand(IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform, List fileFacades) - { - this.BackendHelper = helper; - this.PathResolver = pathResolver; - this.Section = section; - this.Platform = platform; - this.FileFacades = fileFacades; - } - - public List FileFacades { get; private set; } - - private IBackendHelper BackendHelper { get; } - - private IPathResolver PathResolver { get; } - - private IntermediateSection Section { get; } - - private Platform Platform { get; } - - public List Execute() - { - var canonicalComponentTargetPaths = this.ComponentTargetPaths(); - - this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths, this.Section.Type == SectionType.Module)); - - return this.FileFacades; - } - - private Dictionary ComponentTargetPaths() - { - var directories = this.ResolveDirectories(); - - var canonicalPathsByDirectoryId = new Dictionary(); - foreach (var component in this.Section.Symbols.OfType()) - { - var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(directories, null, component.DirectoryRef, this.Platform); - canonicalPathsByDirectoryId.Add(component.Id.Id, directoryPath); - } - - return canonicalPathsByDirectoryId; - } - - private Dictionary ResolveDirectories() - { - var targetPathsByDirectoryId = new Dictionary(); - - // Get the target paths for all directories. - foreach (var directory in this.Section.Symbols.OfType()) - { - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); - targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); - } - - return targetPathsByDirectoryId; - } - - private class FileFacadeOptimizer : IComparer - { - public FileFacadeOptimizer(Dictionary componentTargetPaths, bool optimizingMergeModule) - { - this.ComponentTargetPaths = componentTargetPaths; - this.OptimizingMergeModule = optimizingMergeModule; - } - - private Dictionary ComponentTargetPaths { get; } - - private bool OptimizingMergeModule { get; } - - public int Compare(IFileFacade x, IFileFacade y) - { - // First group files by DiskId but ignore if processing a Merge Module - // because Merge Modules don't have separate disks. - var compare = this.OptimizingMergeModule ? 0 : x.DiskId.CompareTo(y.DiskId); - - if (compare != 0) - { - return compare; - } - - // Next try to group files by target install directory. - if (this.ComponentTargetPaths.TryGetValue(x.ComponentRef, out var canonicalX) && - this.ComponentTargetPaths.TryGetValue(y.ComponentRef, out var canonicalY)) - { - compare = String.Compare(canonicalX, canonicalY, StringComparison.Ordinal); - - if (compare != 0) - { - return compare; - } - } - - // TODO: Consider sorting these facades even smarter by file size or file extension - // or other creative ideas to get optimal install speed out of MSI. - compare = String.Compare(x.FileName, y.FileName, StringComparison.Ordinal); - - if (compare != 0) - { - return compare; - } - - return String.Compare(x.Id, y.Id, StringComparison.Ordinal); - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs b/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs deleted file mode 100644 index 4d849753..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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.Bind -{ - using WixToolset.Data.WindowsInstaller; - - internal class PatchTransform - { - public PatchTransform(string baseline, WindowsInstallerData transform) - { - this.Baseline = baseline; - this.Transform = transform; - } - - public string Baseline { get; } - - public WindowsInstallerData Transform { get; } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs deleted file mode 100644 index 1bd2a427..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs +++ /dev/null @@ -1,114 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class ProcessDependencyReferencesCommand - { - // The root registry key for the dependency extension. We write to Software\Classes explicitly - // based on the current security context instead of HKCR. See - // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. - private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; - private const string RegistryDependents = "Dependents"; - - public ProcessDependencyReferencesCommand(IBackendHelper backendHelper, IntermediateSection section, IEnumerable dependencyRefSymbols) - { - this.BackendHelper = backendHelper; - this.Section = section; - this.DependencyRefSymbols = dependencyRefSymbols; - } - - private IBackendHelper BackendHelper { get; } - - private IntermediateSection Section { get; } - - private IEnumerable DependencyRefSymbols { get; } - - public void Execute() - { - var wixDependencyRows = this.Section.Symbols.OfType().ToDictionary(d => d.Id.Id); - var wixDependencyProviderRows = this.Section.Symbols.OfType().ToDictionary(d => d.Id.Id); - - // For each relationship, get the provides and requires rows to generate registry values. - foreach (var wixDependencyRefRow in this.DependencyRefSymbols) - { - var providesId = wixDependencyRefRow.WixDependencyProviderRef; - var requiresId = wixDependencyRefRow.WixDependencyRef; - - // If we do not find both symbols, skip the registry key generation. - if (!wixDependencyRows.TryGetValue(requiresId, out var wixDependencyRow)) - { - continue; - } - - if (!wixDependencyProviderRows.TryGetValue(providesId, out var wixDependencyProviderRow)) - { - continue; - } - - // Format the root registry key using the required provider key and the current provider key. - var requiresKey = wixDependencyRow.Id.Id; - var providesKey = wixDependencyRow.ProviderKey; - var keyRequires = String.Format(@"{0}{1}\{2}\{3}", DependencyRegistryRoot, requiresKey, RegistryDependents, providesKey); - - // Get the component ID from the provider. - var componentId = wixDependencyProviderRow.ParentRef; - - var id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "(Default)"); - this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) - { - ComponentRef = componentId, - Root = RegistryRootType.MachineUser, - Key = keyRequires, - Name = "*", - }); - - if (!String.IsNullOrEmpty(wixDependencyRow.MinVersion)) - { - id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MinVersion"); - this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) - { - ComponentRef = componentId, - Root = RegistryRootType.MachineUser, - Key = keyRequires, - Name = "MinVersion", - Value = wixDependencyRow.MinVersion - }); - } - - var maxVersion = (string)wixDependencyRow[3]; - if (!String.IsNullOrEmpty(wixDependencyRow.MaxVersion)) - { - id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MaxVersion"); - this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) - { - ComponentRef = componentId, - Root = RegistryRootType.MachineUser, - Key = keyRequires, - Name = "MaxVersion", - Value = wixDependencyRow.MaxVersion - }); - } - - if (wixDependencyRow.Attributes != WixDependencySymbolAttributes.None) - { - id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "Attributes"); - this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) - { - ComponentRef = componentId, - Root = RegistryRootType.MachineUser, - Key = keyRequires, - Name = "Attributes", - Value = String.Concat("#", (int)wixDependencyRow.Attributes) - }); - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs deleted file mode 100644 index 9a068603..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs +++ /dev/null @@ -1,131 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - internal class ProcessPackageSoftwareTagsCommand - { - public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags, string intermediateFolder) - { - this.Section = section; - this.SoftwareTags = softwareTags; - this.IntermediateFolder = intermediateFolder; - } - - private string IntermediateFolder { get; } - - private IntermediateSection Section { get; } - - private IEnumerable SoftwareTags { get; } - - public void Execute() - { - string productName = null; - string productVersion = null; - string manufacturer = null; - string upgradeCode = null; - - var summaryInfo = this.Section.Symbols.OfType().FirstOrDefault(s => s.PropertyId == SummaryInformationType.PackageCode); - var packageCode = NormalizeGuid(summaryInfo?.Value); - - foreach (var property in this.Section.Symbols.OfType()) - { - switch (property.Id.Id) - { - case "ProductName": - productName = property.Value; - break; - case "ProductVersion": - productVersion = property.Value; - break; - case "Manufacturer": - manufacturer = property.Value; - break; - case "UpgradeCode": - upgradeCode = NormalizeGuid(property.Value); - break; - } - } - - var fileSymbolsById = this.Section.Symbols.OfType().Where(f => f.Id != null).ToDictionary(f => f.Id.Id); - - var workingFolder = Path.Combine(this.IntermediateFolder, "_swidtag"); - - Directory.CreateDirectory(workingFolder); - - foreach (var tagRow in this.SoftwareTags) - { - if (fileSymbolsById.TryGetValue(tagRow.FileRef, out var fileSymbol)) - { - var uniqueId = String.Concat("msi:package/", packageCode); - var persistentId = String.IsNullOrEmpty(upgradeCode) ? null : String.Concat("msi:upgrade/", upgradeCode); - - // Write the tag file. - fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(workingFolder, fileSymbol.Name) }; - - using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create)) - { - CreateTagFile(fs, uniqueId, productName, productVersion, tagRow.Regid, manufacturer, persistentId); - } - - // Ensure the matching "SoftwareIdentificationTag" row exists and - // is populated correctly. - this.Section.AddSymbol(new SoftwareIdentificationTagSymbol(tagRow.SourceLineNumbers, tagRow.Id) - { - FileRef = fileSymbol.Id.Id, - Regid = tagRow.Regid, - TagId = uniqueId, - PersistentId = persistentId - }); - } - } - } - - private static string NormalizeGuid(string guidString) - { - if (Guid.TryParse(guidString, out var guid)) - { - return guid.ToString("D").ToUpperInvariant(); - } - - return guidString; - } - - private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId) - { - var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; - - using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) - { - writer.WriteStartDocument(); - writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); - writer.WriteAttributeString("tagId", uniqueId); - writer.WriteAttributeString("name", name); - writer.WriteAttributeString("version", version); - writer.WriteAttributeString("versionScheme", versionScheme); - - writer.WriteStartElement("Entity"); - writer.WriteAttributeString("name", manufacturer); - writer.WriteAttributeString("regid", regid); - writer.WriteAttributeString("role", "softwareCreator tagCreator"); - writer.WriteEndElement(); // - - if (!String.IsNullOrEmpty(persistendId)) - { - writer.WriteStartElement("Meta"); - writer.WriteAttributeString("persistentId", persistendId); - writer.WriteEndElement(); // - } - - writer.WriteEndElement(); // - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs deleted file mode 100644 index 217609be..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs +++ /dev/null @@ -1,101 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class ProcessPropertiesCommand - { - public ProcessPropertiesCommand(IntermediateSection section, WixPackageSymbol packageSymbol, int fallbackLcid, bool populateDelayedVariables, IBackendHelper backendHelper) - { - this.Section = section; - this.PackageSymbol = packageSymbol; - this.FallbackLcid = fallbackLcid; - this.PopulateDelayedVariables = populateDelayedVariables; - this.BackendHelper = backendHelper; - } - - private IntermediateSection Section { get; } - - private WixPackageSymbol PackageSymbol { get; } - - private int FallbackLcid { get; } - - private bool PopulateDelayedVariables { get; } - - private IBackendHelper BackendHelper { get; } - - public Dictionary DelayedVariablesCache { get; private set; } - - public string ProductLanguage { get; private set; } - - public void Execute() - { - PropertySymbol languageSymbol = null; - var variableCache = this.PopulateDelayedVariables ? new Dictionary(StringComparer.OrdinalIgnoreCase) : null; - - if (SectionType.Product == this.Section.Type || variableCache != null) - { - foreach (var propertySymbol in this.Section.Symbols.OfType()) - { - // Set the ProductCode if it is to be generated. - if ("ProductCode" == propertySymbol.Id.Id && "*".Equals(propertySymbol.Value, StringComparison.Ordinal)) - { - propertySymbol.Value = this.BackendHelper.CreateGuid(); - -#if TODO_PATCHING // Is this still necessary? - // Update the target ProductCode in any instance transforms. - foreach (SubStorage subStorage in this.Output.SubStorages) - { - Output subStorageOutput = subStorage.Data; - if (OutputType.Transform != subStorageOutput.Type) - { - continue; - } - - Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; - foreach (Row row in instanceSummaryInformationTable.Rows) - { - if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) - { - row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); - break; - } - } - } -#endif - } - else if ("ProductLanguage" == propertySymbol.Id.Id) - { - languageSymbol = propertySymbol; - } - - // Add the property name and value to the variableCache. - if (variableCache != null) - { - variableCache[$"property.{propertySymbol.Id.Id}"] = propertySymbol.Value; - } - } - - if (this.Section.Type == SectionType.Product && String.IsNullOrEmpty(languageSymbol?.Value)) - { - if (languageSymbol == null) - { - languageSymbol = this.Section.AddSymbol(new PropertySymbol(this.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, "ProductLanguage"))); - } - - this.PackageSymbol.Language = this.FallbackLcid.ToString(); - languageSymbol.Value = this.FallbackLcid.ToString(); - } - } - - this.DelayedVariablesCache = variableCache; - this.ProductLanguage = languageSymbol?.Value; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs deleted file mode 100644 index 039ba495..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs +++ /dev/null @@ -1,125 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Defines the file transfers necessary to layout the uncompressed files. - /// - internal class ProcessUncompressedFilesCommand - { - public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver) - { - this.Section = section; - this.BackendHelper = backendHelper; - this.PathResolver = pathResolver; - } - - private IntermediateSection Section { get; } - - public IBackendHelper BackendHelper { get; } - - public IPathResolver PathResolver { get; } - - public string DatabasePath { private get; set; } - - public IEnumerable FileFacades { private get; set; } - - public string LayoutDirectory { private get; set; } - - public bool Compressed { private get; set; } - - public bool LongNamesInImage { private get; set; } - - public Func ResolveMedia { private get; set; } - - public IEnumerable FileTransfers { get; private set; } - - public IEnumerable TrackedFiles { get; private set; } - - public void Execute() - { - var fileTransfers = new List(); - - var trackedFiles = new List(); - - var directories = new Dictionary(); - - var mediaRows = this.Section.Symbols.OfType().ToDictionary(t => t.DiskId); - - using (var db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) - { - using (var directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) - { - foreach (var directoryRecord in directoryView.Records) - { - var sourceName = this.BackendHelper.GetMsiFileName(directoryRecord.GetString(3), true, this.LongNamesInImage); - - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName); - - directories.Add(directoryRecord.GetString(1), resolvedDirectory); - } - } - - using (var fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) - { - using (var fileQueryRecord = new Record(1)) - { - // for each file in the array of uncompressed files - foreach (var facade in this.FileFacades) - { - var mediaSymbol = mediaRows[facade.DiskId]; - string relativeFileLayoutPath = null; - var mediaLayoutFolder = mediaSymbol.Layout; - - var mediaLayoutDirectory = this.ResolveMedia(mediaSymbol, mediaLayoutFolder, this.LayoutDirectory); - - // setup up the query record and find the appropriate file in the - // previously executed file view - fileQueryRecord[1] = facade.Id; - fileView.Execute(fileQueryRecord); - - using (var fileRecord = fileView.Fetch()) - { - if (null == fileRecord) - { - throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.SourceLineNumber, facade.Id)); - } - - relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); - } - - // finally put together the base media layout path and the relative file layout path - var fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); - - var transfer = this.BackendHelper.CreateFileTransfer(facade.SourcePath, fileLayoutPath, false, facade.SourceLineNumber); - fileTransfers.Add(transfer); - - // Track the location where the cabinet will be placed. If the transfer is - // redundant then then the file should not be cleaned. This is important - // because if the source and destination of the transfer is the same, we - // don't want to clean the file because we'd be deleting the original - // (and that would be bad). - var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.SourceLineNumber); - tracked.Clean = !transfer.Redundant; - - trackedFiles.Add(tracked); - } - } - } - } - - this.FileTransfers = fileTransfers; - this.TrackedFiles = trackedFiles; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs deleted file mode 100644 index 94fa0a6a..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs +++ /dev/null @@ -1,714 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - /// - /// Set sequence numbers for all the actions and create symbols in the output object. - /// - internal class SequenceActionsCommand - { - public SequenceActionsCommand(IMessaging messaging, IntermediateSection section) - { - this.Messaging = messaging; - this.Section = section; - - this.RelativeActionsForActions = new Dictionary(); - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - private Dictionary RelativeActionsForActions { get; } - - public void Execute() - { - var requiredActionSymbols = new Dictionary(); - - // Index all the action symbols and look for collisions. - foreach (var actionSymbol in this.Section.Symbols.OfType()) - { - if (actionSymbol.Overridable) // overridable action - { - if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol)) - { - if (collidingActionSymbol.Overridable) - { - this.Messaging.Write(ErrorMessages.OverridableActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - if (null != collidingActionSymbol.SourceLineNumbers) - { - this.Messaging.Write(ErrorMessages.OverridableActionCollision2(collidingActionSymbol.SourceLineNumbers)); - } - } - } - else - { - requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); - } - } - else // unsequenced or sequenced action. - { - // Unsequenced action (allowed for certain standard actions). - if (null == actionSymbol.Before && null == actionSymbol.After && !actionSymbol.Sequence.HasValue) - { - if (WindowsInstallerStandard.TryGetStandardAction(actionSymbol.Id.Id, out var standardAction)) - { - // Populate the sequence from the standard action - actionSymbol.Sequence = standardAction.Sequence; - } - else // not a supported unscheduled action. - { - throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); - } - } - - if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol) && !collidingActionSymbol.Overridable) - { - this.Messaging.Write(ErrorMessages.ActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - if (null != collidingActionSymbol.SourceLineNumbers) - { - this.Messaging.Write(ErrorMessages.ActionCollision2(collidingActionSymbol.SourceLineNumbers)); - } - } - else - { - requiredActionSymbols[actionSymbol.Id.Id] = actionSymbol; - } - } - } - - // Get the standard actions required based on symbols in the section. - var requiredStandardActions = this.GetRequiredStandardActions(); - - // Add the overridable action symbols that are not overridden to the required action symbols. - foreach (var actionSymbol in requiredStandardActions.Values) - { - if (!requiredActionSymbols.ContainsKey(actionSymbol.Id.Id)) - { - requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); - } - } - - // Suppress the required actions that are overridable. - foreach (var suppressActionSymbol in this.Section.Symbols.OfType()) - { - var key = suppressActionSymbol.Id.Id; - - // If there is an overridable symbol to suppress; suppress it. There is no warning if there - // is no action to suppress because the action may be suppressed from a merge module in - // the binder. - if (requiredActionSymbols.TryGetValue(key, out var requiredActionSymbol)) - { - if (requiredActionSymbol.Overridable) - { - this.Messaging.Write(WarningMessages.SuppressAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.Action, suppressActionSymbol.SequenceTable.ToString())); - if (null != requiredActionSymbol.SourceLineNumbers) - { - this.Messaging.Write(WarningMessages.SuppressAction2(requiredActionSymbol.SourceLineNumbers)); - } - - requiredActionSymbols.Remove(key); - } - else // suppressing a non-overridable action symbol - { - this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.SequenceTable.ToString(), suppressActionSymbol.Action)); - if (null != requiredActionSymbol.SourceLineNumbers) - { - this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction2(requiredActionSymbol.SourceLineNumbers)); - } - } - } - } - - // A dictionary used for detecting cyclic references among action symbols. - var firstReference = new Dictionary(); - - // Build up dependency trees of the relatively scheduled actions. - // Use ToList() to create a copy of the required action symbols so that new symbols can - // be added while enumerating. - foreach (var actionSymbol in requiredActionSymbols.Values.ToList()) - { - if (!actionSymbol.Sequence.HasValue) - { - // check for standard actions that don't have a sequence number in a merge module - if (SectionType.Module == this.Section.Type && WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) - { - this.Messaging.Write(ErrorMessages.StandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - } - - this.SequenceActionSymbol(actionSymbol, requiredActionSymbols, firstReference); - } - else if (SectionType.Module == this.Section.Type && 0 < actionSymbol.Sequence && !WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) // check for custom actions and dialogs that have a sequence number - { - this.Messaging.Write(ErrorMessages.CustomActionSequencedInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - } - } - - // Look for standard actions with sequence restrictions that aren't necessarily scheduled based - // on the presence of a particular table. - if (requiredActionSymbols.ContainsKey("InstallExecuteSequence/DuplicateFiles") && !requiredActionSymbols.ContainsKey("InstallExecuteSequence/InstallFiles")) - { - WindowsInstallerStandard.TryGetStandardAction("InstallExecuteSequence/InstallFiles", out var standardAction); - requiredActionSymbols.Add(standardAction.Id.Id, standardAction); - } - - // Schedule actions. - List scheduledActionSymbols; - if (SectionType.Module == this.Section.Type) - { - scheduledActionSymbols = requiredActionSymbols.Values.ToList(); - } - else - { - scheduledActionSymbols = this.ScheduleActions(requiredActionSymbols); - } - - // Remove all existing WixActionSymbols from the section then add the - // scheduled actions back to the section. - var removeActionSymbols = this.Section.Symbols.Where(s => s.Definition.Type == SymbolDefinitionType.WixAction).ToList(); - - foreach (var removeSymbol in removeActionSymbols) - { - this.Section.RemoveSymbol(removeSymbol); - } - - foreach (var action in scheduledActionSymbols) - { - this.Section.AddSymbol(action); - } - } - - private Dictionary GetRequiredStandardActions() - { - var overridableActionSymbols = new Dictionary(); - - var requiredActionIds = this.GetRequiredActionIds(); - - foreach (var actionId in requiredActionIds) - { - WindowsInstallerStandard.TryGetStandardAction(actionId, out var standardAction); - overridableActionSymbols.Add(standardAction.Id.Id, standardAction); - } - - return overridableActionSymbols; - } - - private List ScheduleActions(Dictionary requiredActionSymbols) - { - var scheduledActionSymbols = new List(); - - // Process each sequence table individually. - foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) - { - // Create a collection of just the action symbols in this sequence - var sequenceActionSymbols = requiredActionSymbols.Values.Where(a => a.SequenceTable == sequenceTable).ToList(); - - // Schedule the absolutely scheduled actions (by sorting them by their sequence numbers). - var absoluteActionSymbols = new List(); - foreach (var actionSymbol in sequenceActionSymbols) - { - if (actionSymbol.Sequence.HasValue) - { - // Look for sequence number collisions - foreach (var sequenceScheduledActionSymbol in absoluteActionSymbols) - { - if (sequenceScheduledActionSymbol.Sequence == actionSymbol.Sequence) - { - this.Messaging.Write(WarningMessages.ActionSequenceCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, sequenceScheduledActionSymbol.Action, actionSymbol.Sequence ?? 0)); - if (null != sequenceScheduledActionSymbol.SourceLineNumbers) - { - this.Messaging.Write(WarningMessages.ActionSequenceCollision2(sequenceScheduledActionSymbol.SourceLineNumbers)); - } - } - } - - absoluteActionSymbols.Add(actionSymbol); - } - } - - absoluteActionSymbols.Sort((x, y) => (x.Sequence ?? 0).CompareTo(y.Sequence ?? 0)); - - // Schedule the relatively scheduled actions (by resolving the dependency trees). - var previousUsedSequence = 0; - var relativeActionSymbols = new List(); - for (int j = 0; j < absoluteActionSymbols.Count; j++) - { - var absoluteActionSymbol = absoluteActionSymbols[j]; - - // Get all the relatively scheduled action symbols occuring before and after this absolutely scheduled action symbol. - var relativeActions = this.GetAllRelativeActionsForSequenceType(sequenceTable, absoluteActionSymbol); - - // Check for relatively scheduled actions occuring before/after a special action - // (those actions with a negative sequence number). - if (absoluteActionSymbol.Sequence < 0 && (relativeActions.PreviousActions.Any() || relativeActions.NextActions.Any())) - { - // Create errors for all the before actions. - foreach (var actionSymbol in relativeActions.PreviousActions) - { - this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); - } - - // Create errors for all the after actions. - foreach (var actionSymbol in relativeActions.NextActions) - { - this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); - } - - // If there is source line information for the absolutely scheduled action display it - if (absoluteActionSymbol.SourceLineNumbers != null) - { - this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction2(absoluteActionSymbol.SourceLineNumbers)); - } - - continue; - } - - // Schedule the action symbols before this one. - var unusedSequence = absoluteActionSymbol.Sequence - 1; - for (var i = relativeActions.PreviousActions.Count - 1; i >= 0; i--) - { - var relativeActionSymbol = relativeActions.PreviousActions[i]; - - // look for collisions - if (unusedSequence == previousUsedSequence) - { - this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); - if (absoluteActionSymbol.SourceLineNumbers != null) - { - this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); - } - - unusedSequence++; - } - - relativeActionSymbol.Sequence = unusedSequence; - relativeActionSymbols.Add(relativeActionSymbol); - - unusedSequence--; - } - - // Determine the next used action sequence number. - var nextUsedSequence = Int16.MaxValue + 1; - if (absoluteActionSymbols.Count > j + 1) - { - nextUsedSequence = absoluteActionSymbols[j + 1].Sequence ?? 0; - } - - // Schedule the action symbols after this one. - unusedSequence = absoluteActionSymbol.Sequence + 1; - for (var i = 0; i < relativeActions.NextActions.Count; i++) - { - var relativeActionSymbol = relativeActions.NextActions[i]; - - if (unusedSequence == nextUsedSequence) - { - this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); - if (absoluteActionSymbol.SourceLineNumbers != null) - { - this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); - } - - unusedSequence--; - } - - relativeActionSymbol.Sequence = unusedSequence; - relativeActionSymbols.Add(relativeActionSymbol); - - unusedSequence++; - } - - // keep track of this sequence number as the previous used sequence number for the next iteration - previousUsedSequence = absoluteActionSymbol.Sequence ?? 0; - } - - // add the absolutely and relatively scheduled actions to the list of scheduled actions - scheduledActionSymbols.AddRange(absoluteActionSymbols); - scheduledActionSymbols.AddRange(relativeActionSymbols); - } - - return scheduledActionSymbols; - } - - private IEnumerable GetRequiredActionIds() - { - var set = new HashSet(); - - // gather the required actions for the output type - if (SectionType.Product == this.Section.Type) - { - // AdminExecuteSequence table - set.Add("AdminExecuteSequence/CostFinalize"); - set.Add("AdminExecuteSequence/CostInitialize"); - set.Add("AdminExecuteSequence/FileCost"); - set.Add("AdminExecuteSequence/InstallAdminPackage"); - set.Add("AdminExecuteSequence/InstallFiles"); - set.Add("AdminExecuteSequence/InstallFinalize"); - set.Add("AdminExecuteSequence/InstallInitialize"); - set.Add("AdminExecuteSequence/InstallValidate"); - - // AdminUISequence table - set.Add("AdminUISequence/CostFinalize"); - set.Add("AdminUISequence/CostInitialize"); - set.Add("AdminUISequence/ExecuteAction"); - set.Add("AdminUISequence/FileCost"); - - // AdvtExecuteSequence table - set.Add("AdvertiseExecuteSequence/CostFinalize"); - set.Add("AdvertiseExecuteSequence/CostInitialize"); - set.Add("AdvertiseExecuteSequence/InstallInitialize"); - set.Add("AdvertiseExecuteSequence/InstallFinalize"); - set.Add("AdvertiseExecuteSequence/InstallValidate"); - set.Add("AdvertiseExecuteSequence/PublishFeatures"); - set.Add("AdvertiseExecuteSequence/PublishProduct"); - - // InstallExecuteSequence table - set.Add("InstallExecuteSequence/CostFinalize"); - set.Add("InstallExecuteSequence/CostInitialize"); - set.Add("InstallExecuteSequence/FileCost"); - set.Add("InstallExecuteSequence/InstallFinalize"); - set.Add("InstallExecuteSequence/InstallInitialize"); - set.Add("InstallExecuteSequence/InstallValidate"); - set.Add("InstallExecuteSequence/ProcessComponents"); - set.Add("InstallExecuteSequence/PublishFeatures"); - set.Add("InstallExecuteSequence/PublishProduct"); - set.Add("InstallExecuteSequence/RegisterProduct"); - set.Add("InstallExecuteSequence/RegisterUser"); - set.Add("InstallExecuteSequence/UnpublishFeatures"); - set.Add("InstallExecuteSequence/ValidateProductID"); - - // InstallUISequence table - set.Add("InstallUISequence/CostFinalize"); - set.Add("InstallUISequence/CostInitialize"); - set.Add("InstallUISequence/ExecuteAction"); - set.Add("InstallUISequence/FileCost"); - set.Add("InstallUISequence/ValidateProductID"); - } - - // Gather the required actions for each symbol type. - foreach (var symbolType in this.Section.Symbols.Select(t => t.Definition.Type).Distinct()) - { - switch (symbolType) - { - case SymbolDefinitionType.AppSearch: - set.Add("InstallExecuteSequence/AppSearch"); - set.Add("InstallUISequence/AppSearch"); - break; - case SymbolDefinitionType.CCPSearch: - set.Add("InstallExecuteSequence/AppSearch"); - set.Add("InstallExecuteSequence/CCPSearch"); - set.Add("InstallExecuteSequence/RMCCPSearch"); - set.Add("InstallUISequence/AppSearch"); - set.Add("InstallUISequence/CCPSearch"); - set.Add("InstallUISequence/RMCCPSearch"); - break; - case SymbolDefinitionType.Class: - set.Add("AdvertiseExecuteSequence/RegisterClassInfo"); - set.Add("InstallExecuteSequence/RegisterClassInfo"); - set.Add("InstallExecuteSequence/UnregisterClassInfo"); - break; - case SymbolDefinitionType.Complus: - set.Add("InstallExecuteSequence/RegisterComPlus"); - set.Add("InstallExecuteSequence/UnregisterComPlus"); - break; - case SymbolDefinitionType.Component: - case SymbolDefinitionType.CreateFolder: - set.Add("InstallExecuteSequence/CreateFolders"); - set.Add("InstallExecuteSequence/RemoveFolders"); - break; - case SymbolDefinitionType.DuplicateFile: - set.Add("InstallExecuteSequence/DuplicateFiles"); - set.Add("InstallExecuteSequence/RemoveDuplicateFiles"); - break; - case SymbolDefinitionType.Environment: - set.Add("InstallExecuteSequence/WriteEnvironmentStrings"); - set.Add("InstallExecuteSequence/RemoveEnvironmentStrings"); - break; - case SymbolDefinitionType.Extension: - set.Add("AdvertiseExecuteSequence/RegisterExtensionInfo"); - set.Add("InstallExecuteSequence/RegisterExtensionInfo"); - set.Add("InstallExecuteSequence/UnregisterExtensionInfo"); - break; - case SymbolDefinitionType.File: - set.Add("InstallExecuteSequence/InstallFiles"); - set.Add("InstallExecuteSequence/RemoveFiles"); - - var foundFont = false; - var foundSelfReg = false; - var foundBindPath = false; - foreach (var file in this.Section.Symbols.OfType()) - { - if (!foundFont && !String.IsNullOrEmpty(file.FontTitle)) - { - set.Add("InstallExecuteSequence/RegisterFonts"); - set.Add("InstallExecuteSequence/UnregisterFonts"); - foundFont = true; - } - - if (!foundSelfReg && file.SelfRegCost.HasValue) - { - set.Add("InstallExecuteSequence/SelfRegModules"); - set.Add("InstallExecuteSequence/SelfUnregModules"); - foundSelfReg = true; - } - - if (!foundBindPath && !String.IsNullOrEmpty(file.BindPath)) - { - set.Add("InstallExecuteSequence/BindImage"); - foundBindPath = true; - } - } - break; - case SymbolDefinitionType.IniFile: - set.Add("InstallExecuteSequence/WriteIniValues"); - set.Add("InstallExecuteSequence/RemoveIniValues"); - break; - case SymbolDefinitionType.IsolatedComponent: - set.Add("InstallExecuteSequence/IsolateComponents"); - break; - case SymbolDefinitionType.LaunchCondition: - set.Add("InstallExecuteSequence/LaunchConditions"); - set.Add("InstallUISequence/LaunchConditions"); - break; - case SymbolDefinitionType.MIME: - set.Add("AdvertiseExecuteSequence/RegisterMIMEInfo"); - set.Add("InstallExecuteSequence/RegisterMIMEInfo"); - set.Add("InstallExecuteSequence/UnregisterMIMEInfo"); - break; - case SymbolDefinitionType.MoveFile: - set.Add("InstallExecuteSequence/MoveFiles"); - break; - case SymbolDefinitionType.Assembly: - set.Add("AdvertiseExecuteSequence/MsiPublishAssemblies"); - set.Add("InstallExecuteSequence/MsiPublishAssemblies"); - set.Add("InstallExecuteSequence/MsiUnpublishAssemblies"); - break; - case SymbolDefinitionType.MsiServiceConfig: - case SymbolDefinitionType.MsiServiceConfigFailureActions: - set.Add("InstallExecuteSequence/MsiConfigureServices"); - break; - case SymbolDefinitionType.ODBCDataSource: - case SymbolDefinitionType.ODBCTranslator: - case SymbolDefinitionType.ODBCDriver: - set.Add("InstallExecuteSequence/SetODBCFolders"); - set.Add("InstallExecuteSequence/InstallODBC"); - set.Add("InstallExecuteSequence/RemoveODBC"); - break; - case SymbolDefinitionType.ProgId: - set.Add("AdvertiseExecuteSequence/RegisterProgIdInfo"); - set.Add("InstallExecuteSequence/RegisterProgIdInfo"); - set.Add("InstallExecuteSequence/UnregisterProgIdInfo"); - break; - case SymbolDefinitionType.PublishComponent: - set.Add("AdvertiseExecuteSequence/PublishComponents"); - set.Add("InstallExecuteSequence/PublishComponents"); - set.Add("InstallExecuteSequence/UnpublishComponents"); - break; - case SymbolDefinitionType.Registry: - case SymbolDefinitionType.RemoveRegistry: - set.Add("InstallExecuteSequence/WriteRegistryValues"); - set.Add("InstallExecuteSequence/RemoveRegistryValues"); - break; - case SymbolDefinitionType.RemoveFile: - set.Add("InstallExecuteSequence/RemoveFiles"); - break; - case SymbolDefinitionType.ServiceControl: - set.Add("InstallExecuteSequence/StartServices"); - set.Add("InstallExecuteSequence/StopServices"); - set.Add("InstallExecuteSequence/DeleteServices"); - break; - case SymbolDefinitionType.ServiceInstall: - set.Add("InstallExecuteSequence/InstallServices"); - break; - case SymbolDefinitionType.Shortcut: - set.Add("AdvertiseExecuteSequence/CreateShortcuts"); - set.Add("InstallExecuteSequence/CreateShortcuts"); - set.Add("InstallExecuteSequence/RemoveShortcuts"); - break; - case SymbolDefinitionType.TypeLib: - set.Add("InstallExecuteSequence/RegisterTypeLibraries"); - set.Add("InstallExecuteSequence/UnregisterTypeLibraries"); - break; - case SymbolDefinitionType.Upgrade: - set.Add("InstallExecuteSequence/FindRelatedProducts"); - set.Add("InstallUISequence/FindRelatedProducts"); - - // Only add the MigrateFeatureStates action if MigrateFeature attribute is set on - // at least one UpgradeVersion element. - if (this.Section.Symbols.OfType().Any(t => t.MigrateFeatures)) - { - set.Add("InstallExecuteSequence/MigrateFeatureStates"); - set.Add("InstallUISequence/MigrateFeatureStates"); - } - break; - } - } - - return set; - } - - /// - /// Sequence an action before or after a standard action. - /// - /// The action symbol to be sequenced. - /// Collection of actions which must be included. - /// A dictionary used for detecting cyclic references among action symbols. - private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) - { - var after = false; - - if (actionSymbol.After != null) - { - after = true; - } - else if (actionSymbol.Before == null) - { - throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); - } - - var parentActionName = (after ? actionSymbol.After : actionSymbol.Before); - var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + parentActionName; - - if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) - { - // If the missing parent action is a standard action (with a suggested sequence number), add it. - if (WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol)) - { - // Create a clone to avoid modifying the static copy of the object. - // TODO: consider this: parentActionSymbol = parentActionSymbol.Clone(); - - requiredActionSymbols.Add(parentActionSymbol.Id.Id, parentActionSymbol); - } - else - { - throw new WixException($"Found action {actionSymbol.Id.Id} with a non-existent {(after ? "After" : "Before")} action '{parentActionName}'. The linker should have prevented this."); - } - } - - this.CheckForCircularActionReference(actionSymbol, requiredActionSymbols, firstReference); - - // Add this action to the appropriate list of dependent action symbols. - var relativeActions = this.GetRelativeActions(parentActionSymbol); - var relatedSymbols = (after ? relativeActions.NextActions : relativeActions.PreviousActions); - relatedSymbols.Add(actionSymbol); - } - - /// - /// Check the specified action symbol to see if it leads to a cycle. - /// - /// Use the provided dictionary to note the initial action symbol that first led to each action - /// symbol. Any action symbol encountered that has already been encountered starting from a different - /// initial action symbol inherits the loop characteristics of that initial action symbol, and thus is - /// also not part of a cycle. However, any action symbol encountered that has already been encountered - /// starting from the same initial action symbol is an indication that the current action symbol is - /// part of a cycle. - /// - /// The action symbol to be checked. - /// Collection of actions which must be included. - /// The first encountered action symbol that led to each action symbol. - private void CheckForCircularActionReference(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) - { - WixActionSymbol currentActionSymbol = null; - var parentActionSymbol = actionSymbol; - - do - { - var previousActionSymbol = currentActionSymbol ?? parentActionSymbol; - currentActionSymbol = parentActionSymbol; - - if (!firstReference.TryGetValue(currentActionSymbol, out var existingInitialActionSymbol)) - { - firstReference[currentActionSymbol] = actionSymbol; - } - else if (existingInitialActionSymbol == actionSymbol) - { - this.Messaging.Write(ErrorMessages.ActionCircularDependency(currentActionSymbol.SourceLineNumbers, currentActionSymbol.SequenceTable.ToString(), currentActionSymbol.Action, previousActionSymbol.Action)); - } - - parentActionSymbol = this.GetParentActionSymbol(currentActionSymbol, requiredActionSymbols); - } while (null != parentActionSymbol && !this.Messaging.EncounteredError); - } - - /// - /// Get the action symbol that is the parent of the given action symbol. - /// - /// The given action symbol. - /// Collection of actions which must be included. - /// Null if there is no parent. Used for loop termination. - private WixActionSymbol GetParentActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols) - { - if (null == actionSymbol.Before && null == actionSymbol.After) - { - return null; - } - - var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + (actionSymbol.After ?? actionSymbol.Before); - - if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) - { - WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol); - } - - return parentActionSymbol; - } - - - private RelativeActions GetRelativeActions(WixActionSymbol action) - { - if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions)) - { - relativeActions = new RelativeActions(); - this.RelativeActionsForActions.Add(action.Id.Id, relativeActions); - } - - return relativeActions; - } - - private RelativeActions GetAllRelativeActionsForSequenceType(SequenceTable sequenceType, WixActionSymbol action) - { - var relativeActions = new RelativeActions(); - - if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) - { - this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, relativeActions.PreviousActions); - - this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, relativeActions.NextActions); - } - - return relativeActions; - } - - private void RecurseRelativeActionsForSequenceType(SequenceTable sequenceType, List actions, List visitedActions) - { - foreach (var action in actions.Where(a => a.SequenceTable == sequenceType)) - { - if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) - { - this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, visitedActions); - } - - visitedActions.Add(action); - - if (actionRelatives != null) - { - this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, visitedActions); - } - } - } - - private class RelativeActions - { - public List PreviousActions { get; } = new List(); - - public List NextActions { get; } = new List(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs deleted file mode 100644 index 0f77abfc..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ /dev/null @@ -1,365 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Update file information. - /// - internal class UpdateFileFacadesCommand - { - public UpdateFileFacadesCommand(IMessaging messaging, IntermediateSection section, IEnumerable fileFacades, IEnumerable updateFileFacades, IDictionary variableCache, bool overwriteHash) - { - this.Messaging = messaging; - this.Section = section; - this.FileFacades = fileFacades; - this.UpdateFileFacades = updateFileFacades; - this.VariableCache = variableCache; - this.OverwriteHash = overwriteHash; - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - private IEnumerable FileFacades { get; } - - private IEnumerable UpdateFileFacades { get; } - - private bool OverwriteHash { get; } - - private IDictionary VariableCache { get; } - - public void Execute() - { - var assemblyNameSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - - foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) - { - this.UpdateFileFacade(file, assemblyNameSymbols); - } - } - - private void UpdateFileFacade(IFileFacade facade, Dictionary assemblyNameSymbols) - { - FileInfo fileInfo = null; - try - { - fileInfo = new FileInfo(facade.SourcePath); - } - catch (ArgumentException) - { - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); - return; - } - catch (PathTooLongException) - { - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); - return; - } - catch (NotSupportedException) - { - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); - return; - } - - if (!fileInfo.Exists) - { - this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath)); - return; - } - - using (var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - if (Int32.MaxValue < fileStream.Length) - { - throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath)); - } - - facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); - } - - string version = null; - string language = null; - try - { - Installer.GetFileVersion(fileInfo.FullName, out version, out language); - } - catch (Win32Exception e) - { - if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND - { - throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); - } - else - { - throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); - } - } - - // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. - if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table - { - if (!this.OverwriteHash) - { - // not overwriting hash, so don't do the rest of these options. - } - else if (null != facade.Version) - { - // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks - // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up. - // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing - // all the file rows) for a relatively uncommon situation. Let's not do that. - // - // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version - // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. - if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) - { - this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); - } - } - else - { - if (null != facade.Language) - { - this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); - } - - int[] hash; - try - { - Installer.GetFileHash(fileInfo.FullName, 0, out hash); - } - catch (Win32Exception e) - { - if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND - { - throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); - } - else - { - throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); - } - } - - if (null == facade.Hash) - { - facade.Hash = this.Section.AddSymbol(new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)); - } - - facade.Hash.Options = 0; - facade.Hash.HashPart1 = hash[0]; - facade.Hash.HashPart2 = hash[1]; - facade.Hash.HashPart3 = hash[2]; - facade.Hash.HashPart4 = hash[3]; - } - } - else // update the file row with the version and language information. - { - // If no version was provided by the user, use the version from the file itself. - // This is the most common case. - if (String.IsNullOrEmpty(facade.Version)) - { - facade.Version = version; - } - else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. - { - // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching - // the version value). We didn't find it so, we will override the default version they provided with the actual - // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match - // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case - // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more - // CPU intensive search to save on the memory intensive index that wouldn't be used much. - // - // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. - // That's typically even more rare than companion files so again, no index, just search. - facade.Version = version; - } - - if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language)) - { - this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); - } - else // override the default provided by the user (usually nothing) with the actual language from the file itself. - { - facade.Language = language; - } - - // Populate the binder variables for this file information if requested. - if (null != this.VariableCache) - { - if (!String.IsNullOrEmpty(facade.Version)) - { - var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.Id); - this.VariableCache[key] = facade.Version; - } - - if (!String.IsNullOrEmpty(facade.Language)) - { - var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.Id); - this.VariableCache[key] = facade.Language; - } - } - } - - // If this is a CLR assembly, load the assembly and get the assembly name information - if (AssemblyType.DotNetAssembly == facade.AssemblyType) - { - try - { - var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version); - - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "culture", assemblyName.Culture); - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); - - if (!String.IsNullOrEmpty(assemblyName.Architecture)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); - } - // TODO: WiX v3 seemed to do this but not clear it should actually be done. - //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) - //{ - // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); - //} - - if (assemblyName.StrongNamedSigned) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); - } - else if (facade.AssemblyApplicationFileRef == null) - { - throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); - } - - if (!String.IsNullOrEmpty(assemblyName.FileVersion)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "fileVersion", assemblyName.FileVersion); - } - - // add the assembly name to the information cache - if (null != this.VariableCache) - { - this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); - } - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - } - else if (AssemblyType.Win32Assembly == facade.AssemblyType) - { - // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through - // all files like this. Even though this is a rare case it looks like we might be able to index the - // file earlier. - var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal)); - if (null == fileManifest) - { - this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef)); - } - - try - { - var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); - - if (!String.IsNullOrEmpty(assemblyName.Name)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); - } - - if (!String.IsNullOrEmpty(assemblyName.Version)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); - } - - if (!String.IsNullOrEmpty(assemblyName.Type)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "type", assemblyName.Type); - } - - if (!String.IsNullOrEmpty(assemblyName.Architecture)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); - } - - if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) - { - this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); - } - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - } - } - - /// - /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise - /// create a new row. - /// - /// MsiAssemblyName table. - /// FileFacade containing the assembly read for the MsiAssemblyName row. - /// MsiAssemblyName name. - /// MsiAssemblyName value. - private void SetMsiAssemblyName(Dictionary assemblyNameSymbols, IFileFacade facade, string name, string value) - { - // check for null value (this can occur when grabbing the file version from an assembly without one) - if (String.IsNullOrEmpty(value)) - { - this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name)); - } - else - { - // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail. - if ("name" == name && AssemblyType.DotNetAssembly == facade.AssemblyType && - String.IsNullOrEmpty(facade.AssemblyApplicationFileRef) && - !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase)) - { - this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value)); - } - - // override directly authored value - var lookup = String.Concat(facade.ComponentRef, "/", name); - if (!assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol)) - { - assemblyNameSymbol = this.Section.AddSymbol(new MsiAssemblyNameSymbol(facade.SourceLineNumber, new Identifier(AccessModifier.Section, facade.ComponentRef, name)) - { - ComponentRef = facade.ComponentRef, - Name = name, - Value = value, - }); - - if (null == facade.AssemblyNames) - { - facade.AssemblyNames = new List(); - } - - facade.AssemblyNames.Add(assemblyNameSymbol); - - assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol); - } - - assemblyNameSymbol.Value = value; - - if (this.VariableCache != null) - { - var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant(); - this.VariableCache[key] = value; - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs deleted file mode 100644 index 66a648cc..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs +++ /dev/null @@ -1,77 +0,0 @@ -// 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.Bind -{ - using System; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class UpdateFromTextFilesCommand - { - public UpdateFromTextFilesCommand(IMessaging messaging, IntermediateSection section) - { - this.Messaging = messaging; - this.Section = section; - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - public void Execute() - { - foreach (var bbControl in this.Section.Symbols.OfType().Where(t => t.SourceFile != null)) - { - bbControl.Text = this.ReadTextFile(bbControl.SourceLineNumbers, bbControl.SourceFile.Path); - } - - foreach (var control in this.Section.Symbols.OfType().Where(t => t.SourceFile != null)) - { - control.Text = this.ReadTextFile(control.SourceLineNumbers, control.SourceFile.Path); - } - - foreach (var customAction in this.Section.Symbols.OfType().Where(c => c.ScriptFile != null)) - { - customAction.Target = this.ReadTextFile(customAction.SourceLineNumbers, customAction.ScriptFile.Path); - } - } - - /// - /// Reads a text file and returns the contents. - /// - /// Source line numbers for row from source. - /// Source path to file to read. - /// Text string read from file. - private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source) - { - try - { - using (var reader = new StreamReader(source)) - { - return reader.ReadToEnd(); - } - } - catch (DirectoryNotFoundException e) - { - this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); - } - catch (FileNotFoundException e) - { - this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); - } - catch (IOException e) - { - this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); - } - catch (NotSupportedException) - { - this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, source)); - } - - return null; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs deleted file mode 100644 index affec09f..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs +++ /dev/null @@ -1,109 +0,0 @@ -// 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.Bind -{ - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - - internal class UpdateMediaSequencesCommand - { - public UpdateMediaSequencesCommand(IntermediateSection section, IEnumerable fileFacades) - { - this.Section = section; - this.FileFacades = fileFacades; - } - - private IntermediateSection Section { get; } - - private IEnumerable FileFacades { get; } - - public void Execute() - { - var mediaRows = this.Section.Symbols.OfType().ToDictionary(t => t.DiskId); - - // Calculate sequence numbers and media disk id layout for all file media information objects. - if (SectionType.Module == this.Section.Type) - { - var lastSequence = 0; - - foreach (var facade in this.FileFacades) - { - facade.Sequence = ++lastSequence; - } - } - else - { - var lastSequence = 0; - MediaSymbol mediaSymbol = null; - var patchGroups = new Dictionary>(); - - // Sequence the non-patch-added files. - foreach (var facade in this.FileFacades) - { - if (null == mediaSymbol) - { - mediaSymbol = mediaRows[facade.DiskId]; - if (SectionType.Patch == this.Section.Type) - { - // patch Media cannot start at zero - lastSequence = mediaSymbol.LastSequence ?? 1; - } - } - else if (mediaSymbol.DiskId != facade.DiskId) - { - mediaSymbol.LastSequence = lastSequence; - mediaSymbol = mediaRows[facade.DiskId]; - } - - if (facade.PatchGroup.HasValue) - { - if (patchGroups.TryGetValue(facade.PatchGroup.Value, out var patchGroup)) - { - patchGroup = new List(); - patchGroups.Add(facade.PatchGroup.Value, patchGroup); - } - - patchGroup.Add(facade); - } - else if (!facade.FromModule) - { - facade.Sequence = ++lastSequence; - } - } - - if (null != mediaSymbol) - { - mediaSymbol.LastSequence = lastSequence; - mediaSymbol = null; - } - - // Sequence the patch-added files. - foreach (var patchGroup in patchGroups.Values) - { - foreach (var facade in patchGroup) - { - if (null == mediaSymbol) - { - mediaSymbol = mediaRows[facade.DiskId]; - } - else if (mediaSymbol.DiskId != facade.DiskId) - { - mediaSymbol.LastSequence = lastSequence; - mediaSymbol = mediaRows[facade.DiskId]; - } - - facade.Sequence = ++lastSequence; - } - } - - if (null != mediaSymbol) - { - mediaSymbol.LastSequence = lastSequence; - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs deleted file mode 100644 index 981fa0a4..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs +++ /dev/null @@ -1,451 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class UpdateTransformsWithFileFacades - { - public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable subStorages, TableDefinitionCollection tableDefinitions, IEnumerable fileFacades) - { - this.Messaging = messaging; - this.Output = output; - this.SubStorages = subStorages; - this.TableDefinitions = tableDefinitions; - this.FileFacades = fileFacades; - } - - private IMessaging Messaging { get; } - - private WindowsInstallerData Output { get; } - - private IEnumerable SubStorages { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - private IEnumerable FileFacades { get; } - - public void Execute() - { - var fileFacadesByDiskId = new Dictionary>(); - - // Index patch file facades by diskId+fileId. - foreach (var facade in this.FileFacades) - { - if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades)) - { - mediaFacades = new Dictionary(); - fileFacadesByDiskId.Add(facade.DiskId, mediaFacades); - } - - mediaFacades.Add(facade.Id, facade); - } - - var patchMediaRows = new RowDictionary(this.Output.Tables["Media"]); - - // Index paired transforms by name without the "#" prefix. - var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); - - // Copy File bind data into substorages - foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) - { - var mainTransform = substorage.Data; - - var mainMsiFileHashIndex = new RowDictionary(mainTransform.Tables["MsiFileHash"]); - - var pairedTransform = pairedTransforms["#" + substorage.Name]; - - // Copy Media.LastSequence. - var pairedMediaTable = pairedTransform.Tables["Media"]; - foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) - { - var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); - pairedMediaRow.LastSequence = patchMediaRow.LastSequence; - } - - // Validate file row changes for keypath-related issues - this.ValidateFileRowChanges(mainTransform); - - // Index File table of pairedTransform - var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); - - var mainFileTable = mainTransform.Tables["File"]; - if (null != mainFileTable) - { - // Remove the MsiFileHash table because it will be updated later with the final file hash for each file - mainTransform.Tables.Remove("MsiFileHash"); - - foreach (FileRow mainFileRow in mainFileTable.Rows) - { - if (RowOperation.Delete == mainFileRow.Operation) - { - continue; - } - else if (RowOperation.None == mainFileRow.Operation) - { - continue; - } - - // Index patch files by diskId+fileId - if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades)) - { - mediaFacades = new Dictionary(); - fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades); - } - - // copy data from the patch back to the transform - if (mediaFacades.TryGetValue(mainFileRow.File, out var facade)) - { - var patchFileRow = facade.GetFileRow(); - var pairedFileRow = pairedFileRows.Get(mainFileRow.File); - - for (var i = 0; i < patchFileRow.Fields.Length; i++) - { - var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty; - var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty; - - if (1 == i) - { - // File.Component_ changes should not come from the shared file rows - // that contain the file information as each individual transform might - // have different changes (or no changes at all). - } - else if (6 == i) // File.Attributes should not changed for binary deltas - { -#if TODO_PATCHING_DELTA - if (null != patchFileRow.Patch) - { - // File.Attribute should not change for binary deltas - pairedFileRow.Attributes = mainFileRow.Attributes; - mainFileRow.Fields[i].Modified = false; - } -#endif - } - else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform - { - // file sequence is updated in Patch table instead of File table for delta patches -#if TODO_PATCHING_DELTA - if (null != patchFileRow.Patch) - { - pairedFileRow.Fields[i].Modified = false; - } - else -#endif - { - pairedFileRow[i] = patchFileRow[i]; - pairedFileRow.Fields[i].Modified = true; - } - mainFileRow.Fields[i].Modified = false; - } - else if (patchValue != mainValue) - { - mainFileRow[i] = patchFileRow[i]; - mainFileRow.Fields[i].Modified = true; - if (mainFileRow.Operation == RowOperation.None) - { - mainFileRow.Operation = RowOperation.Modify; - } - } - } - - // Copy MsiFileHash row for this File. - if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) - { - //patchHashRow = patchFileRow.Hash; - throw new NotImplementedException(); - } - - if (null != patchHashRow) - { - var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); - var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); - for (var i = 0; i < patchHashRow.Fields.Length; i++) - { - mainHashRow[i] = patchHashRow[i]; - if (i > 1) - { - // assume all hash fields have been modified - mainHashRow.Fields[i].Modified = true; - } - } - - // assume the MsiFileHash operation follows the File one - mainHashRow.Operation = mainFileRow.Operation; - } - - // copy MsiAssemblyName rows for this File -#if TODO_PATCHING - List patchAssemblyNameRows = patchFileRow.AssemblyNames; - if (null != patchAssemblyNameRows) - { - var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); - foreach (var patchAssemblyNameRow in patchAssemblyNameRows) - { - // Copy if there isn't an identical modified/added row already in the transform. - var foundMatchingModifiedRow = false; - foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows) - { - if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) - { - foundMatchingModifiedRow = true; - break; - } - } - - if (!foundMatchingModifiedRow) - { - var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); - for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++) - { - mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; - } - - // assume value field has been modified - mainAssemblyNameRow.Fields[2].Modified = true; - mainAssemblyNameRow.Operation = mainFileRow.Operation; - } - } - } -#endif - - // Add patch header for this file -#if TODO_PATCHING_DELTA - if (null != patchFileRow.Patch) - { - // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. - this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); - this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); - - // Add to Patch table - var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); - if (0 == patchTable.Rows.Count) - { - patchTable.Operation = TableOperation.Add; - } - - var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); - patchRow[0] = patchFileRow.File; - patchRow[1] = patchFileRow.Sequence; - - var patchFile = new FileInfo(patchFileRow.Source); - patchRow[2] = (int)patchFile.Length; - patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; - - var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; - if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) - { - streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); - - var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); - if (0 == patchHeadersTable.Rows.Count) - { - patchHeadersTable.Operation = TableOperation.Add; - } - - var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); - patchHeadersRow[0] = streamName; - patchHeadersRow[1] = patchFileRow.Patch; - patchRow[5] = streamName; - patchHeadersRow.Operation = RowOperation.Add; - } - else - { - patchRow[4] = patchFileRow.Patch; - } - patchRow.Operation = RowOperation.Add; - } -#endif - } - else - { - // TODO: throw because all transform rows should have made it into the patch - } - } - } - - this.Output.Tables.Remove("Media"); - this.Output.Tables.Remove("File"); - this.Output.Tables.Remove("MsiFileHash"); - this.Output.Tables.Remove("MsiAssemblyName"); - } - } - - /// - /// Adds the PatchFiles action to the sequence table if it does not already exist. - /// - /// The sequence table to check or modify. - /// The primary authoring transform. - /// The secondary patch transform. - /// The file row that contains information about the patched file. - private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow) - { - var tableName = table.ToString(); - - // Find/add PatchFiles action (also determine sequence for it). - // Search mainTransform first, then pairedTransform (pairedTransform overrides). - var hasPatchFilesAction = false; - var installFilesSequence = 0; - var duplicateFilesSequence = 0; - - TestSequenceTableForPatchFilesAction( - mainTransform.Tables[tableName], - ref hasPatchFilesAction, - ref installFilesSequence, - ref duplicateFilesSequence); - TestSequenceTableForPatchFilesAction( - pairedTransform.Tables[tableName], - ref hasPatchFilesAction, - ref installFilesSequence, - ref duplicateFilesSequence); - if (!hasPatchFilesAction) - { - WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionSymbol); - - var sequence = patchFilesActionSymbol.Sequence; - - // Test for default sequence value's appropriateness - if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence)) - { - if (0 != duplicateFilesSequence) - { - if (duplicateFilesSequence < installFilesSequence) - { - throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action)); - } - else - { - sequence = (duplicateFilesSequence + installFilesSequence) / 2; - if (installFilesSequence == sequence || duplicateFilesSequence == sequence) - { - throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action)); - } - } - } - else - { - sequence = installFilesSequence + 1; - } - } - - var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); - if (0 == sequenceTable.Rows.Count) - { - sequenceTable.Operation = TableOperation.Add; - } - - var patchAction = sequenceTable.CreateRow(null); - patchAction[0] = patchFilesActionSymbol.Action; - patchAction[1] = patchFilesActionSymbol.Condition; - patchAction[2] = sequence; - patchAction.Operation = RowOperation.Add; - } - } - - /// - /// Tests sequence table for PatchFiles and associated actions - /// - /// The table to test. - /// Set to true if PatchFiles action is found. Left unchanged otherwise. - /// Set to sequence value of InstallFiles action if found. Left unchanged otherwise. - /// Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise. - private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence) - { - if (null != sequenceTable) - { - foreach (var row in sequenceTable.Rows) - { - var actionName = row.FieldAsString(0); - switch (actionName) - { - case "PatchFiles": - hasPatchFilesAction = true; - break; - - case "InstallFiles": - installFilesSequence = row.FieldAsInteger(2); - break; - - case "DuplicateFiles": - duplicateFilesSequence = row.FieldAsInteger(2); - break; - } - } - } - } - - /// - /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. - /// - /// The output to validate. - private void ValidateFileRowChanges(WindowsInstallerData transform) - { - var componentTable = transform.Tables["Component"]; - var fileTable = transform.Tables["File"]; - - // There's no sense validating keypaths if the transform has no component or file table - if (componentTable == null || fileTable == null) - { - return; - } - - var componentKeyPath = new Dictionary(componentTable.Rows.Count); - - // Index the Component table for non-directory & non-registry key paths. - foreach (var row in componentTable.Rows) - { - var keyPath = row.FieldAsString(5); - if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) - { - componentKeyPath.Add(row.FieldAsString(0), keyPath); - } - } - - var componentWithChangedKeyPath = new Dictionary(); - var componentWithNonKeyPathChanged = new Dictionary(); - // Verify changes in the file table, now that file diffing has occurred - foreach (FileRow row in fileTable.Rows) - { - if (RowOperation.Modify != row.Operation) - { - continue; - } - - var fileId = row.FieldAsString(0); - var componentId = row.FieldAsString(1); - - // If this file is the keypath of a component - if (componentKeyPath.ContainsValue(fileId)) - { - if (!componentWithChangedKeyPath.ContainsKey(componentId)) - { - componentWithChangedKeyPath.Add(componentId, fileId); - } - } - else - { - if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) - { - componentWithNonKeyPathChanged.Add(componentId, fileId); - } - } - } - - foreach (var componentFile in componentWithNonKeyPathChanged) - { - // Make sure all changes to non keypath files also had a change in the keypath. - if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath)) - { - this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath)); - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs deleted file mode 100644 index cf1e21c2..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs +++ /dev/null @@ -1,187 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ValidateDatabaseCommand : IWindowsInstallerValidatorCallback - { - // Set of ICEs that have equivalent-or-better checks in WiX. - private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" }; - - public ValidateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable cubeFiles, IEnumerable ices, IEnumerable suppressedIces) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.Data = data; - this.OutputPath = outputPath; - this.CubeFiles = cubeFiles; - this.Ices = ices; - this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces); - - this.IntermediateFolder = intermediateFolder; - this.OutputSourceLineNumber = new SourceLineNumber(outputPath); - } - - public IEnumerable TrackedFiles { get; private set; } - - /// - /// Encountered error implementation for . - /// - public bool EncounteredError => this.Messaging.EncounteredError; - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private WindowsInstallerData Data { get; } - - private string OutputPath { get; } - - private IEnumerable CubeFiles { get; } - - private IEnumerable Ices { get; } - - private IEnumerable SuppressedIces { get; } - - private string IntermediateFolder { get; } - - /// - /// Fallback when an exact source line number cannot be calculated for a validation error. - /// - private SourceLineNumber OutputSourceLineNumber { get; set; } - - private Dictionary SourceLineNumbersByTablePrimaryKey { get; set; } - - public void Execute() - { - var trackedFiles = new List(); - var stopwatch = Stopwatch.StartNew(); - - this.Messaging.Write(VerboseMessages.ValidatingDatabase()); - - // Ensure the temporary files can be created the working folder. - var workingFolder = Path.Combine(this.IntermediateFolder, "_validate"); - Directory.CreateDirectory(workingFolder); - - // Copy the database to a temporary location so it can be manipulated. - // Ensure it is not read-only. - var workingDatabasePath = Path.Combine(workingFolder, Path.GetFileName(this.OutputPath)); - FileSystem.CopyFile(this.OutputPath, workingDatabasePath, allowHardlink: false); - - var trackWorkingDatabase = this.BackendHelper.TrackFile(workingDatabasePath, TrackedFileType.Temporary); - trackedFiles.Add(trackWorkingDatabase); - - var attributes = File.GetAttributes(workingDatabasePath); - File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly); - - var validator = new WindowsInstallerValidator(this, workingDatabasePath, this.CubeFiles, this.Ices, this.SuppressedIces); - validator.Execute(); - - stopwatch.Stop(); - this.Messaging.Write(VerboseMessages.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); - - - this.TrackedFiles = trackedFiles; - } - - private void LogValidationMessage(ValidationMessage message) - { - var messageSourceLineNumbers = this.OutputSourceLineNumber; - if (!String.IsNullOrEmpty(message.Table) && !String.IsNullOrEmpty(message.Column) && message.PrimaryKeys != null) - { - messageSourceLineNumbers = this.GetSourceLineNumbers(message.Table, message.PrimaryKeys); - } - - switch (message.Type) - { - case ValidationMessageType.InternalFailure: - case ValidationMessageType.Error: - this.Messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, message.IceName, message.Description)); - break; - case ValidationMessageType.Warning: - this.Messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, message.IceName, message.Description)); - break; - case ValidationMessageType.Info: - this.Messaging.Write(VerboseMessages.ValidationInfo(message.IceName, message.Description)); - break; - default: - throw new WixException(ErrorMessages.InvalidValidatorMessageType(message.Type.ToString())); - } - } - - /// - /// Validation blocked by other installation operation for . - /// - public void ValidationBlocked() - { - this.Messaging.Write(VerboseMessages.ValidationSerialized()); - } - - /// - /// Validation message implementation for . - /// - public bool ValidationMessage(ValidationMessage message) - { - this.LogValidationMessage(message); - return true; - } - - /// - /// Gets the source line information (if available) for a row by its table name and primary key. - /// - /// The table name of the row. - /// The primary keys of the row. - /// The source line number information if found; null otherwise. - private SourceLineNumber GetSourceLineNumbers(string tableName, IEnumerable primaryKeys) - { - // Source line information only exists if an output file was supplied - if (this.Data == null) - { - // Use the file name as the source line information. - return this.OutputSourceLineNumber; - } - - // Index the source line information if it hasn't been indexed already. - if (this.SourceLineNumbersByTablePrimaryKey == null) - { - this.SourceLineNumbersByTablePrimaryKey = new Dictionary(); - - // Index each real table - foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) - { - // Index each row that contain source line information - foreach (var row in table.Rows.Where(r => r.SourceLineNumbers != null)) - { - // Index the row using its table name and primary key - var primaryKey = row.GetPrimaryKey(';'); - - if (!String.IsNullOrEmpty(primaryKey)) - { - try - { - var key = String.Concat(table.Name, ":", primaryKey); - this.SourceLineNumbersByTablePrimaryKey.Add(key, row.SourceLineNumbers); - } - catch (ArgumentException) - { - this.Messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); - } - } - } - } - } - - return this.SourceLineNumbersByTablePrimaryKey.TryGetValue(String.Concat(tableName, ":", String.Join(";", primaryKeys)), out var sourceLineNumbers) ? sourceLineNumbers : null; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs deleted file mode 100644 index aeda4443..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs +++ /dev/null @@ -1,96 +0,0 @@ -// 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.Decompile -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.IO; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class DecompileMsiOrMsmCommand - { - public DecompileMsiOrMsmCommand(IDecompileContext context, IEnumerable backendExtensions) - { - this.Context = context; - this.Extensions = backendExtensions; - this.Messaging = context.ServiceProvider.GetService(); - } - - private IDecompileContext Context { get; } - - private IEnumerable Extensions { get; } - - private IMessaging Messaging { get; } - - public IDecompileResult Execute() - { - var result = this.Context.ServiceProvider.GetService(); - - try - { - using (var database = new Database(this.Context.DecompilePath, OpenDatabase.ReadOnly)) - { - // Delete the directory and its files to prevent cab extraction failure due to an existing file. - if (Directory.Exists(this.Context.ExtractFolder)) - { - Directory.Delete(this.Context.ExtractFolder, true); - } - - var backendHelper = this.Context.ServiceProvider.GetService(); - - var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, database, this.Context.DecompilePath, this.Context.DecompileType, this.Context.ExtractFolder, this.Context.IntermediateFolder, this.Context.IsAdminImage, suppressDemodularization: false, skipSummaryInfo: false); - var output = unbindCommand.Execute(); - var extractedFilePaths = new List(unbindCommand.ExportedFiles); - - var decompiler = new Decompiler(this.Messaging, backendHelper, this.Extensions, this.Context.BaseSourcePath, this.Context.SuppressCustomTables, this.Context.SuppressDroppingEmptyTables, this.Context.SuppressUI, this.Context.TreatProductAsModule); - result.Document = decompiler.Decompile(output); - - result.Platform = GetPlatformFromOutput(output); - - // extract the files from the cabinets - if (!String.IsNullOrEmpty(this.Context.ExtractFolder) && !this.Context.SuppressExtractCabinets) - { - var fileDirectory = String.IsNullOrEmpty(this.Context.CabinetExtractFolder) ? Path.Combine(this.Context.ExtractFolder, "File") : this.Context.CabinetExtractFolder; - - var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.DecompilePath, fileDirectory, this.Context.IntermediateFolder, this.Context.TreatProductAsModule); - extractCommand.Execute(); - - extractedFilePaths.AddRange(extractCommand.ExtractedFiles); - result.ExtractedFilePaths = extractedFilePaths; - } - else - { - result.ExtractedFilePaths = new string[0]; - } - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(ErrorMessages.OpenDatabaseFailed(this.Context.DecompilePath)); - } - - throw; - } - - return result; - } - - private static Platform? GetPlatformFromOutput(WindowsInstallerData output) - { - var template = output.Tables["_SummaryInformation"]?.Rows.SingleOrDefault(row => row.FieldAsInteger(0) == 7)?.FieldAsString(1); - - return Decompiler.GetPlatformFromTemplateSummaryInformation(template?.Split(';')); - - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs deleted file mode 100644 index 0b45a8b3..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs +++ /dev/null @@ -1,7596 +0,0 @@ -// 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.Decompile -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Text.RegularExpressions; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - /// - /// Decompiles an msi database into WiX source. - /// - internal class Decompiler - { - private static readonly Regex NullSplitter = new Regex(@"\[~]"); - - // NameToBit arrays - private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; - private static readonly string[] HyperlinkControlAttributes = { "Transparent" }; - private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; - private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; - private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; - private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; - private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; - private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; - private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; - private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; - private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; - private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; - private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; - private XElement uiElement; - - /// - /// Creates a new decompiler object with a default set of table definitions. - /// - public Decompiler(IMessaging messaging, IBackendHelper backendHelper, IEnumerable extensions, string baseSourcePath, bool suppressCustomTables, bool suppressDroppingEmptyTables, bool suppressUI, bool treatProductAsModule) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.Extensions = extensions; - this.BaseSourcePath = baseSourcePath ?? "SourceDir"; - this.SuppressCustomTables = suppressCustomTables; - this.SuppressDroppingEmptyTables = suppressDroppingEmptyTables; - this.SuppressUI = suppressUI; - this.TreatProductAsModule = treatProductAsModule; - - this.ExtensionsByTableName = new Dictionary(); - this.StandardActions = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id); - - this.TableDefinitions = new TableDefinitionCollection(); - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private IEnumerable Extensions { get; } - - private Dictionary ExtensionsByTableName { get; } - - private string BaseSourcePath { get; } - - private bool SuppressCustomTables { get; } - - private bool SuppressDroppingEmptyTables { get; } - - private bool SuppressRelativeActionSequencing { get; } - - private bool SuppressUI { get; } - - private bool TreatProductAsModule { get; } - - private OutputType OutputType { get; set; } - - private Dictionary StandardActions { get; } - - private bool Compressed { get; set; } - - private XElement RootElement { get; set; } - - private TableDefinitionCollection TableDefinitions { get; } - - private bool ShortNames { get; set; } - - private string ModularizationGuid { get; set; } - - private XElement UIElement - { - get - { - if (null == this.uiElement) - { - this.uiElement = new XElement(Names.UIElement); - this.RootElement.Add(this.uiElement); - } - - return this.uiElement; - } - } - - private Dictionary Singletons { get; } = new Dictionary(); - - private Dictionary IndexedElements { get; } = new Dictionary(); - - private Dictionary PatchTargetFiles { get; } = new Dictionary(); - - /// - /// Decompile the database file. - /// - /// The output to decompile. - /// The serialized WiX source code. - public XDocument Decompile(WindowsInstallerData output) - { - if (null == output) - { - throw new ArgumentNullException(nameof(output)); - } - - this.OutputType = output.Type; - - // collect the table definitions from the output - this.TableDefinitions.Clear(); - foreach (var table in output.Tables) - { - this.TableDefinitions.Add(table.Definition); - } - - // add any missing standard and wix-specific table definitions - foreach (var tableDefinition in WindowsInstallerTableDefinitions.All) - { - if (!this.TableDefinitions.Contains(tableDefinition.Name)) - { - this.TableDefinitions.Add(tableDefinition); - } - } - - // add any missing extension table definitions -#if TODO_DECOMPILER_EXTENSIONS - foreach (var extension in this.Extensions) - { - this.AddExtension(extension); - } -#endif - - switch (this.OutputType) - { - case OutputType.Module: - this.RootElement = new XElement(Names.ModuleElement); - break; - case OutputType.PatchCreation: - this.RootElement = new XElement(Names.PatchCreationElement); - break; - case OutputType.Product: - this.RootElement = new XElement(Names.PackageElement); - break; - default: - throw new InvalidOperationException("Unknown output type."); - } - - var xWix = new XElement(Names.WixElement, this.RootElement); - - // try to decompile the database file - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - this.InitializeDecompile(output.Tables, output.Codepage); - - // stop processing if an error previously occurred - if (this.Messaging.EncounteredError) - { - return null; - } - - // decompile the tables - this.DecompileTables(output); - - // finalize the decompiler and its extensions - this.FinalizeDecompile(output.Tables); - - // return the XML document only if decompilation completed successfully - var document = new XDocument(xWix); - return this.Messaging.EncounteredError ? null : document; - } - -#if TODO_DECOMPILER_EXTENSIONS - private void AddExtension(IWindowsInstallerBackendDecompilerExtension extension) - { - if (null != extension.TableDefinitions) - { - foreach (TableDefinition tableDefinition in extension.TableDefinitions) - { - if (!this.ExtensionsByTableName.ContainsKey(tableDefinition.Name)) - { - this.ExtensionsByTableName.Add(tableDefinition.Name, extension); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name)); - } - } - } - } -#endif - - internal static Platform? GetPlatformFromTemplateSummaryInformation(string[] template) - { - if (null != template && 1 < template.Length && null != template[0] && 0 < template[0].Length) - { - switch (template[0]) - { - case "Intel": - return Platform.X86; - case "x64": - return Platform.X64; - case "Arm64": - return Platform.ARM64; - } - } - - return null; - } - - /// - /// Gets the element corresponding to the row it came from. - /// - /// The row corresponding to the element. - /// The indexed element. - private XElement GetIndexedElement(WixToolset.Data.WindowsInstaller.Row row) => this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); - - /// - /// Gets the element corresponding to the primary key of the given table. - /// - /// The table corresponding to the element. - /// The primary key corresponding to the element. - /// The indexed element. - private XElement GetIndexedElement(string table, params string[] primaryKey) => this.IndexedElements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))]; - - /// - /// Tries to get the element corresponding to the primary key of the given table. - /// - /// The table corresponding to the element. - /// The indexed element. - /// Whether the element was found. - private bool TryGetIndexedElement(WixToolset.Data.WindowsInstaller.Row row, out XElement xElement) => this.TryGetIndexedElement(row.TableDefinition.Name, out xElement, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); - - /// - /// Tries to get the element corresponding to the primary key of the given table. - /// - /// The table corresponding to the element. - /// The indexed element. - /// The primary key corresponding to the element. - /// Whether the element was found. - private bool TryGetIndexedElement(string table, out XElement xElement, params string[] primaryKey) => this.IndexedElements.TryGetValue(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), out xElement); - - /// - /// Index an element by its corresponding row. - /// - /// The row corresponding to the element. - /// The element to index. - private void IndexElement(WixToolset.Data.WindowsInstaller.Row row, XElement element) - { - this.IndexedElements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element); - } - - /// - /// Index an element by its corresponding row. - /// - /// The element to index. - /// - /// - private void IndexElement(XElement element, string table, params string[] primaryKey) - { - this.IndexedElements.Add(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), element); - } - - private Dictionary> IndexTableOneToMany(IEnumerable rows, int column = 0) - { - return rows - .ToLookup(row => row.FieldAsString(column), row => this.GetIndexedElement(row)) - .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); - } - - private Dictionary> IndexTableOneToMany(TableIndexedCollection tables, string tableName, int column = 0) => this.IndexTableOneToMany(tables[tableName]?.Rows ?? Enumerable.Empty(), column); - - private Dictionary> IndexTableOneToMany(Table table, int column = 0) => this.IndexTableOneToMany(table?.Rows ?? Enumerable.Empty(), column); - - private void AddChildToParent(string parentName, XElement xChild, Row row, int column) - { - var key = row.FieldAsString(column); - if (this.TryGetIndexedElement(parentName, out var xParent, key)) - { - xParent.Add(xChild); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row.Fields[column].Column.Name, key, parentName)); - } - } - - private static XAttribute XAttributeIfNotNull(string attributeName, Row row, int column) => row.IsColumnNull(column) ? null : new XAttribute(attributeName, row.FieldAsString(column)); - - private static void SetAttributeIfNotNull(XElement xElement, string attributeName, string value) - { - if (!String.IsNullOrEmpty(value)) - { - xElement.SetAttributeValue(attributeName, value); - } - } - - private static void SetAttributeIfNotNull(XElement xElement, string attributeName, int? value) - { - if (value.HasValue) - { - xElement.SetAttributeValue(attributeName, value); - } - } - - /// - /// Convert an Int32 into a DateTime. - /// - /// The Int32 value. - /// The DateTime. - private static DateTime ConvertIntegerToDateTime(int value) - { - var date = value / 65536; - var time = value % 65536; - - return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2); - } - - /// - /// Set the common control attributes in a control element. - /// - /// The control attributes. - /// The control element. - private static void SetControlAttributes(int attributes, XElement xControl) - { - if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesEnabled)) - { - xControl.SetAttributeValue("Disabled", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesIndirect == (attributes & WindowsInstallerConstants.MsidbControlAttributesIndirect)) - { - xControl.SetAttributeValue("Indirect", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesInteger == (attributes & WindowsInstallerConstants.MsidbControlAttributesInteger)) - { - xControl.SetAttributeValue("Integer", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbControlAttributesLeftScroll)) - { - xControl.SetAttributeValue("LeftScroll", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbControlAttributesRightAligned)) - { - xControl.SetAttributeValue("RightAligned", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbControlAttributesRTLRO)) - { - xControl.SetAttributeValue("RightToLeft", "yes"); - } - - if (WindowsInstallerConstants.MsidbControlAttributesSunken == (attributes & WindowsInstallerConstants.MsidbControlAttributesSunken)) - { - xControl.SetAttributeValue("Sunken", "yes"); - } - - if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesVisible)) - { - xControl.SetAttributeValue("Hidden", "yes"); - } - } - - /// - /// Creates an action element. - /// - /// The action from which the element should be created. - private void CreateActionElement(WixActionSymbol actionSymbol) - { - XElement xAction; - - if (this.TryGetIndexedElement("CustomAction", out var _, actionSymbol.Action)) // custom action - { - xAction = new XElement(Names.CustomElement, - new XAttribute("Action", actionSymbol.Action), - String.IsNullOrEmpty(actionSymbol.Condition) ? null : new XAttribute("Condition", actionSymbol.Condition)); - - switch (actionSymbol.Sequence) - { - case (-4): - xAction.SetAttributeValue("OnExit", "suspend"); - break; - case (-3): - xAction.SetAttributeValue("OnExit", "error"); - break; - case (-2): - xAction.SetAttributeValue("OnExit", "cancel"); - break; - case (-1): - xAction.SetAttributeValue("OnExit", "success"); - break; - default: - if (null != actionSymbol.Before) - { - xAction.SetAttributeValue("Before", actionSymbol.Before); - } - else if (null != actionSymbol.After) - { - xAction.SetAttributeValue("After", actionSymbol.After); - } - else if (actionSymbol.Sequence.HasValue) - { - xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); - } - break; - } - } - else if (this.TryGetIndexedElement("Dialog", out var _, actionSymbol.Action)) // dialog - { - xAction = new XElement(Names.CustomElement, - new XAttribute("Dialog", actionSymbol.Action), - new XAttribute("Condition", actionSymbol.Condition)); - - switch (actionSymbol.Sequence) - { - case (-4): - xAction.SetAttributeValue("OnExit", "suspend"); - break; - case (-3): - xAction.SetAttributeValue("OnExit", "error"); - break; - case (-2): - xAction.SetAttributeValue("OnExit", "cancel"); - break; - case (-1): - xAction.SetAttributeValue("OnExit", "success"); - break; - default: - SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); - SetAttributeIfNotNull(xAction, "After", actionSymbol.After); - SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); - break; - } - } - else // possibly a standard action without suggested sequence information - { - xAction = this.CreateStandardActionElement(actionSymbol); - } - - // add the action element to the appropriate sequence element - if (null != xAction) - { - var sequenceTable = actionSymbol.SequenceTable.ToString(); - if (!this.Singletons.TryGetValue(sequenceTable, out var xSequence)) - { - xSequence = new XElement(Names.WxsNamespace + sequenceTable); - - this.RootElement.Add(xSequence); - this.Singletons.Add(sequenceTable, xSequence); - } - - try - { - xSequence.Add(xAction); - } - catch (ArgumentException) // action/dialog is not valid for this sequence - { - this.Messaging.Write(WarningMessages.IllegalActionInSequence(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - } - } - } - - /// - /// Creates a standard action element. - /// - /// The action row from which the element should be created. - /// The created element. - private XElement CreateStandardActionElement(WixActionSymbol actionSymbol) - { - XElement xStandardAction = null; - - switch (actionSymbol.Action) - { - case "AllocateRegistrySpace": - case "BindImage": - case "CostFinalize": - case "CostInitialize": - case "CreateFolders": - case "CreateShortcuts": - case "DeleteServices": - case "DuplicateFiles": - case "ExecuteAction": - case "FileCost": - case "InstallAdminPackage": - case "InstallFiles": - case "InstallFinalize": - case "InstallInitialize": - case "InstallODBC": - case "InstallServices": - case "InstallValidate": - case "IsolateComponents": - case "MigrateFeatureStates": - case "MoveFiles": - case "MsiPublishAssemblies": - case "MsiUnpublishAssemblies": - case "PatchFiles": - case "ProcessComponents": - case "PublishComponents": - case "PublishFeatures": - case "PublishProduct": - case "RegisterClassInfo": - case "RegisterComPlus": - case "RegisterExtensionInfo": - case "RegisterFonts": - case "RegisterMIMEInfo": - case "RegisterProduct": - case "RegisterProgIdInfo": - case "RegisterTypeLibraries": - case "RegisterUser": - case "RemoveDuplicateFiles": - case "RemoveEnvironmentStrings": - case "RemoveFiles": - case "RemoveFolders": - case "RemoveIniValues": - case "RemoveODBC": - case "RemoveRegistryValues": - case "RemoveShortcuts": - case "SelfRegModules": - case "SelfUnregModules": - case "SetODBCFolders": - case "StartServices": - case "StopServices": - case "UnpublishComponents": - case "UnpublishFeatures": - case "UnregisterClassInfo": - case "UnregisterComPlus": - case "UnregisterExtensionInfo": - case "UnregisterFonts": - case "UnregisterMIMEInfo": - case "UnregisterProgIdInfo": - case "UnregisterTypeLibraries": - case "ValidateProductID": - case "WriteEnvironmentStrings": - case "WriteIniValues": - case "WriteRegistryValues": - xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); - break; - - case "AppSearch": - this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var appSearchActionRow); - - if (null != actionSymbol.Before || null != actionSymbol.After || (null != appSearchActionRow && actionSymbol.Sequence != appSearchActionRow.Sequence)) - { - xStandardAction = new XElement(Names.AppSearchElement); - - SetAttributeIfNotNull(xStandardAction, "Condition", actionSymbol.Condition); - SetAttributeIfNotNull(xStandardAction, "Before", actionSymbol.Before); - SetAttributeIfNotNull(xStandardAction, "After", actionSymbol.After); - SetAttributeIfNotNull(xStandardAction, "Sequence", actionSymbol.Sequence); - - return xStandardAction; - } - break; - - case "CCPSearch": - case "DisableRollback": - case "FindRelatedProducts": - case "ForceReboot": - case "InstallExecute": - case "InstallExecuteAgain": - case "LaunchConditions": - case "RemoveExistingProducts": - case "ResolveSource": - case "RMCCPSearch": - case "ScheduleReboot": - xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); - Decompiler.SequenceRelativeAction(actionSymbol, xStandardAction); - return xStandardAction; - - default: - this.Messaging.Write(WarningMessages.UnknownAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - return null; - } - - if (xStandardAction != null) - { - this.SequenceStandardAction(actionSymbol, xStandardAction); - } - - return xStandardAction; - } - - /// - /// Applies the condition and sequence to a standard action element based on the action symbol data. - /// - /// Action data from the database. - /// Element to be sequenced. - private void SequenceStandardAction(WixActionSymbol actionSymbol, XElement xAction) - { - xAction.SetAttributeValue("Condition", actionSymbol.Condition); - - if ((null != actionSymbol.Before || null != actionSymbol.After) && 0 == actionSymbol.Sequence) - { - this.Messaging.Write(WarningMessages.DecompiledStandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); - } - else if (actionSymbol.Sequence.HasValue) - { - xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); - } - } - - /// - /// Applies the condition and relative sequence to an action element based on the action row data. - /// - /// Action data from the database. - /// Element to be sequenced. - private static void SequenceRelativeAction(WixActionSymbol actionSymbol, XElement xAction) - { - SetAttributeIfNotNull(xAction, "Condition", actionSymbol.Condition); - SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); - SetAttributeIfNotNull(xAction, "After", actionSymbol.After); - SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); - } - - /// - /// Ensure that a particular property exists in the decompiled output. - /// - /// The identifier of the property. - /// The property element. - private XElement EnsureProperty(string id) - { - XElement xProperty; - - if (!this.TryGetIndexedElement("Property", out xProperty, id)) - { - xProperty = new XElement(Names.PropertyElement, new XAttribute("Id", id)); - - this.RootElement.Add(xProperty); - this.IndexElement(xProperty, "Property", id); - } - - return xProperty; - } - - /// - /// Finalize decompilation. - /// - /// The collection of all tables. - private void FinalizeDecompile(TableIndexedCollection tables) - { - if (OutputType.PatchCreation == this.OutputType) - { - this.FinalizeFamilyFileRangesTable(tables); - } - else - { - this.FinalizeSummaryInformationStream(tables); - this.FinalizeCheckBoxTable(tables); - this.FinalizeComponentTable(tables); - this.FinalizeDialogTable(tables); - this.FinalizeDuplicateMoveFileTables(tables); - this.FinalizeFeatureComponentsTable(tables); - this.FinalizeFileTable(tables); - this.FinalizeMIMETable(tables); - this.FinalizeMsiLockPermissionsExTable(tables); - this.FinalizeLockPermissionsTable(tables); - this.FinalizeProgIdTable(tables); - this.FinalizePropertyTable(tables); - this.FinalizeRemoveFileTable(tables); - this.FinalizeSearchTables(tables); - this.FinalizeShortcutTable(tables); - this.FinalizeUpgradeTable(tables); - this.FinalizeSequenceTables(tables); - this.FinalizeVerbTable(tables); - } - } - - /// - /// Finalize the CheckBox table. - /// - /// The collection of all tables. - /// - /// Enumerates through all the Control rows, looking for controls of type "CheckBox" with - /// a value in the Property column. This is then possibly matched up with a CheckBox row - /// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table. - /// - private void FinalizeCheckBoxTable(TableIndexedCollection tables) - { - // if the user has requested to suppress the UI elements, we have nothing to do - if (this.SuppressUI) - { - return; - } - - var checkBoxTable = tables["CheckBox"]; - var controlTable = tables["Control"]; - - var checkBoxes = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); - var checkBoxProperties = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row => false); - - // enumerate through the Control table, adding CheckBox values where appropriate - if (null != controlTable) - { - foreach (var row in controlTable.Rows) - { - var xControl = this.GetIndexedElement(row); - - if ("CheckBox" == row.FieldAsString(2)) - { - var property = row.FieldAsString(8); - if (!String.IsNullOrEmpty(property) && checkBoxes.TryGetValue(property, out var checkBoxRow)) - { - // if we've seen this property already, create a reference to it - if (checkBoxProperties.TryGetValue(property, out var seen) && seen) - { - xControl.SetAttributeValue("CheckBoxPropertyRef", property); - } - else - { - xControl.SetAttributeValue("Property", property); - checkBoxProperties[property] = true; - } - - xControl.SetAttributeValue("CheckBoxValue", checkBoxRow.FieldAsString(1)); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", row.FieldAsString(8), "CheckBox")); - } - } - } - } - } - - /// - /// Finalize the Component table. - /// - /// The collection of all tables. - /// - /// Set the keypaths for each component. - /// - private void FinalizeComponentTable(TableIndexedCollection tables) - { - var componentTable = tables["Component"]; - var fileTable = tables["File"]; - var odbcDataSourceTable = tables["ODBCDataSource"]; - var registryTable = tables["Registry"]; - - // set the component keypaths - if (null != componentTable) - { - foreach (var row in componentTable.Rows) - { - var attributes = row.FieldAsInteger(3); - var keyPath = row.FieldAsString(5); - - if (String.IsNullOrEmpty(keyPath)) - { - var xComponent = this.GetIndexedElement("Component", row.FieldAsString(0)); - xComponent.SetAttributeValue("KeyPath", "yes"); - } - else if (WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath == (attributes & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) - { - if (this.TryGetIndexedElement("Registry", out var xRegistry, keyPath)) - { - if (xRegistry.Name.LocalName == "RegistryValue") - { - xRegistry.SetAttributeValue("KeyPath", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", keyPath)); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "Registry")); - } - } - else if (WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource == (attributes & WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource)) - { - if (this.TryGetIndexedElement("ODBCDataSource", out var xOdbcDataSource, keyPath)) - { - xOdbcDataSource.SetAttributeValue("KeyPath", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "ODBCDataSource")); - } - } - else - { - if (this.TryGetIndexedElement("File", out var xFile, keyPath)) - { - xFile.SetAttributeValue("KeyPath", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "File")); - } - } - } - } - - // add the File children elements - if (null != fileTable) - { - foreach (FileRow fileRow in fileTable.Rows) - { - if (this.TryGetIndexedElement("Component", out var xComponent, fileRow.Component) - && this.TryGetIndexedElement(fileRow, out var xFile)) - { - xComponent.Add(xFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component")); - } - } - } - - // add the ODBCDataSource children elements - if (null != odbcDataSourceTable) - { - foreach (var row in odbcDataSourceTable.Rows) - { - if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(1)) - && this.TryGetIndexedElement(row, out var xOdbcDataSource)) - { - xComponent.Add(xOdbcDataSource); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); - } - } - } - - // add the Registry children elements - if (null != registryTable) - { - foreach (var row in registryTable.Rows) - { - if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(5)) - && this.TryGetIndexedElement(row, out var xRegistry)) - { - xComponent.Add(xRegistry); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(5), "Component")); - } - } - } - } - - /// - /// Finalize the Dialog table. - /// - /// The collection of all tables. - /// - /// Sets the first, default, and cancel control for each dialog and adds all child control - /// elements to the dialog. - /// - private void FinalizeDialogTable(TableIndexedCollection tables) - { - // if the user has requested to suppress the UI elements, we have nothing to do - if (this.SuppressUI) - { - return; - } - - var addedControls = new HashSet(); - - var controlTable = tables["Control"]; - var controlRows = controlTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); - - var dialogTable = tables["Dialog"]; - if (null != dialogTable) - { - foreach (var dialogRow in dialogTable.Rows) - { - var xDialog = this.GetIndexedElement(dialogRow); - var dialogId = dialogRow.FieldAsString(0); - - if (!this.TryGetIndexedElement("Control", out var xControl, dialogId, dialogRow.FieldAsString(7))) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", dialogRow.FieldAsString(7), "Control")); - } - - // add tabbable controls - while (null != xControl) - { - var controlId = xControl.Attribute("Id"); - var controlRow = controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, controlId)]; - - xControl.SetAttributeValue("TabSkip", "no"); - - xDialog.Add(xControl); - addedControls.Add(xControl); - - var controlNext = controlRow.FieldAsString(10); - if (!String.IsNullOrEmpty(controlNext)) - { - if (this.TryGetIndexedElement("Control", out xControl, dialogId, controlNext)) - { - // looped back to the first control in the dialog - if (addedControls.Contains(xControl)) - { - xControl = null; - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", controlNext, "Control")); - } - } - else - { - xControl = null; - } - } - - // set default control - var controlDefault = dialogRow.FieldAsString(8); - if (!String.IsNullOrEmpty(controlDefault)) - { - if (this.TryGetIndexedElement("Control", out var xDefaultControl, dialogId, controlDefault)) - { - xDefaultControl.SetAttributeValue("Default", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(dialogRow[8]), "Control")); - } - } - - // set cancel control - var controlCancel = dialogRow.FieldAsString(8); - if (!String.IsNullOrEmpty(controlCancel)) - { - if (this.TryGetIndexedElement("Control", out var xCancelControl, dialogId, controlCancel)) - { - xCancelControl.SetAttributeValue("Cancel", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(dialogRow[9]), "Control")); - } - } - } - } - - // add the non-tabbable controls to the dialog - if (null != controlTable) - { - foreach (var controlRow in controlTable.Rows) - { - var dialogId = controlRow.FieldAsString(0); - if (!this.TryGetIndexedElement("Dialog", out var xDialog, dialogId)) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Dialog")); - continue; - } - - var xControl = this.GetIndexedElement(controlRow); - if (!addedControls.Contains(xControl)) - { - xControl.SetAttributeValue("TabSkip", "yes"); - xDialog.Add(xControl); - } - } - } - } - - /// - /// Finalize the DuplicateFile and MoveFile tables. - /// - /// The collection of all tables. - /// - /// Sets the source/destination property/directory for each DuplicateFile or - /// MoveFile row. - /// - private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables) - { - var duplicateFileTable = tables["DuplicateFile"]; - if (null != duplicateFileTable) - { - foreach (var row in duplicateFileTable.Rows) - { - var xCopyFile = this.GetIndexedElement(row); - var destination = row.FieldAsString(4); - if (!String.IsNullOrEmpty(destination)) - { - if (this.TryGetIndexedElement("Directory", out var _, destination)) - { - xCopyFile.SetAttributeValue("DestinationDirectory", destination); - } - else - { - xCopyFile.SetAttributeValue("DestinationProperty", destination); - } - } - } - } - - var moveFileTable = tables["MoveFile"]; - if (null != moveFileTable) - { - foreach (var row in moveFileTable.Rows) - { - var xCopyFile = this.GetIndexedElement(row); - var source = row.FieldAsString(4); - if (!String.IsNullOrEmpty(source)) - { - if (this.TryGetIndexedElement("Directory", out var _, source)) - { - xCopyFile.SetAttributeValue("SourceDirectory", source); - } - else - { - xCopyFile.SetAttributeValue("SourceProperty", source); - } - } - - var destination = row.FieldAsString(5); - if (this.TryGetIndexedElement("Directory", out var _, destination)) - { - xCopyFile.SetAttributeValue("DestinationDirectory", destination); - } - else - { - xCopyFile.SetAttributeValue("DestinationProperty", destination); - } - } - } - } - - /// - /// Finalize the FamilyFileRanges table. - /// - /// The collection of all tables. - private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables) - { - var familyFileRangesTable = tables["FamilyFileRanges"]; - if (null != familyFileRangesTable) - { - foreach (var row in familyFileRangesTable.Rows) - { - var xProtectRange = new XElement(Names.ProtectRangeElement); - - if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) - { - var retainOffsets = row.FieldAsString(2).Split(','); - var retainLengths = row.FieldAsString(3).Split(','); - - if (retainOffsets.Length == retainLengths.Length) - { - for (var i = 0; i < retainOffsets.Length; i++) - { - if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal)) - { - xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i].Substring(2), 16)); - } - else - { - xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture)); - } - - if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal)) - { - xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i].Substring(2), 16)); - } - else - { - xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture)); - } - } - } - else - { - // TODO: warn - } - } - else if (!row.IsColumnNull(2) || !row.IsColumnNull(3)) - { - // TODO: warn about mismatch between columns - } - - this.IndexElement(row, xProtectRange); - } - } - - var usedProtectRanges = new HashSet(); - var externalFilesTable = tables["ExternalFiles"]; - if (null != externalFilesTable) - { - foreach (var row in externalFilesTable.Rows) - { - if (this.TryGetIndexedElement(row, out var xExternalFile) - && this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, row.FieldAsString(0), row.FieldAsString(0))) - { - xExternalFile.Add(xProtectRange); - usedProtectRanges.Add(xProtectRange); - } - } - } - - var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"]; - if (null != targetFiles_OptionalDataTable) - { - var targetImagesTable = tables["TargetImages"]; - var targetImageRows = targetImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); - - var upgradedImagesTable = tables["UpgradedImages"]; - var upgradedImagesRows = upgradedImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); - - foreach (var row in targetFiles_OptionalDataTable.Rows) - { - var xTargetFile = this.PatchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)]; - - if (!targetImageRows.TryGetValue(row.FieldAsString(0), out var targetImageRow)) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); - continue; - } - - if (!upgradedImagesRows.TryGetValue(row.FieldAsString(3), out var upgradedImagesRow)) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", row.FieldAsString(3), "UpgradedImages")); - continue; - } - - if (this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, upgradedImagesRow.FieldAsString(4), row.FieldAsString(1))) - { - xTargetFile.Add(xProtectRange); - usedProtectRanges.Add(xProtectRange); - } - } - } - - if (null != familyFileRangesTable) - { - foreach (var row in familyFileRangesTable.Rows) - { - var xProtectRange = this.GetIndexedElement(row); - - if (!usedProtectRanges.Contains(xProtectRange)) - { - var xProtectFile = new XElement(Names.ProtectFileElement, new XAttribute("File", row.FieldAsString(1))); - xProtectFile.Add(xProtectRange); - - this.AddChildToParent("ImageFamilies", xProtectFile, row, 0); - } - } - } - } - - /// - /// Finalize the FeatureComponents table. - /// - /// The collection of all tables. - /// - /// Since tables specifying references to the FeatureComponents table have references to - /// the Feature and Component table separately, but not the FeatureComponents table specifically, - /// the FeatureComponents table and primary features must be decompiled during finalization. - /// - private void FinalizeFeatureComponentsTable(TableIndexedCollection tables) - { - var classTable = tables["Class"]; - if (null != classTable) - { - foreach (var row in classTable.Rows) - { - this.SetPrimaryFeature(row, 11, 2); - } - } - - var extensionTable = tables["Extension"]; - if (null != extensionTable) - { - foreach (var row in extensionTable.Rows) - { - this.SetPrimaryFeature(row, 4, 1); - } - } - - var msiAssemblyTable = tables["MsiAssembly"]; - if (null != msiAssemblyTable) - { - foreach (var row in msiAssemblyTable.Rows) - { - this.SetPrimaryFeature(row, 1, 0); - } - } - - var publishComponentTable = tables["PublishComponent"]; - if (null != publishComponentTable) - { - foreach (var row in publishComponentTable.Rows) - { - this.SetPrimaryFeature(row, 4, 2); - } - } - - var typeLibTable = tables["TypeLib"]; - if (null != typeLibTable) - { - foreach (var row in typeLibTable.Rows) - { - this.SetPrimaryFeature(row, 6, 2); - } - } - } - - /// - /// Finalize the File table. - /// - /// The collection of all tables. - /// - /// Sets the source, diskId, and assembly information for each file. - /// - private void FinalizeFileTable(TableIndexedCollection tables) - { - // index the media table by media id - var mediaTable = tables["Media"]; - var mediaRows = new RowDictionary(mediaTable); - - // set the disk identifiers and sources for files - foreach (var fileRow in tables["File"]?.Rows.Cast() ?? Enumerable.Empty()) - { - var xFile = this.GetIndexedElement("File", fileRow.File); - - // Don't bother processing files that are orphaned (and won't show up in the output anyway) - if (null != xFile.Parent) - { - // set the diskid - if (null != mediaTable) - { - foreach (MediaRow mediaRow in mediaTable.Rows) - { - if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1) - { - xFile.SetAttributeValue("DiskId", mediaRow.DiskId); - break; - } - } - } - - var fileId = xFile?.Attribute("Id")?.Value; - var fileCompressed = xFile?.Attribute("Compressed")?.Value; - var fileShortName = xFile?.Attribute("ShortName")?.Value; - var fileName = xFile?.Attribute("Name")?.Value; - - // set the source (done here because it requires information from the Directory table) - if (OutputType.Module == this.OutputType) - { - xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId, '.', this.ModularizationGuid.Substring(1, 36).Replace('-', '_'))); - } - else if (fileCompressed == "yes" || (fileCompressed != "no" && this.Compressed) || (OutputType.Product == this.OutputType && this.TreatProductAsModule)) - { - xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId)); - } - else // uncompressed - { - var name = (!this.ShortNames && !String.IsNullOrEmpty(fileName)) ? fileName : fileShortName ?? fileName; - - if (this.Compressed) // uncompressed at the root of the source image - { - xFile.SetAttributeValue("Source", String.Concat("SourceDir", Path.DirectorySeparatorChar, name)); - } - else - { - var sourcePath = this.GetSourcePath(xFile); - xFile.SetAttributeValue("Source", Path.Combine(sourcePath, name)); - } - } - } - } - - // set the file assemblies and manifests - foreach (var row in tables["MsiAssembly"]?.Rows ?? Enumerable.Empty()) - { - if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) - { - foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) - { - xFile.SetAttributeValue("AssemblyManifest", row.FieldAsString(2)); - xFile.SetAttributeValue("AssemblyApplication", row.FieldAsString(3)); - xFile.SetAttributeValue("Assembly", row.FieldAsInteger(4) == 0 ? ".net" : "win32"); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); - } - } - - // nest the TypeLib elements - foreach (var row in tables["TypeLib"]?.Rows ?? Enumerable.Empty()) - { - var xComponent = this.GetIndexedElement("Component", row.FieldAsString(2)); - var xTypeLib = this.GetIndexedElement(row); - - foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) - { - xFile.Add(xTypeLib); - } - } - } - - /// - /// Finalize the MIME table. - /// - /// The collection of all tables. - /// - /// There is a foreign key shared between the MIME and Extension - /// tables so either one would be valid to be decompiled first, so - /// the only safe way to nest the MIME elements is to do it during finalize. - /// - private void FinalizeMIMETable(TableIndexedCollection tables) - { - var extensionRows = tables["Extension"]?.Rows ?? Enumerable.Empty(); - foreach (var row in extensionRows) - { - // set the default MIME element for this extension - var mimeRef = row.FieldAsString(3); - if (null != mimeRef) - { - if (this.TryGetIndexedElement("MIME", out var xMime, mimeRef)) - { - xMime.SetAttributeValue("Default", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); - } - } - } - - var extensionsByExtensionId = this.IndexTableOneToMany(extensionRows); - - foreach (var row in tables["MIME"]?.Rows ?? Enumerable.Empty()) - { - var xMime = this.GetIndexedElement(row); - - if (extensionsByExtensionId.TryGetValue(row.FieldAsString(1), out var xExtensions)) - { - foreach (var extension in xExtensions) - { - extension.Add(xMime); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(1), "Extension")); - } - } - } - - /// - /// Finalize the ProgId table. - /// - /// The collection of all tables. - /// - /// Enumerates through all the Class rows, looking for child ProgIds (these are the - /// default ProgIds for a given Class). Then go through the ProgId table and add any - /// remaining ProgIds for each Class. This happens during finalize because there is - /// a circular dependency between the Class and ProgId tables. - /// - private void FinalizeProgIdTable(TableIndexedCollection tables) - { - // add the default ProgIds for each class (and index the class table) - var classRows = tables["Class"]?.Rows?.Where(row => row.FieldAsString(3) != null) ?? Enumerable.Empty(); - - var classesByCLSID = this.IndexTableOneToMany(classRows); - - var addedProgIds = new Dictionary(); - - foreach (var row in classRows) - { - var clsid = row.FieldAsString(0); - var xClass = this.GetIndexedElement(row); - - if (this.TryGetIndexedElement("ProgId", out var xProgId, row.FieldAsString(3))) - { - if (addedProgIds.TryGetValue(xProgId, out var progid)) - { - this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, row.FieldAsString(0), row.FieldAsString(3), progid)); - } - else - { - xClass.Add(xProgId); - addedProgIds.Add(xProgId, clsid); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", row.FieldAsString(3), "ProgId")); - } - } - - // add the remaining non-default ProgId entries for each class - foreach (var row in tables["ProgId"]?.Rows ?? Enumerable.Empty()) - { - var clsid = row.FieldAsString(2); - var xProgId = this.GetIndexedElement(row); - - if (!addedProgIds.ContainsKey(xProgId) && null != clsid && null == xProgId.Parent) - { - if (classesByCLSID.TryGetValue(clsid, out var xClasses)) - { - foreach (var xClass in xClasses) - { - xClass.Add(xProgId); - addedProgIds.Add(xProgId, clsid); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", row.FieldAsString(2), "Class")); - } - } - } - - // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension - var componentsById = this.IndexTableOneToMany(tables, "Component"); - - foreach (var row in tables["Extension"]?.Rows?.Where(row => row.FieldAsString(2) != null) ?? Enumerable.Empty()) - { - var xProgId = this.GetIndexedElement("ProgId", row.FieldAsString(2)); - - // Haven't added the progId yet and it doesn't have a parent progId - if (!addedProgIds.ContainsKey(xProgId) && null == xProgId.Parent) - { - if (componentsById.TryGetValue(row.FieldAsString(1), out var xComponents)) - { - foreach (var xComponent in xComponents) - { - xComponent.Add(xProgId); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); - } - } - } - } - - /// - /// Finalize the Property table. - /// - /// The collection of all tables. - /// - /// Removes properties that are generated from other entries. - /// - private void FinalizePropertyTable(TableIndexedCollection tables) - { - foreach (var row in tables["CustomAction"]?.Rows ?? Enumerable.Empty()) - { - // If no other fields on the property are set we must have created it in the backend. - var bits = row.FieldAsInteger(1); - if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (bits & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget) - && WindowsInstallerConstants.MsidbCustomActionTypeInScript == (bits & WindowsInstallerConstants.MsidbCustomActionTypeInScript) - && this.TryGetIndexedElement("Property", out var xProperty, row.FieldAsString(0)) - && String.IsNullOrEmpty(xProperty.Attribute("Value")?.Value) - && xProperty.Attribute("Secure")?.Value != "yes" - && xProperty.Attribute("SuppressModularization")?.Value != "yes") - { - xProperty.Remove(); - } - } - } - - /// - /// Finalize the RemoveFile table. - /// - /// The collection of all tables. - /// - /// Sets the directory/property for each RemoveFile row. - /// - private void FinalizeRemoveFileTable(TableIndexedCollection tables) - { - foreach (var row in tables["RemoveFile"]?.Rows ?? Enumerable.Empty()) - { - var xRemove = this.GetIndexedElement(row); - var property = row.FieldAsString(3); - - if (this.TryGetIndexedElement("Directory", out var _, property)) - { - xRemove.SetAttributeValue("Directory", property); - } - else - { - xRemove.SetAttributeValue("Property", property); - } - } - } - - /// - /// Finalize the LockPermissions or MsiLockPermissionsEx table. - /// - /// The collection of all tables. - /// Which table to finalize. - /// - /// Nests the Permission elements below their parent elements. There are no declared foreign - /// keys for the parents of the LockPermissions table. - /// - private void FinalizePermissionsTable(TableIndexedCollection tables, string tableName) - { - var createFoldersById = this.IndexTableOneToMany(tables, tableName); - - foreach (var row in tables[tableName]?.Rows ?? Enumerable.Empty()) - { - var id = row.FieldAsString(0); - var table = row.FieldAsString(1); - var xPermission = this.GetIndexedElement(row); - - if ("CreateFolder" == table) - { - if (createFoldersById.TryGetValue(id, out var xCreateFolders)) - { - foreach (var xCreateFolder in xCreateFolders) - { - xCreateFolder.Add(xPermission); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } - } - else - { - if (this.TryGetIndexedElement(table, out var xParent, id)) - { - xParent.Add(xPermission); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); - } - } - } - } - - /// - /// Finalize the LockPermissions table. - /// - /// The collection of all tables. - /// - /// Nests the Permission elements below their parent elements. There are no declared foreign - /// keys for the parents of the LockPermissions table. - /// - private void FinalizeLockPermissionsTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "LockPermissions"); - - /// - /// Finalize the MsiLockPermissionsEx table. - /// - /// The collection of all tables. - /// - /// Nests the PermissionEx elements below their parent elements. There are no declared foreign - /// keys for the parents of the MsiLockPermissionsEx table. - /// - private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "MsiLockPermissionsEx"); - - private static Dictionary> IndexTable(Table table, int keyColumn, int? dataColumn) - { - if (table == null) - { - return new Dictionary>(); - } - - return table.Rows - .ToLookup(row => row.FieldAsString(keyColumn), row => dataColumn.HasValue ? row.FieldAsString(dataColumn.Value) : null) - .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); - } - - private static XElement FindComplianceDrive(XElement xSearch) - { - var xComplianceDrive = xSearch.Element(Names.ComplianceDriveElement); - if (null == xComplianceDrive) - { - xComplianceDrive = new XElement(Names.ComplianceDriveElement); - xSearch.Add(xComplianceDrive); - } - - return xComplianceDrive; - } - - /// - /// Finalize the search tables. - /// - /// The collection of all tables. - /// Does all the complex linking required for the search tables. - private void FinalizeSearchTables(TableIndexedCollection tables) - { - var appSearches = IndexTable(tables["AppSearch"], keyColumn: 1, dataColumn: 0); - var ccpSearches = IndexTable(tables["CCPSearch"], keyColumn: 0, dataColumn: null); - var drLocators = tables["DrLocator"]?.Rows.ToDictionary(row => this.GetIndexedElement(row), row => row); - - var xComplianceCheck = new XElement(Names.ComplianceCheckElement); - if (ccpSearches.Keys.Any(ccpSignature => !appSearches.ContainsKey(ccpSignature))) - { - this.RootElement.Add(xComplianceCheck); - } - - // index the locator tables by their signatures - var locators = - new[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" } - .SelectMany(table => tables[table]?.Rows ?? Enumerable.Empty()) - .ToLookup(row => row.FieldAsString(0), row => row) - .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); - - // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef) - foreach (var locatorRows in locators.Values) - { - var firstDrLocator = -1; - - for (var i = 0; i < locatorRows.Count; i++) - { - var locatorRow = (Row)locatorRows[i]; - - if ("DrLocator" == locatorRow.TableDefinition.Name) - { - if (-1 == firstDrLocator) - { - firstDrLocator = i; - } - - if ("CCP_DRIVE" == Convert.ToString(locatorRow[1])) - { - locatorRows.RemoveAt(i); - locatorRows.Insert(firstDrLocator, locatorRow); - break; - } - } - } - } - - var xUsedSearches = new HashSet(); - var xUnusedSearches = new Dictionary(); - - foreach (var signature in locators.Keys) - { - var locatorRows = locators[signature]; - var xSignatureSearches = new List(); - - foreach (var locatorRow in locatorRows) - { - var used = true; - var xSearch = this.GetIndexedElement(locatorRow); - - if ("Signature" == locatorRow.TableDefinition.Name && 0 < xSignatureSearches.Count) - { - foreach (var xSearchParent in xSignatureSearches) - { - if (!xUsedSearches.Contains(xSearch)) - { - xSearchParent.Add(xSearch); - xUsedSearches.Add(xSearch); - } - else - { - var xFileSearchRef = new XElement(Names.FileSearchRefElement, - new XAttribute("Id", signature)); - - xSearchParent.Add(xFileSearchRef); - } - } - } - else if ("DrLocator" == locatorRow.TableDefinition.Name && !locatorRow.IsColumnNull(1)) - { - var parentSignature = locatorRow.FieldAsString(1); - - if ("CCP_DRIVE" == parentSignature) - { - if (appSearches.ContainsKey(signature) - && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) - { - foreach (var propertyId in appSearchPropertyIds) - { - var xProperty = this.EnsureProperty(propertyId); - - if (ccpSearches.ContainsKey(signature)) - { - xProperty.SetAttributeValue("ComplianceCheck", "yes"); - } - - var xComplianceDrive = FindComplianceDrive(xProperty); - - if (!xUsedSearches.Contains(xSearch)) - { - xComplianceDrive.Add(xSearch); - xUsedSearches.Add(xSearch); - } - else - { - var directorySearchRef = new XElement(Names.DirectorySearchRefElement, - new XAttribute("Id", signature), - XAttributeIfNotNull("Parent", locatorRow, 1), - XAttributeIfNotNull("Path", locatorRow, 2)); - - xComplianceDrive.Add(directorySearchRef); - xSignatureSearches.Add(directorySearchRef); - } - } - } - else if (ccpSearches.ContainsKey(signature)) - { - var xComplianceDrive = FindComplianceDrive(xComplianceCheck); - - if (!xUsedSearches.Contains(xSearch)) - { - xComplianceDrive.Add(xSearch); - xUsedSearches.Add(xSearch); - } - else - { - var directorySearchRef = new XElement(Names.DirectorySearchRefElement, - new XAttribute("Id", signature), - XAttributeIfNotNull("Parent", locatorRow, 1), - XAttributeIfNotNull("Path", locatorRow, 2)); - - xComplianceDrive.Add(directorySearchRef); - xSignatureSearches.Add(directorySearchRef); - } - } - } - else - { - var usedDrLocator = false; - - if (locators.TryGetValue(parentSignature, out var parentLocatorRows)) - { - foreach (var parentLocatorRow in parentLocatorRows) - { - if ("DrLocator" == parentLocatorRow.TableDefinition.Name) - { - var xParentSearch = this.GetIndexedElement(parentLocatorRow); - - if (xParentSearch.HasElements) - { - var parentDrLocatorRow = drLocators[xParentSearch]; - var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, - new XAttribute("Id", parentSignature), - XAttributeIfNotNull("Parent", parentDrLocatorRow, 1), - XAttributeIfNotNull("Path", parentDrLocatorRow, 2)); - - xParentSearch = xDirectorySearchRef; - xUnusedSearches.Add(parentSignature, xDirectorySearchRef); - } - - if (!xUsedSearches.Contains(xSearch)) - { - xParentSearch.Add(xSearch); - xUsedSearches.Add(xSearch); - usedDrLocator = true; - } - else - { - var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, - new XAttribute("Id", signature), - new XAttribute("Parent", parentSignature), - XAttributeIfNotNull("Path", locatorRow, 2)); - - xParentSearch.Add(xSearch); - usedDrLocator = true; - } - } - else if ("RegLocator" == parentLocatorRow.TableDefinition.Name) - { - var xParentSearch = this.GetIndexedElement(parentLocatorRow); - - xParentSearch.Add(xSearch); - xUsedSearches.Add(xSearch); - usedDrLocator = true; - } - } - - // keep track of unused DrLocator rows - if (!usedDrLocator) - { - xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); - } - } - else - { - // TODO: warn - } - } - } - else if (appSearches.ContainsKey(signature) - && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) - { - foreach (var propertyId in appSearchPropertyIds) - { - var xProperty = this.EnsureProperty(propertyId); - - if (ccpSearches.ContainsKey(signature)) - { - xProperty.SetAttributeValue("ComplianceCheck", "yes"); - } - - if (!xUsedSearches.Contains(xSearch)) - { - xProperty.Add(xSearch); - xUsedSearches.Add(xSearch); - } - else if ("RegLocator" == locatorRow.TableDefinition.Name) - { - var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, - new XAttribute("Id", signature)); - - xProperty.Add(xRegistrySearchRef); - xSignatureSearches.Add(xRegistrySearchRef); - } - else - { - // TODO: warn about unavailable Ref element - } - } - } - else if (ccpSearches.ContainsKey(signature)) - { - if (!xUsedSearches.Contains(xSearch)) - { - xComplianceCheck.Add(xSearch); - xUsedSearches.Add(xSearch); - } - else if ("RegLocator" == locatorRow.TableDefinition.Name) - { - var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, - new XAttribute("Id", signature)); - - xComplianceCheck.Add(xRegistrySearchRef); - xSignatureSearches.Add(xRegistrySearchRef); - } - else - { - // TODO: warn about unavailable Ref element - } - } - else - { - if (xSearch.Name.LocalName == "DirectorySearch" || xSearch.Name.LocalName == "RegistrySearch") - { - xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); - } - else - { - // TODO: warn - used = false; - } - } - - // keep track of the search elements for this signature so that nested searches go in the proper parents - if (used) - { - xSignatureSearches.Add(xSearch); - } - } - } - - // Iterate through the unused elements through a sorted list of their ids so the output is deterministic. - foreach (var unusedSearch in xUnusedSearches.OrderBy(kvp => kvp.Key)) - { - var used = false; - - XElement xLeafDirectorySearch = null; - var xUnusedSearch = unusedSearch.Value; - var xParent = xUnusedSearch; - var updatedLeaf = true; - while (updatedLeaf) - { - updatedLeaf = false; - - var xDirectorySearch = xParent.Element(Names.DirectorySearchElement); - if (xDirectorySearch != null) - { - xParent = xLeafDirectorySearch = xDirectorySearch; - updatedLeaf = true; - } - } - - if (xLeafDirectorySearch != null) - { - var leafDirectorySearchId = xLeafDirectorySearch.Attribute("Id").Value; - if (appSearches.TryGetValue(leafDirectorySearchId, out var appSearchPropertyIds)) - { - var xProperty = this.EnsureProperty(appSearchPropertyIds[0]); - xProperty.Add(xUnusedSearch); - used = true; - } - else if (ccpSearches.ContainsKey(leafDirectorySearchId)) - { - xComplianceCheck.Add(xUnusedSearch); - used = true; - } - else - { - // TODO: warn - } - } - - if (!used) - { - // TODO: warn - } - } - } - - /// - /// Finalize the Shortcut table. - /// - /// The collection of all tables. - /// - /// Sets Advertise to yes if Target points to a Feature. - /// Occurs during finalization because it has to check against every feature row. - /// - private void FinalizeShortcutTable(TableIndexedCollection tables) - { - var shortcutTable = tables["Shortcut"]; - if (null == shortcutTable) - { - return; - } - - foreach (var row in shortcutTable.Rows) - { - var xShortcut = this.GetIndexedElement(row); - - var target = row.FieldAsString(4); - - if (this.TryGetIndexedElement("Feature", out var _, target)) - { - xShortcut.SetAttributeValue("Advertise", "yes"); - this.SetPrimaryFeature(row, 4, 3); - } - else - { - // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element - xShortcut.SetAttributeValue("Target", target); - } - } - } - - /// - /// Finalize the sequence tables. - /// - /// The collection of all tables. - /// - /// Creates the sequence elements. Occurs during finalization because its - /// not known if sequences refer to custom actions or dialogs during decompilation. - /// - private void FinalizeSequenceTables(TableIndexedCollection tables) - { - // finalize the normal sequence tables - if (OutputType.Product == this.OutputType && !this.TreatProductAsModule) - { - foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) - { - var sequenceTableName = sequenceTable.WindowsInstallerTableName(); - - // if suppressing UI elements, skip UI-related sequence tables - if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName)) - { - continue; - } - - var table = tables[sequenceTableName]; - - if (null != table) - { - var actionSymbols = new List(); - var needAbsoluteScheduling = this.SuppressRelativeActionSequencing; - var nonSequencedActionRows = new Dictionary(); - var suppressedRelativeActionRows = new Dictionary(); - - // create a sorted array of actions in this table - foreach (var row in table.Rows) - { - var action = row.FieldAsString(0); - var actionSymbol = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, action)); - - actionSymbol.Action = action; - - if (!row.IsColumnNull(1)) - { - actionSymbol.Condition = row.FieldAsString(1); - } - - actionSymbol.Sequence = row.FieldAsInteger(2); - - actionSymbol.SequenceTable = sequenceTable; - - actionSymbols.Add(actionSymbol); - } - actionSymbols = actionSymbols.OrderBy(t => t.Sequence).ToList(); - - for (var i = 0; i < actionSymbols.Count && !needAbsoluteScheduling; i++) - { - var actionSymbol = actionSymbols[i]; - this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var standardActionRow); - - // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions - if ("AppSearch" == actionSymbol.Action || null == standardActionRow || actionSymbol.Condition != standardActionRow.Condition) - { - WixActionSymbol previousActionSymbol = null; - WixActionSymbol nextActionSymbol = null; - - // find the previous action row if there is one - if (0 <= i - 1) - { - previousActionSymbol = actionSymbols[i - 1]; - } - - // find the next action row if there is one - if (actionSymbols.Count > i + 1) - { - nextActionSymbol = actionSymbols[i + 1]; - } - - // the logic for setting the before or after attribute for an action: - // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced. - // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it. - // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it. - // 4. If this action is not standard and the previous action is 1 sequence number lower and does not occur before this action, this action occurs after it. - // 5. If this action is not standard and the previous action does not have the same sequence number and the next action is 1 sequence number higher, this action occurs before it. - // 6. If this action is AppSearch and has all standard information, ignore it. - // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information. - // 8. Everything must be absolutely sequenced. - if ((null != previousActionSymbol && actionSymbol.Sequence == previousActionSymbol.Sequence) || (null != nextActionSymbol && actionSymbol.Sequence == nextActionSymbol.Sequence)) - { - needAbsoluteScheduling = true; - } - else if (null != nextActionSymbol && this.StandardActions.ContainsKey(nextActionSymbol.Id.Id) && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence) - { - actionSymbol.Before = nextActionSymbol.Action; - } - else if (null != previousActionSymbol && this.StandardActions.ContainsKey(previousActionSymbol.Id.Id) && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence) - { - actionSymbol.After = previousActionSymbol.Action; - } - else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence && previousActionSymbol.Before != actionSymbol.Action) - { - actionSymbol.After = previousActionSymbol.Action; - } - else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence != previousActionSymbol.Sequence && null != nextActionSymbol && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence) - { - actionSymbol.Before = nextActionSymbol.Action; - } - else if ("AppSearch" == actionSymbol.Action && null != standardActionRow && actionSymbol.Sequence == standardActionRow.Sequence && actionSymbol.Condition == standardActionRow.Condition) - { - // ignore an AppSearch row which has the WiX standard sequence and a standard condition - } - else if (null != standardActionRow && actionSymbol.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers - { - nonSequencedActionRows.Add(actionSymbol.Id.Id, actionSymbol); - } - else if (0 < actionSymbol.Sequence) - { - needAbsoluteScheduling = true; - } - } - else - { - suppressedRelativeActionRows.Add(actionSymbol.Id.Id, actionSymbol); - } - } - - // create the actions now that we know if they must be absolutely or relatively scheduled - foreach (var actionRow in actionSymbols) - { - var key = actionRow.Id.Id; - - if (needAbsoluteScheduling) - { - // remove any before/after information to ensure this is absolutely sequenced - actionRow.Before = null; - actionRow.After = null; - } - else if (nonSequencedActionRows.ContainsKey(key)) - { - // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after) - actionRow.Sequence = 0; - } - else if (suppressedRelativeActionRows.ContainsKey(key)) - { - // skip the suppressed relatively scheduled action rows - continue; - } - - // create the action element - this.CreateActionElement(actionRow); - } - } - } - } - else if (OutputType.Module == this.OutputType || this.TreatProductAsModule) // finalize the Module sequence tables - { - foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) - { - var sequenceTableName = sequenceTable.WindowsInstallerTableName(); - - // if suppressing UI elements, skip UI-related sequence tables - if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName)) - { - continue; - } - - var table = tables[String.Concat("Module", sequenceTableName)]; - - if (null != table) - { - foreach (var row in table.Rows) - { - var actionRow = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, row.FieldAsString(0))); - - actionRow.Action = row.FieldAsString(0); - - if (!row.IsColumnNull(1)) - { - actionRow.Sequence = row.FieldAsInteger(1); - } - - if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) - { - switch (row.FieldAsInteger(3)) - { - case 0: - actionRow.Before = row.FieldAsString(2); - break; - case 1: - actionRow.After = row.FieldAsString(2); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3])); - break; - } - } - - if (!row.IsColumnNull(4)) - { - actionRow.Condition = row.FieldAsString(4); - } - - actionRow.SequenceTable = sequenceTable; - - // create action elements for non-standard actions - if (!this.StandardActions.ContainsKey(actionRow.Id.Id) || null != actionRow.After || null != actionRow.Before) - { - this.CreateActionElement(actionRow); - } - } - } - } - } - } - - /// - /// Finalize the Upgrade table. - /// - /// The collection of all tables. - /// - /// Decompile the rows from the Upgrade and LaunchCondition tables - /// created by the MajorUpgrade element. - /// - private void FinalizeUpgradeTable(TableIndexedCollection tables) - { - var launchConditionTable = tables["LaunchCondition"]; - var upgradeTable = tables["Upgrade"]; - string downgradeErrorMessage = null; - string disallowUpgradeErrorMessage = null; - - // find the DowngradePreventedCondition launch condition message - if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count) - { - foreach (var launchRow in launchConditionTable.Rows) - { - if (WixUpgradeConstants.DowngradePreventedCondition == Convert.ToString(launchRow[0])) - { - downgradeErrorMessage = Convert.ToString(launchRow[1]); - } - else if (WixUpgradeConstants.UpgradePreventedCondition == Convert.ToString(launchRow[0])) - { - disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]); - } - } - } - - if (null != upgradeTable && 0 < upgradeTable.Rows.Count) - { - XElement xMajorUpgrade = null; - - foreach (UpgradeRow upgradeRow in upgradeTable.Rows) - { - if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty) - { - var attr = upgradeRow.Attributes; - var removeFeatures = upgradeRow.Remove; - xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); - - if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) - { - xMajorUpgrade.SetAttributeValue("AllowSameVersionUpgrades", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures != (attr & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) - { - xMajorUpgrade.SetAttributeValue("MigrateFeatures", "no"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) - { - xMajorUpgrade.SetAttributeValue("IgnoreRemoveFailure", "yes"); - } - - if (!String.IsNullOrEmpty(removeFeatures)) - { - xMajorUpgrade.SetAttributeValue("RemoveFeatures", removeFeatures); - } - } - else if (WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty) - { - xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); - xMajorUpgrade.SetAttributeValue("DowngradeErrorMessage", downgradeErrorMessage); - } - } - - if (xMajorUpgrade != null) - { - if (String.IsNullOrEmpty(downgradeErrorMessage)) - { - xMajorUpgrade.SetAttributeValue("AllowDowngrades", "yes"); - } - - if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage)) - { - xMajorUpgrade.SetAttributeValue("Disallow", "yes"); - xMajorUpgrade.SetAttributeValue("DisallowUpgradeErrorMessage", disallowUpgradeErrorMessage); - } - - var scheduledType = DetermineMajorUpgradeScheduling(tables); - if (scheduledType != "afterInstallValidate") - { - xMajorUpgrade.SetAttributeValue("Schedule", scheduledType); - } - - this.RootElement.Add(xMajorUpgrade); - } - } - } - - /// - /// Finalize the Verb table. - /// - /// The collection of all tables. - /// - /// The Extension table is a foreign table for the Verb table, but the - /// foreign key is only part of the primary key of the Extension table, - /// so it needs special logic to be nested properly. - /// - private void FinalizeVerbTable(TableIndexedCollection tables) - { - var xExtensions = this.IndexTableOneToMany(tables["Extension"]); - - var verbTable = tables["Verb"]; - if (null != verbTable) - { - foreach (var row in verbTable.Rows) - { - if (xExtensions.TryGetValue(row.FieldAsString(0), out var xVerbExtensions)) - { - var xVerb = this.GetIndexedElement(row); - - foreach (var xVerbExtension in xVerbExtensions) - { - xVerbExtension.Add(xVerb); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(0), "Extension")); - } - } - } - } - - /// - /// Get the path to a file in the source image. - /// - /// The file. - /// The path to the file in the source image. - private string GetSourcePath(XElement xFile) - { - var sourcePath = new StringBuilder(); - - var component = xFile.Parent; - - for (var xDirectory = component.Parent; null != xDirectory && xDirectory.Name.LocalName == "Directory"; xDirectory = xDirectory.Parent) - { - string name; - - var dirSourceName = xDirectory.Attribute("SourceName")?.Value; - var dirShortSourceName = xDirectory.Attribute("ShortSourceName")?.Value; - var dirShortName = xDirectory.Attribute("ShortName")?.Value; - var dirName = xDirectory.Attribute("Name")?.Value; - - if (!this.ShortNames && null != dirSourceName) - { - name = dirSourceName; - } - else if (null != dirShortSourceName) - { - name = dirShortSourceName; - } - else if (!this.ShortNames || null == dirShortName) - { - name = dirName; - } - else - { - name = dirShortName; - } - - if (0 == sourcePath.Length) - { - sourcePath.Append(name); - } - else - { - sourcePath.Insert(0, Path.DirectorySeparatorChar); - sourcePath.Insert(0, name); - } - } - - return sourcePath.ToString(); - } - - /// - /// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames). - /// - /// The name of the table to resolve. - /// The unsorted table names. - /// The sorted table names. - private void ResolveTableDependencies(string tableName, List unsortedTableNames, HashSet sortedTableNames) - { - unsortedTableNames.Remove(tableName); - - foreach (var columnDefinition in this.TableDefinitions[tableName].Columns) - { - // no dependency to resolve because this column doesn't reference another table - if (null == columnDefinition.KeyTable) - { - continue; - } - - foreach (var keyTable in columnDefinition.KeyTable.Split(';')) - { - if (tableName == keyTable) - { - continue; // self-referencing dependency - } - else if (sortedTableNames.Contains(keyTable)) - { - continue; // dependent table has already been sorted - } - else if (!this.TableDefinitions.Contains(keyTable)) - { - this.Messaging.Write(ErrorMessages.MissingTableDefinition(keyTable)); - } - else if (unsortedTableNames.Contains(keyTable)) - { - this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames); - } - else - { - // found a circular dependency, so ignore it (this assumes that the tables will - // use a finalize method to nest their elements since the ordering will not be - // deterministic - } - } - } - - sortedTableNames.Add(tableName); - } - - /// - /// Get the names of the tables to process in the order they should be processed, according to their dependencies. - /// - /// A StringCollection containing the ordered table names. - private HashSet GetOrderedTableNames() - { - var orderedTableNames = new HashSet(); - var unsortedTableNames = new List(this.TableDefinitions.Select(t => t.Name)); - - // resolve the dependencies for each table - while (0 < unsortedTableNames.Count) - { - this.ResolveTableDependencies(unsortedTableNames[0], unsortedTableNames, orderedTableNames); - } - - return orderedTableNames; - } - - /// - /// Initialize decompilation. - /// - /// The collection of all tables. - /// - private void InitializeDecompile(TableIndexedCollection tables, int codepage) - { - // reset all the state information - this.Compressed = false; - this.ShortNames = false; - - this.Singletons.Clear(); - this.IndexedElements.Clear(); - this.PatchTargetFiles.Clear(); - - // set the codepage if its not neutral (0) - if (0 != codepage) - { - this.RootElement.SetAttributeValue("Codepage", codepage); - } - - if (this.OutputType == OutputType.Module) - { - var table = tables["_SummaryInformation"]; - var row = table.Rows.SingleOrDefault(r => r.FieldAsInteger(0) == 9); - this.ModularizationGuid = row?.FieldAsString(1); - } - - // index the rows from the extension libraries - var indexedExtensionTables = new Dictionary>(); -#if TODO_DECOMPILER_EXTENSIONS - foreach (IDecompilerExtension extension in this.extensions) - { - // Get the optional library from the extension with the rows to be removed. - Library library = extension.GetLibraryToRemove(this.tableDefinitions); - if (null != library) - { - foreach (var section in library.Sections) - { - foreach (Table table in section.Tables) - { - foreach (Row row in table.Rows) - { - string primaryKey; - string tableName; - - // the Actions table needs to be handled specially - if ("WixAction" == table.Name) - { - primaryKey = row.FieldAsString(1); - - if (OutputType.Module == this.outputType) - { - tableName = String.Concat("Module", row.FieldAsString(0)); - } - else - { - tableName = row.FieldAsString(0); - } - } - else - { - primaryKey = row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter); - tableName = table.Name; - } - - if (null != primaryKey) - { - HashSet indexedExtensionRows; - if (!indexedExtensionTables.TryGetValue(tableName, out indexedExtensionRows)) - { - indexedExtensionRows = new HashSet(); - indexedExtensionTables.Add(tableName, indexedExtensionRows); - } - - indexedExtensionRows.Add(primaryKey); - } - } - } - } - } - } -#endif - - // remove the rows from the extension libraries (to allow full round-tripping) - foreach (var kvp in indexedExtensionTables) - { - var tableName = kvp.Key; - var indexedExtensionRows = kvp.Value; - - var table = tables[tableName]; - if (null != table) - { - var originalRows = new RowDictionary(table); - - // remove the original rows so that they can be added back if they should remain - table.Rows.Clear(); - - foreach (var row in originalRows.Values) - { - if (!indexedExtensionRows.Contains(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter))) - { - table.Rows.Add(row); - } - } - } - } - } - - /// - /// Decompile the tables. - /// - /// The output being decompiled. - private void DecompileTables(WindowsInstallerData output) - { - var orderedTableNames = this.GetOrderedTableNames(); - foreach (var tableName in orderedTableNames) - { - var table = output.Tables[tableName]; - - // table does not exist in this database or should not be decompiled - if (null == table || !this.DecompilableTable(output, tableName)) - { - continue; - } - - this.Messaging.Write(VerboseMessages.DecompilingTable(table.Name)); - - // empty tables may be kept with EnsureTable if the user set the proper option - if (0 == table.Rows.Count && this.SuppressDroppingEmptyTables) - { - this.RootElement.Add(new XElement(Names.EnsureTableElement, new XAttribute("Id", table.Name))); - } - - switch (table.Name) - { - case "_SummaryInformation": - // handled in FinalizeDecompile - break; - case "AdminExecuteSequence": - case "AdminUISequence": - case "AdvtExecuteSequence": - case "InstallExecuteSequence": - case "InstallUISequence": - case "ModuleAdminExecuteSequence": - case "ModuleAdminUISequence": - case "ModuleAdvtExecuteSequence": - case "ModuleInstallExecuteSequence": - case "ModuleInstallUISequence": - // handled in FinalizeSequenceTables - break; - case "ActionText": - this.DecompileActionTextTable(table); - break; - case "AdvtUISequence": - this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); - break; - case "AppId": - this.DecompileAppIdTable(table); - break; - case "AppSearch": - // handled in FinalizeSearchTables - break; - case "BBControl": - this.DecompileBBControlTable(table); - break; - case "Billboard": - this.DecompileBillboardTable(table); - break; - case "Binary": - this.DecompileBinaryTable(table); - break; - case "BindImage": - this.DecompileBindImageTable(table); - break; - case "CCPSearch": - // handled in FinalizeSearchTables - break; - case "CheckBox": - // handled in FinalizeCheckBoxTable - break; - case "Class": - this.DecompileClassTable(table); - break; - case "ComboBox": - this.DecompileComboBoxTable(table); - break; - case "Control": - this.DecompileControlTable(table); - break; - case "ControlCondition": - this.DecompileControlConditionTable(table); - break; - case "ControlEvent": - this.DecompileControlEventTable(table); - break; - case "CreateFolder": - this.DecompileCreateFolderTable(table); - break; - case "CustomAction": - this.DecompileCustomActionTable(table); - break; - case "CompLocator": - this.DecompileCompLocatorTable(table); - break; - case "Complus": - this.DecompileComplusTable(table); - break; - case "Component": - this.DecompileComponentTable(table); - break; - case "Condition": - this.DecompileConditionTable(table); - break; - case "Dialog": - this.DecompileDialogTable(table); - break; - case "Directory": - this.DecompileDirectoryTable(table); - break; - case "DrLocator": - this.DecompileDrLocatorTable(table); - break; - case "DuplicateFile": - this.DecompileDuplicateFileTable(table); - break; - case "Environment": - this.DecompileEnvironmentTable(table); - break; - case "Error": - this.DecompileErrorTable(table); - break; - case "EventMapping": - this.DecompileEventMappingTable(table); - break; - case "Extension": - this.DecompileExtensionTable(table); - break; - case "ExternalFiles": - this.DecompileExternalFilesTable(table); - break; - case "FamilyFileRanges": - // handled in FinalizeFamilyFileRangesTable - break; - case "Feature": - this.DecompileFeatureTable(table); - break; - case "FeatureComponents": - this.DecompileFeatureComponentsTable(table); - break; - case "File": - this.DecompileFileTable(table); - break; - case "FileSFPCatalog": - this.DecompileFileSFPCatalogTable(table); - break; - case "Font": - this.DecompileFontTable(table); - break; - case "Icon": - this.DecompileIconTable(table); - break; - case "ImageFamilies": - this.DecompileImageFamiliesTable(table); - break; - case "IniFile": - this.DecompileIniFileTable(table); - break; - case "IniLocator": - this.DecompileIniLocatorTable(table); - break; - case "IsolatedComponent": - this.DecompileIsolatedComponentTable(table); - break; - case "LaunchCondition": - this.DecompileLaunchConditionTable(table); - break; - case "ListBox": - this.DecompileListBoxTable(table); - break; - case "ListView": - this.DecompileListViewTable(table); - break; - case "LockPermissions": - this.DecompileLockPermissionsTable(table); - break; - case "Media": - this.DecompileMediaTable(table); - break; - case "MIME": - this.DecompileMIMETable(table); - break; - case "ModuleAdvtUISequence": - this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); - break; - case "ModuleComponents": - // handled by DecompileComponentTable (since the ModuleComponents table - // rows are created by nesting components under the Module element) - break; - case "ModuleConfiguration": - this.DecompileModuleConfigurationTable(table); - break; - case "ModuleDependency": - this.DecompileModuleDependencyTable(table); - break; - case "ModuleExclusion": - this.DecompileModuleExclusionTable(table); - break; - case "ModuleIgnoreTable": - this.DecompileModuleIgnoreTableTable(table); - break; - case "ModuleSignature": - this.DecompileModuleSignatureTable(table); - break; - case "ModuleSubstitution": - this.DecompileModuleSubstitutionTable(table); - break; - case "MoveFile": - this.DecompileMoveFileTable(table); - break; - case "MsiAssembly": - // handled in FinalizeFileTable - break; - case "MsiDigitalCertificate": - this.DecompileMsiDigitalCertificateTable(table); - break; - case "MsiDigitalSignature": - this.DecompileMsiDigitalSignatureTable(table); - break; - case "MsiEmbeddedChainer": - this.DecompileMsiEmbeddedChainerTable(table); - break; - case "MsiEmbeddedUI": - this.DecompileMsiEmbeddedUITable(table); - break; - case "MsiLockPermissionsEx": - this.DecompileMsiLockPermissionsExTable(table); - break; - case "MsiPackageCertificate": - this.DecompileMsiPackageCertificateTable(table); - break; - case "MsiPatchCertificate": - this.DecompileMsiPatchCertificateTable(table); - break; - case "MsiShortcutProperty": - this.DecompileMsiShortcutPropertyTable(table); - break; - case "ODBCAttribute": - this.DecompileODBCAttributeTable(table); - break; - case "ODBCDataSource": - this.DecompileODBCDataSourceTable(table); - break; - case "ODBCDriver": - this.DecompileODBCDriverTable(table); - break; - case "ODBCSourceAttribute": - this.DecompileODBCSourceAttributeTable(table); - break; - case "ODBCTranslator": - this.DecompileODBCTranslatorTable(table); - break; - case "PatchMetadata": - this.DecompilePatchMetadataTable(table); - break; - case "PatchSequence": - this.DecompilePatchSequenceTable(table); - break; - case "ProgId": - this.DecompileProgIdTable(table); - break; - case "Properties": - this.DecompilePropertiesTable(table); - break; - case "Property": - this.DecompilePropertyTable(table); - break; - case "PublishComponent": - this.DecompilePublishComponentTable(table); - break; - case "RadioButton": - this.DecompileRadioButtonTable(table); - break; - case "Registry": - this.DecompileRegistryTable(table); - break; - case "RegLocator": - this.DecompileRegLocatorTable(table); - break; - case "RemoveFile": - this.DecompileRemoveFileTable(table); - break; - case "RemoveIniFile": - this.DecompileRemoveIniFileTable(table); - break; - case "RemoveRegistry": - this.DecompileRemoveRegistryTable(table); - break; - case "ReserveCost": - this.DecompileReserveCostTable(table); - break; - case "SelfReg": - this.DecompileSelfRegTable(table); - break; - case "ServiceControl": - this.DecompileServiceControlTable(table); - break; - case "ServiceInstall": - this.DecompileServiceInstallTable(table); - break; - case "SFPCatalog": - this.DecompileSFPCatalogTable(table); - break; - case "Shortcut": - this.DecompileShortcutTable(table); - break; - case "Signature": - this.DecompileSignatureTable(table); - break; - case "TargetFiles_OptionalData": - this.DecompileTargetFiles_OptionalDataTable(table); - break; - case "TargetImages": - this.DecompileTargetImagesTable(table); - break; - case "TextStyle": - this.DecompileTextStyleTable(table); - break; - case "TypeLib": - this.DecompileTypeLibTable(table); - break; - case "Upgrade": - this.DecompileUpgradeTable(table); - break; - case "UpgradedFiles_OptionalData": - this.DecompileUpgradedFiles_OptionalDataTable(table); - break; - case "UpgradedFilesToIgnore": - this.DecompileUpgradedFilesToIgnoreTable(table); - break; - case "UpgradedImages": - this.DecompileUpgradedImagesTable(table); - break; - case "UIText": - this.DecompileUITextTable(table); - break; - case "Verb": - this.DecompileVerbTable(table); - break; - - default: -#if TODO_DECOMPILER_EXTENSIONS - if (this.ExtensionsByTableName.TryGetValue(table.Name, out var extension) - { - extension.DecompileTable(table); - } - else -#endif - if (!this.SuppressCustomTables) - { - this.DecompileCustomTable(table); - } - break; - } - } - } - - /// - /// Determine if a particular table should be decompiled with the current settings. - /// - /// The output being decompiled. - /// The name of a table. - /// true if the table should be decompiled; false otherwise. - private bool DecompilableTable(WindowsInstallerData output, string tableName) - { - switch (tableName) - { - case "ActionText": - case "BBControl": - case "Billboard": - case "CheckBox": - case "Control": - case "ControlCondition": - case "ControlEvent": - case "Dialog": - case "Error": - case "EventMapping": - case "RadioButton": - case "TextStyle": - case "UIText": - return !this.SuppressUI; - case "ModuleAdminExecuteSequence": - case "ModuleAdminUISequence": - case "ModuleAdvtExecuteSequence": - case "ModuleAdvtUISequence": - case "ModuleComponents": - case "ModuleConfiguration": - case "ModuleDependency": - case "ModuleIgnoreTable": - case "ModuleInstallExecuteSequence": - case "ModuleInstallUISequence": - case "ModuleExclusion": - case "ModuleSignature": - case "ModuleSubstitution": - if (OutputType.Module != output.Type) - { - this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName)); - return false; - } - else - { - return true; - } - case "ExternalFiles": - case "FamilyFileRanges": - case "ImageFamilies": - case "PatchMetadata": - case "PatchSequence": - case "Properties": - case "TargetFiles_OptionalData": - case "TargetImages": - case "UpgradedFiles_OptionalData": - case "UpgradedFilesToIgnore": - case "UpgradedImages": - if (OutputType.PatchCreation != output.Type) - { - this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName)); - return false; - } - else - { - return true; - } - case "MsiPatchHeaders": - case "MsiPatchMetadata": - case "MsiPatchOldAssemblyName": - case "MsiPatchOldAssemblyFile": - case "MsiPatchSequence": - case "Patch": - case "PatchPackage": - this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName)); - return false; - case "_SummaryInformation": - return true; - case "_Validation": - case "MsiAssemblyName": - case "MsiFileHash": - return false; - default: // all other tables are allowed in any output except for a patch creation package - if (OutputType.PatchCreation == output.Type) - { - this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName)); - return false; - } - else - { - return true; - } - } - } - - /// - /// Decompile the _SummaryInformation table. - /// - /// The tables to decompile. - private void FinalizeSummaryInformationStream(TableIndexedCollection tables) - { - var table = tables["_SummaryInformation"]; - - if (OutputType.Module == this.OutputType || OutputType.Product == this.OutputType) - { - var xSummaryInformation = new XElement(Names.SummaryInformationElement); - - foreach (var row in table.Rows) - { - var value = row.FieldAsString(1); - - if (!String.IsNullOrEmpty(value)) - { - switch (row.FieldAsInteger(0)) - { - case 1: - if ("1252" != value) - { - xSummaryInformation.SetAttributeValue("Codepage", value); - } - break; - case 3: - { - var productName = this.RootElement.Attribute("Name")?.Value; - if (value != productName) - { - xSummaryInformation.SetAttributeValue("Description", value); - } - break; - } - case 4: - { - var productManufacturer = this.RootElement.Attribute("Manufacturer")?.Value; - if (value != productManufacturer) - { - xSummaryInformation.SetAttributeValue("Manufacturer", value); - } - break; - } - case 5: - if ("Installer" != value) - { - xSummaryInformation.SetAttributeValue("Keywords", value); - } - break; - case 7: - var template = value.Split(';'); - if (0 < template.Length && 0 < template[template.Length - 1].Length) - { - this.RootElement.SetAttributeValue("Language", template[template.Length - 1]); - } - break; - case 14: - var installerVersion = row.FieldAsInteger(1); - // Default InstallerVersion. - if (installerVersion != 500) - { - this.RootElement.SetAttributeValue("InstallerVersion", installerVersion); - } - break; - case 15: - var wordCount = row.FieldAsInteger(1); - if (0x1 == (wordCount & 0x1)) - { - this.ShortNames = true; - if (OutputType.Product == this.OutputType) - { - this.RootElement.SetAttributeValue("ShortNames", "yes"); - } - } - - if (0x2 == (wordCount & 0x2)) - { - this.Compressed = true; - - if (OutputType.Product == this.OutputType) - { - this.RootElement.SetAttributeValue("Compressed", "yes"); - } - } - - if (OutputType.Product == this.OutputType) - { - if (0x8 == (wordCount & 0x8)) - { - this.RootElement.SetAttributeValue("Scope", "perUser"); - } - else - { - var xAllUsers = this.RootElement.Elements(Names.PropertyElement).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS"); - if (xAllUsers?.Attribute("Value")?.Value == "1") - { - xAllUsers?.Remove(); - } - } - } - - break; - } - } - } - - if (xSummaryInformation.HasAttributes) - { - this.RootElement.Add(xSummaryInformation); - } - } - else - { - var xPatchInformation = new XElement(Names.PatchInformationElement); - - foreach (var row in table.Rows) - { - var propertyId = row.FieldAsInteger(0); - var value = row.FieldAsString(1); - - if (!String.IsNullOrEmpty(value)) - { - switch (propertyId) - { - case 1: - if ("1252" != value) - { - xPatchInformation.SetAttributeValue("SummaryCodepage", value); - } - break; - case 3: - xPatchInformation.SetAttributeValue("Description", value); - break; - case 4: - xPatchInformation.SetAttributeValue("Manufacturer", value); - break; - case 5: - if ("Installer,Patching,PCP,Database" != value) - { - xPatchInformation.SetAttributeValue("Keywords", value); - } - break; - case 6: - xPatchInformation.SetAttributeValue("Comments", value); - break; - case 19: - var security = Convert.ToInt32(value, CultureInfo.InvariantCulture); - switch (security) - { - case 0: - xPatchInformation.SetAttributeValue("ReadOnly", "no"); - break; - case 4: - xPatchInformation.SetAttributeValue("ReadOnly", "yes"); - break; - } - break; - } - } - } - - this.RootElement.Add(xPatchInformation); - } - } - - /// - /// Decompile the ActionText table. - /// - /// The table to decompile. - private void DecompileActionTextTable(Table table) - { - foreach (var row in table.Rows) - { - var progressText = new XElement(Names.ProgressTextElement, - new XAttribute("Action", row.FieldAsString(0)), - row.IsColumnNull(1) ? null : new XAttribute("Message", row.FieldAsString(1)), - row.IsColumnNull(2) ? null : new XAttribute("Template", row.FieldAsString(2))); - - this.UIElement.Add(progressText); - } - } - - /// - /// Decompile the AppId table. - /// - /// The table to decompile. - private void DecompileAppIdTable(Table table) - { - foreach (var row in table.Rows) - { - var appId = new XElement(Names.AppIdElement, - new XAttribute("Advertise", "yes"), - new XAttribute("Id", row.FieldAsString(0)), - row.IsColumnNull(1) ? null : new XAttribute("RemoteServerName", row.FieldAsString(1)), - row.IsColumnNull(2) ? null : new XAttribute("LocalService", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("ServiceParameters", row.FieldAsString(3)), - row.IsColumnNull(4) ? null : new XAttribute("DllSurrogate", row.FieldAsString(4)), - row.IsColumnNull(5) || row.FieldAsInteger(5) != 1 ? null : new XAttribute("ActivateAtStorage", "yes"), - row.IsColumnNull(6) || row.FieldAsInteger(6) != 1 ? null : new XAttribute("RunAsInteractiveUser", "yes")); - - this.RootElement.Add(appId); - this.IndexElement(row, appId); - } - } - - /// - /// Decompile the BBControl table. - /// - /// The table to decompile. - private void DecompileBBControlTable(Table table) - { - foreach (BBControlRow bbControlRow in table.Rows) - { - var xControl = new XElement(Names.ControlElement, - new XAttribute("Id", bbControlRow.BBControl), - new XAttribute("Type", bbControlRow.Type), - new XAttribute("X", bbControlRow.X), - new XAttribute("Y", bbControlRow.Y), - new XAttribute("Width", bbControlRow.Width), - new XAttribute("Height", bbControlRow.Height), - null == bbControlRow.Text ? null : new XAttribute("Text", bbControlRow.Text)); - - if (null != bbControlRow[7]) - { - SetControlAttributes(bbControlRow.Attributes, xControl); - } - - if (this.TryGetIndexedElement("Billboard", out var xBillboard, bbControlRow.Billboard)) - { - xBillboard.Add(xControl); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard")); - } - } - } - - /// - /// Decompile the Billboard table. - /// - /// The table to decompile. - private void DecompileBillboardTable(Table table) - { - var billboards = new SortedList(); - - foreach (var row in table.Rows) - { - var xBillboard = new XElement(Names.BillboardElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Feature", row.FieldAsString(1))); - - this.IndexElement(row, xBillboard); - billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row); - } - - var billboardActions = new Dictionary(); - - foreach (var row in billboards.Values) - { - var xBillboard = this.GetIndexedElement(row); - - if (!billboardActions.TryGetValue(row.FieldAsString(2), out var xBillboardAction)) - { - xBillboardAction = new XElement(Names.BillboardActionElement, - new XAttribute("Id", row.FieldAsString(2))); - - this.UIElement.Add(xBillboardAction); - billboardActions.Add(row.FieldAsString(2), xBillboardAction); - } - - xBillboardAction.Add(xBillboard); - } - } - - /// - /// Decompile the Binary table. - /// - /// The table to decompile. - private void DecompileBinaryTable(Table table) - { - foreach (var row in table.Rows) - { - var xBinary = new XElement(Names.BinaryElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1))); - - this.RootElement.Add(xBinary); - } - } - - /// - /// Decompile the BindImage table. - /// - /// The table to decompile. - private void DecompileBindImageTable(Table table) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) - { - xFile.SetAttributeValue("BindPath", row.FieldAsString(1)); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); - } - } - } - - /// - /// Decompile the Class table. - /// - /// The table to decompile. - private void DecompileClassTable(Table table) - { - foreach (var row in table.Rows) - { - var xClass = new XElement(Names.ClassElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Advertise", "yes"), - new XAttribute("Context", row.FieldAsString(1)), - row.IsColumnNull(4) ? null : new XAttribute("Description", row.FieldAsString(4)), - row.IsColumnNull(5) ? null : new XAttribute("AppId", row.FieldAsString(5)), - row.IsColumnNull(7) ? null : new XAttribute("Icon", row.FieldAsString(7)), - row.IsColumnNull(8) ? null : new XAttribute("IconIndex", row.FieldAsString(8)), - row.IsColumnNull(9) ? null : new XAttribute("Handler", row.FieldAsString(9)), - row.IsColumnNull(10) ? null : new XAttribute("Argument", row.FieldAsString(10))); - - if (!row.IsColumnNull(6)) - { - var fileTypeMaskStrings = row.FieldAsString(6).Split(';'); - - try - { - foreach (var fileTypeMaskString in fileTypeMaskStrings) - { - var fileTypeMaskParts = fileTypeMaskString.Split(','); - - if (4 == fileTypeMaskParts.Length) - { - var xFileTypeMask = new XElement(Names.FileTypeMaskElement, - new XAttribute("Offset", Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture)), - new XAttribute("Mask", fileTypeMaskParts[2]), - new XAttribute("Value", fileTypeMaskParts[3])); - - xClass.Add(xFileTypeMask); - } - else - { - // TODO: warn - } - } - } - catch (FormatException) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - } - catch (OverflowException) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - } - } - - if (!row.IsColumnNull(12)) - { - if (1 == row.FieldAsInteger(12)) - { - xClass.SetAttributeValue("RelativePath", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12])); - } - } - - this.AddChildToParent("Component", xClass, row, 2); - this.IndexElement(row, xClass); - } - } - - /// - /// Decompile the ComboBox table. - /// - /// The table to decompile. - private void DecompileComboBoxTable(Table table) - { - // sort the combo boxes by their property and order - var comboBoxRows = table.Rows.Select(row => row).OrderBy(row => String.Format("{0}|{1:0000000000}", row.FieldAsString(0), row.FieldAsInteger(1))); - - XElement xComboBox = null; - string property = null; - foreach (var row in comboBoxRows) - { - if (null == xComboBox || row.FieldAsString(0) != property) - { - property = row.FieldAsString(0); - - xComboBox = new XElement(Names.ComboBoxElement, - new XAttribute("Property", property)); - - this.UIElement.Add(xComboBox); - } - - var xListItem = new XElement(Names.ListItemElement, - new XAttribute("Value", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); - xComboBox.Add(xListItem); - } - } - - /// - /// Decompile the Control table. - /// - /// The table to decompile. - private void DecompileControlTable(Table table) - { - foreach (ControlRow controlRow in table.Rows) - { - var xControl = new XElement(Names.ControlElement, - new XAttribute("Id", controlRow.Control), - new XAttribute("Type", controlRow.Type), - new XAttribute("X", controlRow.X), - new XAttribute("Y", controlRow.Y), - new XAttribute("Width", controlRow.Width), - new XAttribute("Height", controlRow.Height), - new XAttribute("Text", controlRow.Text)); - - if (!controlRow.IsColumnNull(7)) - { - string[] specialAttributes; - - // sets various common attributes like Disabled, Indirect, Integer, ... - SetControlAttributes(controlRow.Attributes, xControl); - - switch (controlRow.Type) - { - case "Bitmap": - specialAttributes = BitmapControlAttributes; - break; - case "CheckBox": - specialAttributes = CheckboxControlAttributes; - break; - case "ComboBox": - specialAttributes = ComboboxControlAttributes; - break; - case "DirectoryCombo": - specialAttributes = VolumeControlAttributes; - break; - case "Edit": - specialAttributes = EditControlAttributes; - break; - case "Icon": - specialAttributes = IconControlAttributes; - break; - case "ListBox": - specialAttributes = ListboxControlAttributes; - break; - case "ListView": - specialAttributes = ListviewControlAttributes; - break; - case "MaskedEdit": - specialAttributes = EditControlAttributes; - break; - case "PathEdit": - specialAttributes = EditControlAttributes; - break; - case "ProgressBar": - specialAttributes = ProgressControlAttributes; - break; - case "PushButton": - specialAttributes = ButtonControlAttributes; - break; - case "RadioButtonGroup": - specialAttributes = RadioControlAttributes; - break; - case "Text": - specialAttributes = TextControlAttributes; - break; - case "VolumeCostList": - specialAttributes = VolumeControlAttributes; - break; - case "VolumeSelectCombo": - specialAttributes = VolumeControlAttributes; - break; - default: - specialAttributes = null; - break; - } - - if (null != specialAttributes) - { - var iconSizeSet = false; - - for (var i = 16; 32 > i; i++) - { - if (1 == ((controlRow.Attributes >> i) & 1)) - { - string attribute = null; - - if (specialAttributes.Length > (i - 16)) - { - attribute = specialAttributes[i - 16]; - } - - // unknown attribute - if (null == attribute) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); - continue; - } - - switch (attribute) - { - case "Bitmap": - xControl.SetAttributeValue("Bitmap", "yes"); - break; - case "CDROM": - xControl.SetAttributeValue("CDROM", "yes"); - break; - case "ComboList": - xControl.SetAttributeValue("ComboList", "yes"); - break; - case "ElevationShield": - xControl.SetAttributeValue("ElevationShield", "yes"); - break; - case "Fixed": - xControl.SetAttributeValue("Fixed", "yes"); - break; - case "FixedSize": - xControl.SetAttributeValue("FixedSize", "yes"); - break; - case "Floppy": - xControl.SetAttributeValue("Floppy", "yes"); - break; - case "FormatSize": - xControl.SetAttributeValue("FormatSize", "yes"); - break; - case "HasBorder": - xControl.SetAttributeValue("HasBorder", "yes"); - break; - case "Icon": - xControl.SetAttributeValue("Icon", "yes"); - break; - case "Icon16": - if (iconSizeSet) - { - xControl.SetAttributeValue("IconSize", "48"); - } - else - { - iconSizeSet = true; - xControl.SetAttributeValue("IconSize", "16"); - } - break; - case "Icon32": - if (iconSizeSet) - { - xControl.SetAttributeValue("IconSize", "48"); - } - else - { - iconSizeSet = true; - xControl.SetAttributeValue("IconSize", "32"); - } - break; - case "Image": - xControl.SetAttributeValue("Image", "yes"); - break; - case "Multiline": - xControl.SetAttributeValue("Multiline", "yes"); - break; - case "NoPrefix": - xControl.SetAttributeValue("NoPrefix", "yes"); - break; - case "NoWrap": - xControl.SetAttributeValue("NoWrap", "yes"); - break; - case "Password": - xControl.SetAttributeValue("Password", "yes"); - break; - case "ProgressBlocks": - xControl.SetAttributeValue("ProgressBlocks", "yes"); - break; - case "PushLike": - xControl.SetAttributeValue("PushLike", "yes"); - break; - case "RAMDisk": - xControl.SetAttributeValue("RAMDisk", "yes"); - break; - case "Remote": - xControl.SetAttributeValue("Remote", "yes"); - break; - case "Removable": - xControl.SetAttributeValue("Removable", "yes"); - break; - case "ShowRollbackCost": - xControl.SetAttributeValue("ShowRollbackCost", "yes"); - break; - case "Sorted": - xControl.SetAttributeValue("Sorted", "yes"); - break; - case "Transparent": - xControl.SetAttributeValue("Transparent", "yes"); - break; - case "UserLanguage": - xControl.SetAttributeValue("UserLanguage", "yes"); - break; - default: - throw new InvalidOperationException($"Unknown control attribute: '{attribute}'."); - } - } - } - } - else if (0 < (controlRow.Attributes & 0xFFFF0000)) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); - } - } - - // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef - if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", controlRow.Type)) - { - xControl.SetAttributeValue("Property", controlRow.Property); - } - - if (null != controlRow.Help) - { - var help = controlRow.Help.Split('|'); - - if (2 == help.Length) - { - if (0 < help[0].Length) - { - xControl.SetAttributeValue("ToolTip", help[0]); - } - - if (0 < help[1].Length) - { - xControl.SetAttributeValue("Help", help[1]); - } - } - } - - this.IndexElement(controlRow, xControl); - } - } - - /// - /// Decompile the ControlCondition table. - /// - /// The table to decompile. - private void DecompileControlConditionTable(Table table) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) - { - switch (row.FieldAsString(2)) - { - case "Default": - xControl.SetAttributeValue("DefaultCondition", row.FieldAsString(3)); - break; - case "Disable": - xControl.SetAttributeValue("DisableCondition", row.FieldAsString(3)); - break; - case "Enable": - xControl.SetAttributeValue("EnableCondition", row.FieldAsString(3)); - break; - case "Hide": - xControl.SetAttributeValue("HideCondition", row.FieldAsString(3)); - break; - case "Show": - xControl.SetAttributeValue("ShowCondition", row.FieldAsString(3)); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - break; - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); - } - } - } - - /// - /// Decompile the ControlEvent table. - /// - /// The table to decompile. - private void DecompileControlEventTable(Table table) - { - var controlEvents = new SortedList(); - - foreach (var row in table.Rows) - { - var xPublish = new XElement(Names.PublishElement, - new XAttribute("Condition", row.FieldAsString(4))); - - var publishEvent = row.FieldAsString(2); - if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal)) - { - xPublish.SetAttributeValue("Property", publishEvent.Substring(1, publishEvent.Length - 2)); - - if ("{}" != row.FieldAsString(3)) - { - xPublish.SetAttributeValue("Value", row.FieldAsString(3)); - } - } - else - { - xPublish.SetAttributeValue("Event", publishEvent); - xPublish.SetAttributeValue("Value", row.FieldAsString(3)); - } - - controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row.FieldAsString(0), row.FieldAsString(1), row.FieldAsNullableInteger(5) ?? 0, row.FieldAsString(2), row.FieldAsString(3), row.FieldAsString(4)), row); - - this.IndexElement(row, xPublish); - } - - foreach (var row in controlEvents.Values) - { - if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) - { - var xPublish = this.GetIndexedElement(row); - xControl.Add(xPublish); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); - } - } - } - - /// - /// Decompile a custom table. - /// - /// The table to decompile. - private void DecompileCustomTable(Table table) - { - if (0 < table.Rows.Count || this.SuppressDroppingEmptyTables) - { - this.Messaging.Write(WarningMessages.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name)); - - var xCustomTable = new XElement(Names.CustomTableElement, - new XAttribute("Id", table.Name)); - - foreach (var columnDefinition in table.Definition.Columns) - { - var xColumn = new XElement(Names.ColumnElement, - new XAttribute("Id", columnDefinition.Name), - columnDefinition.Description == null ? null : new XAttribute("Description", columnDefinition.Description), - columnDefinition.KeyTable == null ? null : new XAttribute("KeyTable", columnDefinition.KeyTable), - !columnDefinition.KeyColumn.HasValue ? null : new XAttribute("KeyColumn", columnDefinition.KeyColumn.Value), - !columnDefinition.IsLocalizable ? null : new XAttribute("Localizable", "yes"), - !columnDefinition.MaxValue.HasValue ? null : new XAttribute("MaxValue", columnDefinition.MaxValue.Value), - !columnDefinition.MinValue.HasValue ? null : new XAttribute("MinValue", columnDefinition.MinValue.Value), - !columnDefinition.Nullable ? null : new XAttribute("Nullable", "yes"), - !columnDefinition.PrimaryKey ? null : new XAttribute("PrimaryKey", "yes"), - columnDefinition.Possibilities == null ? null : new XAttribute("Possibilities", "yes"), - new XAttribute("Width", columnDefinition.Length)); - - if (ColumnCategory.Unknown != columnDefinition.Category) - { - switch (columnDefinition.Category) - { - case ColumnCategory.Text: - xColumn.SetAttributeValue("Category", "text"); - break; - case ColumnCategory.UpperCase: - xColumn.SetAttributeValue("Category", "upperCase"); - break; - case ColumnCategory.LowerCase: - xColumn.SetAttributeValue("Category", "lowerCase"); - break; - case ColumnCategory.Integer: - xColumn.SetAttributeValue("Category", "integer"); - break; - case ColumnCategory.DoubleInteger: - xColumn.SetAttributeValue("Category", "doubleInteger"); - break; - case ColumnCategory.TimeDate: - xColumn.SetAttributeValue("Category", "timeDate"); - break; - case ColumnCategory.Identifier: - xColumn.SetAttributeValue("Category", "identifier"); - break; - case ColumnCategory.Property: - xColumn.SetAttributeValue("Category", "property"); - break; - case ColumnCategory.Filename: - xColumn.SetAttributeValue("Category", "filename"); - break; - case ColumnCategory.WildCardFilename: - xColumn.SetAttributeValue("Category", "wildCardFilename"); - break; - case ColumnCategory.Path: - xColumn.SetAttributeValue("Category", "path"); - break; - case ColumnCategory.Paths: - xColumn.SetAttributeValue("Category", "paths"); - break; - case ColumnCategory.AnyPath: - xColumn.SetAttributeValue("Category", "anyPath"); - break; - case ColumnCategory.DefaultDir: - xColumn.SetAttributeValue("Category", "defaultDir"); - break; - case ColumnCategory.RegPath: - xColumn.SetAttributeValue("Category", "regPath"); - break; - case ColumnCategory.Formatted: - xColumn.SetAttributeValue("Category", "formatted"); - break; - case ColumnCategory.FormattedSDDLText: - xColumn.SetAttributeValue("Category", "formattedSddl"); - break; - case ColumnCategory.Template: - xColumn.SetAttributeValue("Category", "template"); - break; - case ColumnCategory.Condition: - xColumn.SetAttributeValue("Category", "condition"); - break; - case ColumnCategory.Guid: - xColumn.SetAttributeValue("Category", "guid"); - break; - case ColumnCategory.Version: - xColumn.SetAttributeValue("Category", "version"); - break; - case ColumnCategory.Language: - xColumn.SetAttributeValue("Category", "language"); - break; - case ColumnCategory.Binary: - xColumn.SetAttributeValue("Category", "binary"); - break; - case ColumnCategory.CustomSource: - xColumn.SetAttributeValue("Category", "customSource"); - break; - case ColumnCategory.Cabinet: - xColumn.SetAttributeValue("Category", "cabinet"); - break; - case ColumnCategory.Shortcut: - xColumn.SetAttributeValue("Category", "shortcut"); - break; - default: - throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'."); - } - } - - if (ColumnModularizeType.None != columnDefinition.ModularizeType) - { - switch (columnDefinition.ModularizeType) - { - case ColumnModularizeType.Column: - xColumn.SetAttributeValue("Modularize", "Column"); - break; - case ColumnModularizeType.Condition: - xColumn.SetAttributeValue("Modularize", "Condition"); - break; - case ColumnModularizeType.Icon: - xColumn.SetAttributeValue("Modularize", "Icon"); - break; - case ColumnModularizeType.Property: - xColumn.SetAttributeValue("Modularize", "Property"); - break; - case ColumnModularizeType.SemicolonDelimited: - xColumn.SetAttributeValue("Modularize", "SemicolonDelimited"); - break; - default: - throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'."); - } - } - - if (ColumnType.Unknown != columnDefinition.Type) - { - switch (columnDefinition.Type) - { - case ColumnType.Localized: - xColumn.SetAttributeValue("Localizable", "yes"); - xColumn.SetAttributeValue("Type", "string"); - break; - case ColumnType.Number: - xColumn.SetAttributeValue("Type", "int"); - break; - case ColumnType.Object: - xColumn.SetAttributeValue("Type", "binary"); - break; - case ColumnType.Preserved: - case ColumnType.String: - xColumn.SetAttributeValue("Type", "string"); - break; - default: - throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type}'."); - } - } - - xCustomTable.Add(xColumn); - } - - foreach (var row in table.Rows) - { - var xRow = new XElement(Names.RowElement); - - foreach (var field in row.Fields.Where(f => f.Data != null)) - { - var xData = new XElement(Names.DataElement, - new XAttribute("Column", field.Column.Name), - new XAttribute("Value", field.AsString())); - - xRow.Add(xData); - } - - xCustomTable.Add(xRow); - } - - this.RootElement.Add(xCustomTable); - } - } - - /// - /// Decompile the CreateFolder table. - /// - /// The table to decompile. - private void DecompileCreateFolderTable(Table table) - { - foreach (var row in table.Rows) - { - var xCreateFolder = new XElement(Names.CreateFolderElement, - new XAttribute("Directory", row.FieldAsString(0))); - - this.AddChildToParent("Component", xCreateFolder, row, 1); - this.IndexElement(row, xCreateFolder); - } - } - - /// - /// Decompile the CustomAction table. - /// - /// The table to decompile. - private void DecompileCustomActionTable(Table table) - { - foreach (var row in table.Rows) - { - var xCustomAction = new XElement(Names.CustomActionElement, - new XAttribute("Id", row.FieldAsString(0))); - - var type = row.FieldAsInteger(1); - - if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (type & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget)) - { - xCustomAction.SetAttributeValue("HideTarget", "yes"); - } - - if (WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate == (type & WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate)) - { - xCustomAction.SetAttributeValue("Impersonate", "no"); - } - - if (WindowsInstallerConstants.MsidbCustomActionTypeTSAware == (type & WindowsInstallerConstants.MsidbCustomActionTypeTSAware)) - { - xCustomAction.SetAttributeValue("TerminalServerAware", "yes"); - } - - if (WindowsInstallerConstants.MsidbCustomActionType64BitScript == (type & WindowsInstallerConstants.MsidbCustomActionType64BitScript)) - { - xCustomAction.SetAttributeValue("Bitness", "always64"); - } - else if (WindowsInstallerConstants.MsidbCustomActionTypeVBScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) || - WindowsInstallerConstants.MsidbCustomActionTypeJScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeJScript)) - { - xCustomAction.SetAttributeValue("Bitness", "always32"); - } - - switch (type & WindowsInstallerConstants.MsidbCustomActionTypeExecuteBits) - { - case 0: - // this is the default value - break; - case WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence: - xCustomAction.SetAttributeValue("Execute", "firstSequence"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess: - xCustomAction.SetAttributeValue("Execute", "oncePerProcess"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat: - xCustomAction.SetAttributeValue("Execute", "secondSequence"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInScript: - xCustomAction.SetAttributeValue("Execute", "deferred"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeRollback: - xCustomAction.SetAttributeValue("Execute", "rollback"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeCommit: - xCustomAction.SetAttributeValue("Execute", "commit"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - switch (type & WindowsInstallerConstants.MsidbCustomActionTypeReturnBits) - { - case 0: - // this is the default value - break; - case WindowsInstallerConstants.MsidbCustomActionTypeContinue: - xCustomAction.SetAttributeValue("Return", "ignore"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeAsync: - xCustomAction.SetAttributeValue("Return", "asyncWait"); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeAsync + WindowsInstallerConstants.MsidbCustomActionTypeContinue: - xCustomAction.SetAttributeValue("Return", "asyncNoWait"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - var source = type & WindowsInstallerConstants.MsidbCustomActionTypeSourceBits; - switch (source) - { - case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: - xCustomAction.SetAttributeValue("BinaryRef", row.FieldAsString(2)); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: - if (!row.IsColumnNull(2)) - { - xCustomAction.SetAttributeValue("FileRef", row.FieldAsString(2)); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeDirectory: - if (!row.IsColumnNull(2)) - { - xCustomAction.SetAttributeValue("Directory", row.FieldAsString(2)); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeProperty: - xCustomAction.SetAttributeValue("Property", row.FieldAsString(2)); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - switch (type & WindowsInstallerConstants.MsidbCustomActionTypeTargetBits) - { - case WindowsInstallerConstants.MsidbCustomActionTypeDll: - xCustomAction.SetAttributeValue("DllEntry", row.FieldAsString(3)); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeExe: - xCustomAction.SetAttributeValue("ExeCommand", row.FieldAsString(3)); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeTextData: - if (WindowsInstallerConstants.MsidbCustomActionTypeSourceFile == source) - { - xCustomAction.SetAttributeValue("Error", row.FieldAsString(3)); - } - else - { - xCustomAction.SetAttributeValue("Value", row.FieldAsString(3)); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeJScript: - if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) - { - xCustomAction.SetAttributeValue("Script", "jscript"); - // TODO: Extract to @ScriptFile? - // xCustomAction.Content = row.FieldAsString(3); - } - else - { - xCustomAction.SetAttributeValue("JScriptCall", row.FieldAsString(3)); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeVBScript: - if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) - { - xCustomAction.SetAttributeValue("Script", "vbscript"); - // TODO: Extract to @ScriptFile? - // xCustomAction.Content = row.FieldAsString(3); - } - else - { - xCustomAction.SetAttributeValue("VBScriptCall", row.FieldAsString(3)); - } - break; - case WindowsInstallerConstants.MsidbCustomActionTypeInstall: - this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - continue; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - var extype = 4 < row.Fields.Length && !row.IsColumnNull(4) ? row.FieldAsInteger(4) : 0; - if (WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall == (extype & WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall)) - { - xCustomAction.SetAttributeValue("PatchUninstall", "yes"); - } - - this.RootElement.Add(xCustomAction); - this.IndexElement(row, xCustomAction); - } - } - - /// - /// Decompile the CompLocator table. - /// - /// The table to decompile. - private void DecompileCompLocatorTable(Table table) - { - foreach (var row in table.Rows) - { - var xComponentSearch = new XElement(Names.ComponentSearchElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Guid", row.FieldAsString(1))); - - if (!row.IsColumnNull(2)) - { - switch (row.FieldAsInteger(2)) - { - case WindowsInstallerConstants.MsidbLocatorTypeDirectory: - xComponentSearch.SetAttributeValue("Type", "directory"); - break; - case WindowsInstallerConstants.MsidbLocatorTypeFileName: - // this is the default value - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - break; - } - } - - this.IndexElement(row, xComponentSearch); - } - } - - /// - /// Decompile the Complus table. - /// - /// The table to decompile. - private void DecompileComplusTable(Table table) - { - foreach (var row in table.Rows) - { - if (!row.IsColumnNull(1)) - { - if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) - { - xComponent.SetAttributeValue("ComPlusFlags", row.FieldAsInteger(1)); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); - } - } - } - } - - /// - /// Decompile the Component table. - /// - /// The table to decompile. - private void DecompileComponentTable(Table table) - { - foreach (var row in table.Rows) - { - var xComponent = new XElement(Names.ComponentElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Guid", row.FieldAsString(1) ?? String.Empty)); - - var attributes = row.FieldAsInteger(3); - - if (WindowsInstallerConstants.MsidbComponentAttributesSourceOnly == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSourceOnly)) - { - xComponent.SetAttributeValue("Location", "source"); - } - else if (WindowsInstallerConstants.MsidbComponentAttributesOptional == (attributes & WindowsInstallerConstants.MsidbComponentAttributesOptional)) - { - xComponent.SetAttributeValue("Location", "either"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount)) - { - xComponent.SetAttributeValue("SharedDllRefCount", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesPermanent == (attributes & WindowsInstallerConstants.MsidbComponentAttributesPermanent)) - { - xComponent.SetAttributeValue("Permanent", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesTransitive == (attributes & WindowsInstallerConstants.MsidbComponentAttributesTransitive)) - { - xComponent.SetAttributeValue("Transitive", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite == (attributes & WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite)) - { - xComponent.SetAttributeValue("NeverOverwrite", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributes64bit == (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit)) - { - xComponent.SetAttributeValue("Bitness", "always64"); - } - else - { - xComponent.SetAttributeValue("Bitness", "always32"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection == (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection)) - { - xComponent.SetAttributeValue("DisableRegistryReflection", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence == (attributes & WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence)) - { - xComponent.SetAttributeValue("UninstallWhenSuperseded", "yes"); - } - - if (WindowsInstallerConstants.MsidbComponentAttributesShared == (attributes & WindowsInstallerConstants.MsidbComponentAttributesShared)) - { - xComponent.SetAttributeValue("Shared", "yes"); - } - - if (!row.IsColumnNull(4)) - { - xComponent.SetAttributeValue("Condition", row.FieldAsString(4)); - } - - this.AddChildToParent("Directory", xComponent, row, 2); - this.IndexElement(row, xComponent); - } - } - - /// - /// Decompile the Condition table. - /// - /// The table to decompile. - private void DecompileConditionTable(Table table) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("Feature", out var xFeature, row.FieldAsString(0))) - { - var xLevel = new XElement(Names.LevelElement, - row.IsColumnNull(2) ? null : new XAttribute("Condition", row.FieldAsString(2)), - new XAttribute("Level", row.FieldAsInteger(1))); - - xFeature.Add(xLevel); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", row.FieldAsString(0), "Feature")); - } - } - } - - /// - /// Decompile the Dialog table. - /// - /// The table to decompile. - private void DecompileDialogTable(Table table) - { - foreach (var row in table.Rows) - { - var attributes = row.FieldAsNullableInteger(5) ?? 0; - - var xDialog = new XElement(Names.DialogElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("X", row.FieldAsString(1)), - new XAttribute("Y", row.FieldAsString(2)), - new XAttribute("Width", row.FieldAsString(3)), - new XAttribute("Height", row.FieldAsString(4)), - 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesVisible) ? new XAttribute("Hidden", "yes") : null, - 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesModal) ? new XAttribute("Modeless", "yes") : null, - 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, - 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesSysModal == (attributes & WindowsInstallerConstants.MsidbDialogAttributesSysModal) ? new XAttribute("SystemModal", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesKeepModeless == (attributes & WindowsInstallerConstants.MsidbDialogAttributesKeepModeless) ? new XAttribute("KeepModeless", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace == (attributes & WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace) ? new XAttribute("TrackDiskSpace", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette == (attributes & WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette) ? new XAttribute("CustomPalette", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbDialogAttributesLeftScroll) ? new XAttribute("LeftScroll", "yes") : null, - WindowsInstallerConstants.MsidbDialogAttributesError == (attributes & WindowsInstallerConstants.MsidbDialogAttributesError) ? new XAttribute("ErrorDialog", "yes") : null, - !row.IsColumnNull(6) ? new XAttribute("Title", row.FieldAsString(6)) : null); - - this.UIElement.Add(xDialog); - this.IndexElement(row, xDialog); - } - } - - /// - /// Decompile the Directory table. - /// - /// The table to decompile. - private void DecompileDirectoryTable(Table table) - { - foreach (var row in table.Rows) - { - var id = row.FieldAsString(0); - var elementName = WindowsInstallerStandard.IsStandardDirectory(id) ? Names.StandardDirectoryElement : Names.DirectoryElement; - var xDirectory = new XElement(elementName, - new XAttribute("Id", id)); - - if (!WindowsInstallerStandard.IsStandardDirectory(id)) - { - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); - - if (id == "TARGETDIR" && names[0] != "SourceDir") - { - this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir()); - xDirectory.SetAttributeValue("Name", "SourceDir"); - } - else - { - if (null != names[0] && "." != names[0]) - { - if (null != names[1]) - { - xDirectory.SetAttributeValue("ShortName", names[0]); - } - else - { - xDirectory.SetAttributeValue("Name", names[0]); - } - } - - if (null != names[1]) - { - xDirectory.SetAttributeValue("Name", names[1]); - } - } - - if (null != names[2]) - { - if (null != names[3]) - { - xDirectory.SetAttributeValue("ShortSourceName", names[2]); - } - else - { - xDirectory.SetAttributeValue("SourceName", names[2]); - } - } - - if (null != names[3]) - { - xDirectory.SetAttributeValue("SourceName", names[3]); - } - } - - this.IndexElement(row, xDirectory); - } - - // nest the directories - foreach (var row in table.Rows) - { - var xDirectory = this.GetIndexedElement(row); - - var id = row.FieldAsString(0); - - if (id == "TARGETDIR") - { - // Skip TARGETDIR (but see below!). - } - else if (row.IsColumnNull(1) || WindowsInstallerStandard.IsStandardDirectory(id)) - { - this.RootElement.Add(xDirectory); - } - else - { - var parentDirectoryId = row.FieldAsString(1); - - if (!this.TryGetIndexedElement("Directory", out var xParentDirectory, parentDirectoryId)) - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", row.FieldAsString(1), "Directory")); - } - else if (xParentDirectory == xDirectory) // another way to specify a root directory - { - this.RootElement.Add(xDirectory); - } - else - { - // TARGETDIR is omitted but if this directory is a first-generation descendant, add it as a root. - if (parentDirectoryId == "TARGETDIR") - { - this.RootElement.Add(xDirectory); - } - else - { - xParentDirectory.Add(xDirectory); - } - } - } - } - } - - /// - /// Decompile the DrLocator table. - /// - /// The table to decompile. - private void DecompileDrLocatorTable(Table table) - { - foreach (var row in table.Rows) - { - var xDirectorySearch = new XElement(Names.DirectorySearchElement, - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("Path", row, 2), - XAttributeIfNotNull("Depth", row, 3)); - - this.IndexElement(row, xDirectorySearch); - } - } - - /// - /// Decompile the DuplicateFile table. - /// - /// The table to decompile. - private void DecompileDuplicateFileTable(Table table) - { - foreach (var row in table.Rows) - { - var xCopyFile = new XElement(Names.CopyFileElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("FileId", row.FieldAsString(2))); - - if (!row.IsColumnNull(3)) - { - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3)); - if (null != names[0] && null != names[1]) - { - xCopyFile.SetAttributeValue("DestinationShortName", names[0]); - xCopyFile.SetAttributeValue("DestinationName", names[1]); - } - else if (null != names[0]) - { - xCopyFile.SetAttributeValue("DestinationName", names[0]); - } - } - - // destination directory/property is set in FinalizeDuplicateMoveFileTables - - this.AddChildToParent("Component", xCopyFile, row, 1); - this.IndexElement(row, xCopyFile); - } - } - - /// - /// Decompile the Environment table. - /// - /// The table to decompile. - private void DecompileEnvironmentTable(Table table) - { - foreach (var row in table.Rows) - { - var xEnvironment = new XElement(Names.EnvironmentElement, - new XAttribute("Id", row.FieldAsString(0))); - - var done = false; - var permanent = true; - var name = row.FieldAsString(1); - for (var i = 0; i < name.Length && !done; i++) - { - switch (name[i]) - { - case '=': - xEnvironment.SetAttributeValue("Action", "set"); - break; - case '+': - xEnvironment.SetAttributeValue("Action", "create"); - break; - case '-': - permanent = false; - break; - case '!': - xEnvironment.SetAttributeValue("Action", "remove"); - break; - case '*': - xEnvironment.SetAttributeValue("System", "yes"); - break; - default: - xEnvironment.SetAttributeValue("Name", name.Substring(i)); - done = true; - break; - } - } - - if (permanent) - { - xEnvironment.SetAttributeValue("Permanent", "yes"); - } - - if (!row.IsColumnNull(2)) - { - var value = row.FieldAsString(2); - - if (value.StartsWith("[~]", StringComparison.Ordinal)) - { - xEnvironment.SetAttributeValue("Part", "last"); - - if (3 < value.Length) - { - xEnvironment.SetAttributeValue("Separator", value.Substring(3, 1)); - xEnvironment.SetAttributeValue("Value", value.Substring(4)); - } - } - else if (value.EndsWith("[~]", StringComparison.Ordinal)) - { - xEnvironment.SetAttributeValue("Part", "first"); - - if (3 < value.Length) - { - xEnvironment.SetAttributeValue("Separator", value.Substring(value.Length - 4, 1)); - xEnvironment.SetAttributeValue("Value", value.Substring(0, value.Length - 4)); - } - } - else - { - xEnvironment.SetAttributeValue("Value", value); - } - } - - this.AddChildToParent("Component", xEnvironment, row, 3); - } - } - - /// - /// Decompile the Error table. - /// - /// The table to decompile. - private void DecompileErrorTable(Table table) - { - foreach (var row in table.Rows) - { - var xError = new XElement(Names.ErrorElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Message", row.FieldAsString(1))); - - this.UIElement.Add(xError); - } - } - - /// - /// Decompile the EventMapping table. - /// - /// The table to decompile. - private void DecompileEventMappingTable(Table table) - { - foreach (var row in table.Rows) - { - var xSubscribe = new XElement(Names.SubscribeElement, - new XAttribute("Event", row.FieldAsString(2)), - new XAttribute("Attribute", row.FieldAsString(3))); - - if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) - { - xControl.Add(xSubscribe); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); - } - } - } - - /// - /// Decompile the Extension table. - /// - /// The table to decompile. - private void DecompileExtensionTable(Table table) - { - foreach (var row in table.Rows) - { - var xExtension = new XElement(Names.ExtensionElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Advertise", "yes")); - - if (!row.IsColumnNull(3)) - { - if (this.TryGetIndexedElement("MIME", out var xMime, row.FieldAsString(3))) - { - xMime.SetAttributeValue("Default", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); - } - } - - if (!row.IsColumnNull(2)) - { - this.AddChildToParent("ProgId", xExtension, row, 2); - } - else - { - this.AddChildToParent("Component", xExtension, row, 1); - } - - this.IndexElement(row, xExtension); - } - } - - /// - /// Decompile the ExternalFiles table. - /// - /// The table to decompile. - private void DecompileExternalFilesTable(Table table) - { - foreach (var row in table.Rows) - { - var xExternalFile = new XElement(Names.ExternalFileElement, - new XAttribute("File", row.FieldAsString(1)), - new XAttribute("Source", row.FieldAsString(2))); - - AddSymbolPaths(row, 3, xExternalFile); - - if (!row.IsColumnNull(4) && !row.IsColumnNull(5)) - { - var ignoreOffsets = row.FieldAsString(4).Split(','); - var ignoreLengths = row.FieldAsString(5).Split(','); - - if (ignoreOffsets.Length == ignoreLengths.Length) - { - for (var i = 0; i < ignoreOffsets.Length; i++) - { - var xIgnoreRange = new XElement(Names.IgnoreRangeElement); - - if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) - { - xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); - } - else - { - xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); - } - - if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) - { - xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); - } - else - { - xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); - } - - xExternalFile.Add(xIgnoreRange); - } - } - else - { - // TODO: warn - } - } - else if (!row.IsColumnNull(4) || !row.IsColumnNull(5)) - { - // TODO: warn about mismatch between columns - } - - // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable - - if (!row.IsColumnNull(7)) - { - xExternalFile.SetAttributeValue("Order", row.FieldAsInteger(7)); - } - - this.AddChildToParent("ImageFamilies", xExternalFile, row, 0); - this.IndexElement(row, xExternalFile); - } - } - - /// - /// Decompile the Feature table. - /// - /// The table to decompile. - private void DecompileFeatureTable(Table table) - { - var sortedFeatures = new SortedList(); - - foreach (var row in table.Rows) - { - var feature = new XElement(Names.FeatureElement, - new XAttribute("Id", row.FieldAsString(0)), - row.IsColumnNull(2) ? null : new XAttribute("Title", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("Description", row.FieldAsString(3)), - new XAttribute("Level", row.FieldAsInteger(5)), - row.IsColumnNull(6) ? null : new XAttribute("ConfigurableDirectory", row.FieldAsString(6))); - - if (row.IsColumnNull(4)) - { - feature.SetAttributeValue("Display", "hidden"); - } - else - { - var display = row.FieldAsInteger(4); - - if (0 == display) - { - feature.SetAttributeValue("Display", "hidden"); - } - else if (1 == display % 2) - { - feature.SetAttributeValue("Display", "expand"); - } - } - - var attributes = row.FieldAsInteger(7); - - if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource) && WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) - { - // TODO: display a warning for setting favor local and follow parent together - } - else if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource)) - { - feature.SetAttributeValue("InstallDefault", "source"); - } - else if (WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) - { - feature.SetAttributeValue("InstallDefault", "followParent"); - } - - if (WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise)) - { - feature.SetAttributeValue("InstallDefault", "advertise"); - } - - if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise) && - WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) - { - this.Messaging.Write(WarningMessages.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no")); - feature.SetAttributeValue("AllowAdvertise", "no"); - } - else if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise)) - { - feature.SetAttributeValue("AllowAdvertise", "no"); - } - else if (WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) - { - feature.SetAttributeValue("AllowAdvertise", "system"); - } - - if (WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent)) - { - feature.SetAttributeValue("Absent", "disallow"); - } - - this.IndexElement(row, feature); - - // sort the features by their display column (and append the identifier to ensure unique keys) - sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", row.FieldAsInteger(4), row[0]), row); - } - - // nest the features - foreach (var row in sortedFeatures.Values) - { - var xFeature = this.GetIndexedElement("Feature", row.FieldAsString(0)); - - if (row.IsColumnNull(1)) - { - this.RootElement.Add(xFeature); - } - else - { - if (this.TryGetIndexedElement("Feature", out var xParentFeature, row.FieldAsString(1))) - { - if (xParentFeature == xFeature) - { - // TODO: display a warning about self-nesting - } - else - { - xParentFeature.Add(xFeature); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", row.FieldAsString(1), "Feature")); - } - } - } - } - - /// - /// Decompile the FeatureComponents table. - /// - /// The table to decompile. - private void DecompileFeatureComponentsTable(Table table) - { - foreach (var row in table.Rows) - { - var xComponentRef = new XElement(Names.ComponentRefElement, - new XAttribute("Id", row.FieldAsString(1))); - - this.AddChildToParent("Feature", xComponentRef, row, 0); - this.IndexElement(row, xComponentRef); - } - } - - /// - /// Decompile the File table. - /// - /// The table to decompile. - private void DecompileFileTable(Table table) - { - foreach (FileRow fileRow in table.Rows) - { - var xFile = new XElement(Names.FileElement, - new XAttribute("Id", fileRow.File), - WindowsInstallerConstants.MsidbFileAttributesReadOnly == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly) ? new XAttribute("ReadOnly", "yes") : null, - WindowsInstallerConstants.MsidbFileAttributesHidden == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesHidden) ? new XAttribute("Hidden", "yes") : null, - WindowsInstallerConstants.MsidbFileAttributesSystem == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesSystem) ? new XAttribute("System", "yes") : null, - WindowsInstallerConstants.MsidbFileAttributesChecksum == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum) ? new XAttribute("Checksum", "yes") : null, - WindowsInstallerConstants.MsidbFileAttributesVital != (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesVital) ? new XAttribute("Vital", "no") : null, - null != fileRow.Version && 0 < fileRow.Version.Length && !Char.IsDigit(fileRow.Version[0]) ? new XAttribute("CompanionFile", fileRow.Version) : null); - - var names = this.BackendHelper.SplitMsiFileName(fileRow.FileName); - if (null != names[0] && null != names[1]) - { - xFile.SetAttributeValue("ShortName", names[0]); - xFile.SetAttributeValue("Name", names[1]); - } - else if (null != names[0]) - { - xFile.SetAttributeValue("Name", names[0]); - } - - if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) && - WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) - { - // TODO: error - } - else if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed)) - { - xFile.SetAttributeValue("Compressed", "no"); - } - else if (WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) - { - xFile.SetAttributeValue("Compressed", "yes"); - } - - this.IndexElement(fileRow, xFile); - } - } - - /// - /// Decompile the FileSFPCatalog table. - /// - /// The table to decompile. - private void DecompileFileSFPCatalogTable(Table table) - { - foreach (var row in table.Rows) - { - var xSfpFile = new XElement(Names.SFPFileElement, - new XAttribute("Id", row.FieldAsString(0))); - - this.AddChildToParent("SFPCatalog", xSfpFile, row, 1); - } - } - - /// - /// Decompile the Font table. - /// - /// The table to decompile. - private void DecompileFontTable(Table table) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) - { - if (!row.IsColumnNull(1)) - { - xFile.SetAttributeValue("FontTitle", row.FieldAsString(1)); - } - else - { - xFile.SetAttributeValue("TrueType", "yes"); - } - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); - } - } - } - - /// - /// Decompile the Icon table. - /// - /// The table to decompile. - private void DecompileIconTable(Table table) - { - foreach (var row in table.Rows) - { - var icon = new XElement(Names.IconElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1))); - - this.RootElement.Add(icon); - } - } - - /// - /// Decompile the ImageFamilies table. - /// - /// The table to decompile. - private void DecompileImageFamiliesTable(Table table) - { - foreach (var row in table.Rows) - { - var family = new XElement(Names.FamilyElement, - new XAttribute("Name", row.FieldAsString(0)), - row.IsColumnNull(1) ? null : new XAttribute("MediaSrcProp", row.FieldAsString(1)), - row.IsColumnNull(2) ? null : new XAttribute("DiskId", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("SequenceStart", row.FieldAsString(3)), - row.IsColumnNull(4) ? null : new XAttribute("DiskPrompt", row.FieldAsString(4)), - row.IsColumnNull(5) ? null : new XAttribute("VolumeLabel", row.FieldAsString(5))); - - this.RootElement.Add(family); - this.IndexElement(row, family); - } - } - - /// - /// Decompile the IniFile table. - /// - /// The table to decompile. - private void DecompileIniFileTable(Table table) - { - foreach (var row in table.Rows) - { - var xIniFile = new XElement(Names.IniFileElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Section", row.FieldAsString(3)), - new XAttribute("Key", row.FieldAsString(4)), - new XAttribute("Value", row.FieldAsString(5)), - row.IsColumnNull(2) ? null : new XAttribute("Directory", row.FieldAsString(2))); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); - - if (null != names[0]) - { - if (null == names[1]) - { - xIniFile.SetAttributeValue("Name", names[0]); - } - else - { - xIniFile.SetAttributeValue("ShortName", names[0]); - } - } - - if (null != names[1]) - { - xIniFile.SetAttributeValue("Name", names[1]); - } - - switch (row.FieldAsInteger(6)) - { - case WindowsInstallerConstants.MsidbIniFileActionAddLine: - xIniFile.SetAttributeValue("Action", "addLine"); - break; - case WindowsInstallerConstants.MsidbIniFileActionCreateLine: - xIniFile.SetAttributeValue("Action", "createLine"); - break; - case WindowsInstallerConstants.MsidbIniFileActionAddTag: - xIniFile.SetAttributeValue("Action", "addTag"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; - } - - this.AddChildToParent("Component", xIniFile, row, 7); - } - } - - /// - /// Decompile the IniLocator table. - /// - /// The table to decompile. - private void DecompileIniLocatorTable(Table table) - { - foreach (var row in table.Rows) - { - var xIniFileSearch = new XElement(Names.IniFileSearchElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Section", row.FieldAsString(2)), - new XAttribute("Key", row.FieldAsString(3)), - row.IsColumnNull(4) || row.FieldAsInteger(4) == 0 ? null : new XAttribute("Field", row.FieldAsInteger(4))); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); - if (null != names[0] && null != names[1]) - { - xIniFileSearch.SetAttributeValue("ShortName", names[0]); - xIniFileSearch.SetAttributeValue("Name", names[1]); - } - else if (null != names[0]) - { - xIniFileSearch.SetAttributeValue("Name", names[0]); - } - - if (!row.IsColumnNull(5)) - { - switch (row.FieldAsInteger(5)) - { - case WindowsInstallerConstants.MsidbLocatorTypeDirectory: - xIniFileSearch.SetAttributeValue("Type", "directory"); - break; - case WindowsInstallerConstants.MsidbLocatorTypeFileName: - // this is the default value - break; - case WindowsInstallerConstants.MsidbLocatorTypeRawValue: - xIniFileSearch.SetAttributeValue("Type", "raw"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); - break; - } - } - - this.IndexElement(row, xIniFileSearch); - } - } - - /// - /// Decompile the IsolatedComponent table. - /// - /// The table to decompile. - private void DecompileIsolatedComponentTable(Table table) - { - foreach (var row in table.Rows) - { - var xIsolateComponent = new XElement(Names.IsolateComponentElement, - new XAttribute("Shared", row.FieldAsString(0))); - - this.AddChildToParent("Component", xIsolateComponent, row, 1); - } - } - - /// - /// Decompile the LaunchCondition table. - /// - /// The table to decompile. - private void DecompileLaunchConditionTable(Table table) - { - foreach (var row in table.Rows) - { - if (WixUpgradeConstants.DowngradePreventedCondition == row.FieldAsString(0) || WixUpgradeConstants.UpgradePreventedCondition == row.FieldAsString(0)) - { - continue; // MajorUpgrade rows processed in FinalizeUpgradeTable - } - - var condition = new XElement(Names.LaunchElement, - new XAttribute("Condition", row.FieldAsString(0)), - new XAttribute("Message", row.FieldAsString(1))); - - this.RootElement.Add(condition); - } - } - - /// - /// Decompile the ListBox table. - /// - /// The table to decompile. - private void DecompileListBoxTable(Table table) - { - // sort the list boxes by their property and order - var listBoxRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); - - XElement xListBox = null; - foreach (Row row in listBoxRows) - { - if (null == xListBox || row.FieldAsString(0) != xListBox.Attribute("Property")?.Value) - { - xListBox = new XElement(Names.ListBoxElement, - new XAttribute("Property", row.FieldAsString(0))); - - this.UIElement.Add(xListBox); - } - - var listItem = new XElement(Names.ListItemElement, - new XAttribute("Value", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); - - xListBox.Add(listItem); - } - } - - /// - /// Decompile the ListView table. - /// - /// The table to decompile. - private void DecompileListViewTable(Table table) - { - // sort the list views by their property and order - var listViewRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); - - XElement xListView = null; - foreach (var row in listViewRows) - { - if (null == xListView || row.FieldAsString(0) != xListView.Attribute("Property")?.Value) - { - xListView = new XElement(Names.ListViewElement, - new XAttribute("Property", row.FieldAsString(0))); - - this.UIElement.Add(xListView); - } - - var listItem = new XElement(Names.ListItemElement, - new XAttribute("Value", row.FieldAsString(2)), - row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)), - row.IsColumnNull(4) ? null : new XAttribute("Icon", row.FieldAsString(4))); - - xListView.Add(listItem); - } - } - - /// - /// Decompile the LockPermissions table. - /// - /// The table to decompile. - private void DecompileLockPermissionsTable(Table table) - { - foreach (var row in table.Rows) - { - var xPermission = new XElement(Names.PermissionElement, - row.IsColumnNull(2) ? null : new XAttribute("Domain", row.FieldAsString(2)), - new XAttribute("User", row.FieldAsString(3))); - - string[] specialPermissions; - - switch (row.FieldAsString(1)) - { - case "CreateFolder": - specialPermissions = LockPermissionConstants.FolderPermissions; - break; - case "File": - specialPermissions = LockPermissionConstants.FilePermissions; - break; - case "Registry": - specialPermissions = LockPermissionConstants.RegistryPermissions; - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); - return; - } - - var permissionBits = row.FieldAsInteger(4); - for (var i = 0; i < 32; i++) - { - if (0 != ((permissionBits >> i) & 1)) - { - string name = null; - - if (specialPermissions.Length > i) - { - name = specialPermissions[i]; - } - else if (16 > i && specialPermissions.Length <= i) - { - name = "SpecificRightsAll"; - } - else if (28 > i && LockPermissionConstants.StandardPermissions.Length > (i - 16)) - { - name = LockPermissionConstants.StandardPermissions[i - 16]; - } - else if (0 <= (i - 28) && LockPermissionConstants.GenericPermissions.Length > (i - 28)) - { - name = LockPermissionConstants.GenericPermissions[i - 28]; - } - - if (null == name) - { - this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i)); - } - else - { - switch (name) - { - case "Append": - case "ChangePermission": - case "CreateChild": - case "CreateFile": - case "CreateLink": - case "CreateSubkeys": - case "Delete": - case "DeleteChild": - case "EnumerateSubkeys": - case "Execute": - case "FileAllRights": - case "GenericAll": - case "GenericExecute": - case "GenericRead": - case "GenericWrite": - case "Notify": - case "Read": - case "ReadAttributes": - case "ReadExtendedAttributes": - case "ReadPermission": - case "SpecificRightsAll": - case "Synchronize": - case "TakeOwnership": - case "Traverse": - case "Write": - case "WriteAttributes": - case "WriteExtendedAttributes": - xPermission.SetAttributeValue(name, "yes"); - break; - default: - throw new InvalidOperationException($"Unknown permission attribute '{name}'."); - } - } - } - } - - this.IndexElement(row, xPermission); - } - } - - /// - /// Decompile the Media table. - /// - /// The table to decompile. - private void DecompileMediaTable(Table table) - { - foreach (MediaRow mediaRow in table.Rows) - { - var xMedia = new XElement(Names.MediaElement, - new XAttribute("Id", mediaRow.DiskId), - mediaRow.DiskPrompt == null ? null : new XAttribute("DiskPrompt", mediaRow.DiskPrompt), - mediaRow.VolumeLabel == null ? null : new XAttribute("VolumeLabel", mediaRow.VolumeLabel)); - - if (null != mediaRow.Cabinet) - { - var cabinet = mediaRow.Cabinet; - - if (cabinet.StartsWith("#", StringComparison.Ordinal)) - { - xMedia.SetAttributeValue("EmbedCab", "yes"); - cabinet = cabinet.Substring(1); - } - - xMedia.SetAttributeValue("Cabinet", cabinet); - } - - this.RootElement.Add(xMedia); - this.IndexElement(mediaRow, xMedia); - } - } - - /// - /// Decompile the MIME table. - /// - /// The table to decompile. - private void DecompileMIMETable(Table table) - { - foreach (var row in table.Rows) - { - var mime = new XElement(Names.MIMEElement, - new XAttribute("ContentType", row.FieldAsString(0)), - row.IsColumnNull(2) ? null : new XAttribute("Class", row.FieldAsString(2))); - - this.IndexElement(row, mime); - } - } - - /// - /// Decompile the ModuleConfiguration table. - /// - /// The table to decompile. - private void DecompileModuleConfigurationTable(Table table) - { - foreach (var row in table.Rows) - { - var configuration = new XElement(Names.ConfigurationElement, - new XAttribute("Name", row.FieldAsString(0)), - XAttributeIfNotNull("Type", row, 2), - XAttributeIfNotNull("ContextData", row, 3), - XAttributeIfNotNull("DefaultValue", row, 4), - XAttributeIfNotNull("DisplayName", row, 6), - XAttributeIfNotNull("Description", row, 7), - XAttributeIfNotNull("HelpLocation", row, 8), - XAttributeIfNotNull("HelpKeyword", row, 9)); - - switch (row.FieldAsInteger(1)) - { - case 0: - configuration.SetAttributeValue("Format", "Text"); - break; - case 1: - configuration.SetAttributeValue("Format", "Key"); - break; - case 2: - configuration.SetAttributeValue("Format", "Integer"); - break; - case 3: - configuration.SetAttributeValue("Format", "Bitfield"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - if (!row.IsColumnNull(5)) - { - var attributes = row.FieldAsInteger(5); - - if (WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan)) - { - configuration.SetAttributeValue("KeyNoOrphan", "yes"); - } - - if (WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable)) - { - configuration.SetAttributeValue("NonNullable", "yes"); - } - - if (3 < attributes) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); - } - } - - this.RootElement.Add(configuration); - } - } - - /// - /// Decompile the ModuleDependency table. - /// - /// The table to decompile. - private void DecompileModuleDependencyTable(Table table) - { - foreach (var row in table.Rows) - { - var xDependency = new XElement(Names.DependencyElement, - new XAttribute("RequiredId", row.FieldAsString(2)), - new XAttribute("RequiredLanguage", row.FieldAsString(3)), - XAttributeIfNotNull("RequiredVersion", row, 4)); - - this.RootElement.Add(xDependency); - } - } - - /// - /// Decompile the ModuleExclusion table. - /// - /// The table to decompile. - private void DecompileModuleExclusionTable(Table table) - { - foreach (var row in table.Rows) - { - var xExclusion = new XElement(Names.ExclusionElement, - new XAttribute("ExcludedId", row.FieldAsString(2)), - XAttributeIfNotNull("ExcludedMinVersion", row, 4), - XAttributeIfNotNull("ExcludedMaxVersion", row, 5)); - - var excludedLanguage = row.FieldAsInteger(3); - if (0 < excludedLanguage) - { - xExclusion.SetAttributeValue("ExcludeLanguage", excludedLanguage); - } - else if (0 > excludedLanguage) - { - xExclusion.SetAttributeValue("ExcludeExceptLanguage", -excludedLanguage); - } - - this.RootElement.Add(xExclusion); - } - } - - /// - /// Decompile the ModuleIgnoreTable table. - /// - /// The table to decompile. - private void DecompileModuleIgnoreTableTable(Table table) - { - foreach (var row in table.Rows) - { - var tableName = row.FieldAsString(0); - - // the linker automatically adds a ModuleIgnoreTable row for some tables - if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName) - { - var xIgnoreTable = new XElement(Names.IgnoreTableElement, - new XAttribute("Id", tableName)); - - this.RootElement.Add(xIgnoreTable); - } - } - } - - /// - /// Decompile the ModuleSignature table. - /// - /// The table to decompile. - private void DecompileModuleSignatureTable(Table table) - { - if (1 == table.Rows.Count) - { - var row = table.Rows[0]; - - this.RootElement.SetAttributeValue("Id", row.FieldAsString(0)); - // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability) - this.RootElement.SetAttributeValue("Language", row.FieldAsString(1)); - this.RootElement.SetAttributeValue("Version", row.FieldAsString(2)); - } - else - { - // TODO: warn - } - } - - /// - /// Decompile the ModuleSubstitution table. - /// - /// The table to decompile. - private void DecompileModuleSubstitutionTable(Table table) - { - foreach (var row in table.Rows) - { - var xSubstitution = new XElement(Names.SubstitutionElement, - new XAttribute("Table", row.FieldAsString(0)), - new XAttribute("Row", row.FieldAsString(1)), - new XAttribute("Column", row.FieldAsString(2)), - XAttributeIfNotNull("Value", row, 3)); - - this.RootElement.Add(xSubstitution); - } - } - - /// - /// Decompile the MoveFile table. - /// - /// The table to decompile. - private void DecompileMoveFileTable(Table table) - { - foreach (var row in table.Rows) - { - var xCopyFile = new XElement(Names.CopyFileElement, - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("SourceName", row, 2)); - - if (!row.IsColumnNull(3)) - { - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3)); - if (null != names[0] && null != names[1]) - { - xCopyFile.SetAttributeValue("DestinationShortName", names[0]); - xCopyFile.SetAttributeValue("DestinationName", names[1]); - } - else if (null != names[0]) - { - xCopyFile.SetAttributeValue("DestinationName", names[0]); - } - } - - // source/destination directory/property is set in FinalizeDuplicateMoveFileTables - - switch (row.FieldAsInteger(6)) - { - case 0: - break; - case WindowsInstallerConstants.MsidbMoveFileOptionsMove: - xCopyFile.SetAttributeValue("Delete", "yes"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; - } - - this.AddChildToParent("Component", xCopyFile, row, 1); - this.IndexElement(row, xCopyFile); - } - } - - /// - /// Decompile the MsiDigitalCertificate table. - /// - /// The table to decompile. - private void DecompileMsiDigitalCertificateTable(Table table) - { - foreach (var row in table.Rows) - { - var xDigitalCertificate = new XElement(Names.DigitalCertificateElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1))); - - this.IndexElement(row, xDigitalCertificate); - } - } - - /// - /// Decompile the MsiDigitalSignature table. - /// - /// The table to decompile. - private void DecompileMsiDigitalSignatureTable(Table table) - { - foreach (var row in table.Rows) - { - var xDigitalSignature = new XElement(Names.DigitalSignatureElement, - XAttributeIfNotNull("SourceFile", row, 3)); - - this.AddChildToParent("MsiDigitalCertificate", xDigitalSignature, row, 2); - - if (this.TryGetIndexedElement(row.FieldAsString(0), out var xParentElement, row.FieldAsString(1))) - { - xParentElement.Add(xDigitalSignature); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", row.FieldAsString(1), row.FieldAsString(0))); - } - } - } - - /// - /// Decompile the MsiEmbeddedChainer table. - /// - /// The table to decompile. - private void DecompileMsiEmbeddedChainerTable(Table table) - { - foreach (var row in table.Rows) - { - var xEmbeddedChainer = new XElement(Names.EmbeddedChainerElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Condition", row.FieldAsString(1)), - XAttributeIfNotNull("CommandLine", row, 2)); - - switch (row.FieldAsInteger(4)) - { - case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: - xEmbeddedChainer.SetAttributeValue("BinarySource", row.FieldAsString(3)); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: - xEmbeddedChainer.SetAttributeValue("FileSource", row.FieldAsString(3)); - break; - case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeProperty: - xEmbeddedChainer.SetAttributeValue("PropertySource", row.FieldAsString(3)); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - this.RootElement.Add(xEmbeddedChainer); - } - } - - /// - /// Decompile the MsiEmbeddedUI table. - /// - /// The table to decompile. - private void DecompileMsiEmbeddedUITable(Table table) - { - var xEmbeddedUI = new XElement(Names.EmbeddedUIElement); - - var foundEmbeddedUI = false; - var foundEmbeddedResources = false; - - foreach (var row in table.Rows) - { - var attributes = row.FieldAsInteger(2); - - if (WindowsInstallerConstants.MsidbEmbeddedUI == (attributes & WindowsInstallerConstants.MsidbEmbeddedUI)) - { - if (foundEmbeddedUI) - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); - } - else - { - xEmbeddedUI.SetAttributeValue("Id", row.FieldAsString(0)); - xEmbeddedUI.SetAttributeValue("Name", row.FieldAsString(1)); - - var messageFilter = row.FieldAsInteger(3); - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT)) - { - xEmbeddedUI.SetAttributeValue("IgnoreFatalExit", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ERROR)) - { - xEmbeddedUI.SetAttributeValue("IgnoreError", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_WARNING)) - { - xEmbeddedUI.SetAttributeValue("IgnoreWarning", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_USER)) - { - xEmbeddedUI.SetAttributeValue("IgnoreUser", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INFO)) - { - xEmbeddedUI.SetAttributeValue("IgnoreInfo", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreFilesInUse", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreResolveSource", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreOutOfDiskSpace", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART)) - { - xEmbeddedUI.SetAttributeValue("IgnoreActionStart", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA)) - { - xEmbeddedUI.SetAttributeValue("IgnoreActionData", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS)) - { - xEmbeddedUI.SetAttributeValue("IgnoreProgress", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA)) - { - xEmbeddedUI.SetAttributeValue("IgnoreCommonData", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreInitialize", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreTerminate", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG)) - { - xEmbeddedUI.SetAttributeValue("IgnoreShowDialog", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE)) - { - xEmbeddedUI.SetAttributeValue("IgnoreRMFilesInUse", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART)) - { - xEmbeddedUI.SetAttributeValue("IgnoreInstallStart", "yes"); - } - - if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND)) - { - xEmbeddedUI.SetAttributeValue("IgnoreInstallEnd", "yes"); - } - - if (WindowsInstallerConstants.MsidbEmbeddedHandlesBasic == (attributes & WindowsInstallerConstants.MsidbEmbeddedHandlesBasic)) - { - xEmbeddedUI.SetAttributeValue("SupportBasicUI", "yes"); - } - - xEmbeddedUI.SetAttributeValue("SourceFile", row.FieldAsString(4)); - - this.UIElement.Add(xEmbeddedUI); - foundEmbeddedUI = true; - } - } - else - { - var xEmbeddedResource = new XElement(Names.EmbeddedUIResourceElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(1)), - new XAttribute("SourceFile", row.FieldAsString(4))); - - xEmbeddedUI.Add(xEmbeddedResource); - foundEmbeddedResources = true; - } - } - - if (!foundEmbeddedUI && foundEmbeddedResources) - { - // TODO: warn - } - } - - /// - /// Decompile the MsiLockPermissionsEx table. - /// - /// The table to decompile. - private void DecompileMsiLockPermissionsExTable(Table table) - { - foreach (var row in table.Rows) - { - var xPermissionEx = new XElement(Names.PermissionExElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Sddl", row.FieldAsString(3)), - XAttributeIfNotNull("Condition", row, 4)); - - switch (row.FieldAsString(2)) - { - case "CreateFolder": - case "File": - case "Registry": - case "ServiceInstall": - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); - return; - } - - this.IndexElement(row, xPermissionEx); - } - } - - /// - /// Decompile the MsiPackageCertificate table. - /// - /// The table to decompile. - private void DecompileMsiPackageCertificateTable(Table table) - { - if (0 < table.Rows.Count) - { - var xPackageCertificates = new XElement(Names.PatchCertificatesElement); - this.RootElement.Add(xPackageCertificates); - this.AddCertificates(table, xPackageCertificates); - } - } - - /// - /// Decompile the MsiPatchCertificate table. - /// - /// The table to decompile. - private void DecompileMsiPatchCertificateTable(Table table) - { - if (0 < table.Rows.Count) - { - var xPatchCertificates = new XElement(Names.PatchCertificatesElement); - this.RootElement.Add(xPatchCertificates); - this.AddCertificates(table, xPatchCertificates); - } - } - - /// - /// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table. - /// - /// The table being decompiled. - /// DigitalCertificate parent - private void AddCertificates(Table table, XElement parent) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("MsiDigitalCertificate", out var xDigitalCertificate, row.FieldAsString(1))) - { - parent.Add(xDigitalCertificate); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", row.FieldAsString(1), "MsiDigitalCertificate")); - } - } - } - - /// - /// Decompile the MsiShortcutProperty table. - /// - /// The table to decompile. - private void DecompileMsiShortcutPropertyTable(Table table) - { - foreach (var row in table.Rows) - { - var xProperty = new XElement(Names.ShortcutPropertyElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2)), - new XAttribute("Value", row.FieldAsString(3))); - - this.AddChildToParent("Shortcut", xProperty, row, 1); - } - } - - /// - /// Decompile the ODBCAttribute table. - /// - /// The table to decompile. - private void DecompileODBCAttributeTable(Table table) - { - foreach (var row in table.Rows) - { - var xProperty = new XElement(Names.PropertyElement, - new XAttribute("Id", row.FieldAsString(1)), - row.IsColumnNull(2) ? null : new XAttribute("Value", row.FieldAsString(2))); - - this.AddChildToParent("ODBCDriver", xProperty, row, 0); - } - } - - /// - /// Decompile the ODBCDataSource table. - /// - /// The table to decompile. - private void DecompileODBCDataSourceTable(Table table) - { - foreach (var row in table.Rows) - { - var xOdbcDataSource = new XElement(Names.ODBCDataSourceElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(2)), - new XAttribute("DriverName", row.FieldAsString(3))); - - switch (row.FieldAsInteger(4)) - { - case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerMachine: - xOdbcDataSource.SetAttributeValue("Registration", "machine"); - break; - case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerUser: - xOdbcDataSource.SetAttributeValue("Registration", "user"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - this.IndexElement(row, xOdbcDataSource); - } - } - - /// - /// Decompile the ODBCDriver table. - /// - /// The table to decompile. - private void DecompileODBCDriverTable(Table table) - { - foreach (var row in table.Rows) - { - var xOdbcDriver = new XElement(Names.ODBCDriverElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(2)), - new XAttribute("File", row.FieldAsString(3)), - XAttributeIfNotNull("SetupFile", row, 4)); - - this.AddChildToParent("Component", xOdbcDriver, row, 1); - this.IndexElement(row, xOdbcDriver); - } - } - - /// - /// Decompile the ODBCSourceAttribute table. - /// - /// The table to decompile. - private void DecompileODBCSourceAttributeTable(Table table) - { - foreach (var row in table.Rows) - { - var xProperty = new XElement(Names.PropertyElement, - new XAttribute("Id", row.FieldAsString(1)), - XAttributeIfNotNull("Value", row, 2)); - - this.AddChildToParent("ODBCDataSource", xProperty, row, 0); - } - } - - /// - /// Decompile the ODBCTranslator table. - /// - /// The table to decompile. - private void DecompileODBCTranslatorTable(Table table) - { - foreach (var row in table.Rows) - { - var xOdbcTranslator = new XElement(Names.ODBCTranslatorElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(2)), - new XAttribute("File", row.FieldAsString(3)), - XAttributeIfNotNull("SetupFile", row, 4)); - - this.AddChildToParent("Component", xOdbcTranslator, row, 1); - } - } - - /// - /// Decompile the PatchMetadata table. - /// - /// The table to decompile. - private void DecompilePatchMetadataTable(Table table) - { - if (0 < table.Rows.Count) - { - var xPatchMetadata = new XElement(Names.PatchMetadataElement); - - foreach (var row in table.Rows) - { - var value = row.FieldAsString(2); - - switch (row.FieldAsString(1)) - { - case "AllowRemoval": - if ("1" == value) - { - xPatchMetadata.SetAttributeValue("AllowRemoval", "yes"); - } - break; - case "Classification": - if (null != value) - { - xPatchMetadata.SetAttributeValue("Classification", value); - } - break; - case "CreationTimeUTC": - if (null != value) - { - xPatchMetadata.SetAttributeValue("CreationTimeUTC", value); - } - break; - case "Description": - if (null != value) - { - xPatchMetadata.SetAttributeValue("Description", value); - } - break; - case "DisplayName": - if (null != value) - { - xPatchMetadata.SetAttributeValue("DisplayName", value); - } - break; - case "ManufacturerName": - if (null != value) - { - xPatchMetadata.SetAttributeValue("ManufacturerName", value); - } - break; - case "MinorUpdateTargetRTM": - if (null != value) - { - xPatchMetadata.SetAttributeValue("MinorUpdateTargetRTM", value); - } - break; - case "MoreInfoURL": - if (null != value) - { - xPatchMetadata.SetAttributeValue("MoreInfoURL", value); - } - break; - case "OptimizeCA": - var xOptimizeCustomActions = new XElement(Names.OptimizeCustomActionsElement); - var optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture); - if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipAssignment) & optimizeCA)) - { - xOptimizeCustomActions.SetAttributeValue("SkipAssignment", "yes"); - } - - if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipImmediate) & optimizeCA)) - { - xOptimizeCustomActions.SetAttributeValue("SkipImmediate", "yes"); - } - - if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipDeferred) & optimizeCA)) - { - xOptimizeCustomActions.SetAttributeValue("SkipDeferred", "yes"); - } - - xPatchMetadata.Add(xOptimizeCustomActions); - break; - case "OptimizedInstallMode": - if ("1" == value) - { - xPatchMetadata.SetAttributeValue("OptimizedInstallMode", "yes"); - } - break; - case "TargetProductName": - if (null != value) - { - xPatchMetadata.SetAttributeValue("TargetProductName", value); - } - break; - default: - var xCustomProperty = new XElement(Names.CustomPropertyElement, - XAttributeIfNotNull("Company", row, 0), - XAttributeIfNotNull("Property", row, 1), - XAttributeIfNotNull("Value", row, 2)); - - xPatchMetadata.Add(xCustomProperty); - break; - } - } - - this.RootElement.Add(xPatchMetadata); - } - } - - /// - /// Decompile the PatchSequence table. - /// - /// The table to decompile. - private void DecompilePatchSequenceTable(Table table) - { - foreach (var row in table.Rows) - { - var patchSequence = new XElement(Names.PatchSequenceElement, - new XAttribute("PatchFamily", row.FieldAsString(0))); - - if (!row.IsColumnNull(1)) - { - try - { - var guid = new Guid(row.FieldAsString(1)); - - patchSequence.SetAttributeValue("ProductCode", row.FieldAsString(1)); - } - catch // non-guid value - { - patchSequence.SetAttributeValue("TargetImage", row.FieldAsString(1)); - } - } - - if (!row.IsColumnNull(2)) - { - patchSequence.SetAttributeValue("Sequence", row.FieldAsString(2)); - } - - if (!row.IsColumnNull(3) && 0x1 == row.FieldAsInteger(3)) - { - patchSequence.SetAttributeValue("Supersede", "yes"); - } - - this.RootElement.Add(patchSequence); - } - } - - /// - /// Decompile the ProgId table. - /// - /// The table to decompile. - private void DecompileProgIdTable(Table table) - { - foreach (var row in table.Rows) - { - var xProgId = new XElement(Names.ProgIdElement, - new XAttribute("Advertise", "yes"), - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("Description", row, 3), - XAttributeIfNotNull("Icon", row, 4), - XAttributeIfNotNull("IconIndex", row, 5)); - - this.IndexElement(row, xProgId); - } - - // nest the ProgIds - foreach (var row in table.Rows) - { - var xProgId = this.GetIndexedElement(row); - - if (!row.IsColumnNull(1)) - { - this.AddChildToParent("ProgId", xProgId, row, 1); - } - else if (!row.IsColumnNull(2)) - { - // nesting is handled in FinalizeProgIdTable - } - else - { - // TODO: warn for orphaned ProgId - } - } - } - - /// - /// Decompile the Properties table. - /// - /// The table to decompile. - private void DecompilePropertiesTable(Table table) - { - foreach (var row in table.Rows) - { - var name = row.FieldAsString(0); - var value = row.FieldAsString(1); - - switch (name) - { - case "AllowProductCodeMismatches": - if ("1" == value) - { - this.RootElement.SetAttributeValue("AllowProductCodeMismatches", "yes"); - } - break; - case "AllowProductVersionMajorMismatches": - if ("1" == value) - { - this.RootElement.SetAttributeValue("AllowMajorVersionMismatches", "yes"); - } - break; - case "ApiPatchingSymbolFlags": - if (null != value) - { - try - { - // remove the leading "0x" if its present - if (value.StartsWith("0x", StringComparison.Ordinal)) - { - value = value.Substring(2); - } - - this.RootElement.SetAttributeValue("SymbolFlags", Convert.ToInt32(value, 16)); - } - catch - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - } - } - break; - case "DontRemoveTempFolderWhenFinished": - if ("1" == value) - { - this.RootElement.SetAttributeValue("CleanWorkingFolder", "no"); - } - break; - case "IncludeWholeFilesOnly": - if ("1" == value) - { - this.RootElement.SetAttributeValue("WholeFilesOnly", "yes"); - } - break; - case "ListOfPatchGUIDsToReplace": - if (null != value) - { - var guidRegex = 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}\}"); - var guidMatches = guidRegex.Matches(value); - - foreach (Match guidMatch in guidMatches) - { - var xReplacePatch = new XElement(Names.ReplacePatchElement, - new XAttribute("Id", guidMatch.Value)); - - this.RootElement.Add(xReplacePatch); - } - } - break; - case "ListOfTargetProductCodes": - if (null != value) - { - var targetProductCodes = value.Split(';'); - - foreach (var targetProductCodeString in targetProductCodes) - { - var xTargetProductCode = new XElement(Names.TargetProductCodeElement, - new XAttribute("Id", targetProductCodeString)); - - this.RootElement.Add(xTargetProductCode); - } - } - break; - case "PatchGUID": - this.RootElement.SetAttributeValue("Id", value); - break; - case "PatchSourceList": - this.RootElement.SetAttributeValue("SourceList", value); - break; - case "PatchOutputPath": - this.RootElement.SetAttributeValue("OutputPath", value); - break; - default: - var patchProperty = new XElement(Names.PatchPropertyElement, - new XAttribute("Name", name), - new XAttribute("Value", value)); - - this.RootElement.Add(patchProperty); - break; - } - } - } - - /// - /// Decompile the Property table. - /// - /// The table to decompile. - private void DecompilePropertyTable(Table table) - { - foreach (var row in table.Rows) - { - var id = row.FieldAsString(0); - var value = row.FieldAsString(1); - - if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id) - { - if (0 < value.Length) - { - foreach (var propertyId in value.Split(';')) - { - if (WixUpgradeConstants.DowngradeDetectedProperty == propertyId || WixUpgradeConstants.UpgradeDetectedProperty == propertyId) - { - continue; - } - - var property = propertyId; - var suppressModulularization = false; - if (OutputType.Module == this.OutputType) - { - if (propertyId.EndsWith(this.ModularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal)) - { - property = propertyId.Substring(0, propertyId.Length - this.ModularizationGuid.Length + 1); - } - else - { - suppressModulularization = true; - } - } - - var xSpecialProperty = this.EnsureProperty(property); - if (suppressModulularization) - { - xSpecialProperty.SetAttributeValue("SuppressModularization", "yes"); - } - - switch (id) - { - case "AdminProperties": - xSpecialProperty.SetAttributeValue("Admin", "yes"); - break; - case "MsiHiddenProperties": - xSpecialProperty.SetAttributeValue("Hidden", "yes"); - break; - case "SecureCustomProperties": - xSpecialProperty.SetAttributeValue("Secure", "yes"); - break; - } - } - } - - continue; - } - else if (OutputType.Product == this.OutputType) - { - switch (id) - { - case "Manufacturer": - this.RootElement.SetAttributeValue("Manufacturer", value); - continue; - case "ProductCode": - this.RootElement.SetAttributeValue("ProductCode", value.ToUpper(CultureInfo.InvariantCulture)); - continue; - case "ProductLanguage": - this.RootElement.SetAttributeValue("Language", value); - continue; - case "ProductName": - this.RootElement.SetAttributeValue("Name", value); - continue; - case "ProductVersion": - this.RootElement.SetAttributeValue("Version", value); - continue; - case "UpgradeCode": - this.RootElement.SetAttributeValue("UpgradeCode", value); - continue; - } - } - - if (!this.SuppressUI || "ErrorDialog" != id) - { - var xProperty = this.EnsureProperty(id); - - xProperty.SetAttributeValue("Value", value); - } - } - } - - /// - /// Decompile the PublishComponent table. - /// - /// The table to decompile. - private void DecompilePublishComponentTable(Table table) - { - foreach (var row in table.Rows) - { - var category = new XElement(Names.CategoryElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Qualifier", row.FieldAsString(1)), - XAttributeIfNotNull("AppData", row, 3)); - - this.AddChildToParent("Component", category, row, 2); - } - } - - /// - /// Decompile the RadioButton table. - /// - /// The table to decompile. - private void DecompileRadioButtonTable(Table table) - { - foreach (var row in table.Rows) - { - var radioButton = new XElement(Names.RadioButtonElement, - new XAttribute("Value", row.FieldAsString(2)), - new XAttribute("X", row.FieldAsInteger(3)), - new XAttribute("Y", row.FieldAsInteger(4)), - new XAttribute("Width", row.FieldAsInteger(5)), - new XAttribute("Height", row.FieldAsInteger(6)), - XAttributeIfNotNull("Text", row, 7)); - - if (!row.IsColumnNull(8)) - { - var help = (row.FieldAsString(8)).Split('|'); - - if (2 == help.Length) - { - if (0 < help[0].Length) - { - radioButton.SetAttributeValue("ToolTip", help[0]); - } - - if (0 < help[1].Length) - { - radioButton.SetAttributeValue("Help", help[1]); - } - } - } - - this.IndexElement(row, radioButton); - } - - // nest the radio buttons - var xRadioButtonGroups = new Dictionary(); - foreach (var row in table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1))) - { - var xRadioButton = this.GetIndexedElement(row); - - if (!xRadioButtonGroups.TryGetValue(row.FieldAsString(0), out var xRadioButtonGroup)) - { - xRadioButtonGroup = new XElement(Names.RadioButtonGroupElement, - new XAttribute("Property", row.FieldAsString(0))); - - this.UIElement.Add(xRadioButtonGroup); - xRadioButtonGroups.Add(row.FieldAsString(0), xRadioButtonGroup); - } - - xRadioButtonGroup.Add(xRadioButton); - } - } - - /// - /// Decompile the Registry table. - /// - /// The table to decompile. - private void DecompileRegistryTable(Table table) - { - foreach (var row in table.Rows) - { - if (("-" == row.FieldAsString(3) || "+" == row.FieldAsString(3) || "*" == row.FieldAsString(3)) && row.IsColumnNull(4)) - { - var xRegistryKey = new XElement(Names.RegistryKeyElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2))); - - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) - { - xRegistryKey.SetAttributeValue("Root", registryRootType); - } - - switch (row.FieldAsString(3)) - { - case "+": - xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); - break; - case "-": - xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); - break; - case "*": - xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); - xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); - break; - } - - this.IndexElement(row, xRegistryKey); - } - else - { - var xRegistryValue = new XElement(Names.RegistryValueElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2)), - XAttributeIfNotNull("Name", row, 3)); - - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) - { - xRegistryValue.SetAttributeValue("Root", registryRootType); - } - - if (!row.IsColumnNull(4)) - { - var value = row.FieldAsString(4); - - if (value.StartsWith("#x", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Type", "binary"); - xRegistryValue.SetAttributeValue("Value", value.Substring(2)); - } - else if (value.StartsWith("#%", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Type", "expandable"); - xRegistryValue.SetAttributeValue("Value", value.Substring(2)); - } - else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Type", "integer"); - xRegistryValue.SetAttributeValue("Value", value.Substring(1)); - } - else - { - if (value.StartsWith("##", StringComparison.Ordinal)) - { - value = value.Substring(1); - } - - if (0 <= value.IndexOf("[~]", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Type", "multiString"); - - if ("[~]" == value) - { - value = String.Empty; - } - else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal)) - { - value = value.Substring(3, value.Length - 6); - } - else if (value.StartsWith("[~]", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Action", "append"); - value = value.Substring(3); - } - else if (value.EndsWith("[~]", StringComparison.Ordinal)) - { - xRegistryValue.SetAttributeValue("Action", "prepend"); - value = value.Substring(0, value.Length - 3); - } - - var multiValues = NullSplitter.Split(value); - foreach (var multiValue in multiValues) - { - var xMultiStringValue = new XElement(Names.MultiStringElement, - new XAttribute("Value", multiValue)); - - xRegistryValue.Add(xMultiStringValue); - } - } - else - { - xRegistryValue.SetAttributeValue("Type", "string"); - xRegistryValue.SetAttributeValue("Value", value); - } - } - } - else - { - xRegistryValue.SetAttributeValue("Type", "string"); - xRegistryValue.SetAttributeValue("Value", String.Empty); - } - - this.IndexElement(row, xRegistryValue); - } - } - } - - /// - /// Decompile the RegLocator table. - /// - /// The table to decompile. - private void DecompileRegLocatorTable(Table table) - { - foreach (var row in table.Rows) - { - var xRegistrySearch = new XElement(Names.RegistrySearchElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2)), - XAttributeIfNotNull("Name", row, 3)); - - switch (row.FieldAsInteger(1)) - { - case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: - xRegistrySearch.SetAttributeValue("Root", "HKCR"); - break; - case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: - xRegistrySearch.SetAttributeValue("Root", "HKCU"); - break; - case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: - xRegistrySearch.SetAttributeValue("Root", "HKLM"); - break; - case WindowsInstallerConstants.MsidbRegistryRootUsers: - xRegistrySearch.SetAttributeValue("Root", "HKU"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); - break; - } - - if (row.IsColumnNull(4)) - { - xRegistrySearch.SetAttributeValue("Type", "file"); - } - else - { - var type = row.FieldAsInteger(4); - - if (WindowsInstallerConstants.MsidbLocatorType64bit == (type & WindowsInstallerConstants.MsidbLocatorType64bit)) - { - xRegistrySearch.SetAttributeValue("Bitness", "always64"); - type &= ~WindowsInstallerConstants.MsidbLocatorType64bit; - } - else - { - xRegistrySearch.SetAttributeValue("Bitness", "always32"); - } - - switch (type) - { - case WindowsInstallerConstants.MsidbLocatorTypeDirectory: - xRegistrySearch.SetAttributeValue("Type", "directory"); - break; - case WindowsInstallerConstants.MsidbLocatorTypeFileName: - xRegistrySearch.SetAttributeValue("Type", "file"); - break; - case WindowsInstallerConstants.MsidbLocatorTypeRawValue: - xRegistrySearch.SetAttributeValue("Type", "raw"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - } - - this.IndexElement(row, xRegistrySearch); - } - } - - /// - /// Decompile the RemoveFile table. - /// - /// The table to decompile. - private void DecompileRemoveFileTable(Table table) - { - foreach (var row in table.Rows) - { - if (row.IsColumnNull(2)) - { - var xRemoveFolder = new XElement(Names.RemoveFolderElement, - new XAttribute("Id", row.FieldAsString(0))); - - // directory/property is set in FinalizeDecompile - - switch (row.FieldAsInteger(4)) - { - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: - xRemoveFolder.SetAttributeValue("On", "install"); - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: - xRemoveFolder.SetAttributeValue("On", "uninstall"); - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: - xRemoveFolder.SetAttributeValue("On", "both"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - this.AddChildToParent("Component", xRemoveFolder, row, 1); - this.IndexElement(row, xRemoveFolder); - } - else - { - var xRemoveFile = new XElement(Names.RemoveFileElement, - new XAttribute("Id", row.FieldAsString(0))); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); - if (null != names[0] && null != names[1]) - { - xRemoveFile.SetAttributeValue("ShortName", names[0]); - xRemoveFile.SetAttributeValue("Name", names[1]); - } - else if (null != names[0]) - { - xRemoveFile.SetAttributeValue("Name", names[0]); - } - - // directory/property is set in FinalizeDecompile - - switch (row.FieldAsInteger(4)) - { - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: - xRemoveFile.SetAttributeValue("On", "install"); - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: - xRemoveFile.SetAttributeValue("On", "uninstall"); - break; - case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: - xRemoveFile.SetAttributeValue("On", "both"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - break; - } - - this.AddChildToParent("Component", xRemoveFile, row, 1); - this.IndexElement(row, xRemoveFile); - } - } - } - - /// - /// Decompile the RemoveIniFile table. - /// - /// The table to decompile. - private void DecompileRemoveIniFileTable(Table table) - { - foreach (var row in table.Rows) - { - var xIniFile = new XElement(Names.IniFileElement, - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("Directory", row, 2), - new XAttribute("Section", row.FieldAsString(3)), - new XAttribute("Key", row.FieldAsString(4)), - XAttributeIfNotNull("Value", row, 5)); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); - if (null != names[0] && null != names[1]) - { - xIniFile.SetAttributeValue("ShortName", names[0]); - xIniFile.SetAttributeValue("Name", names[1]); - } - else if (null != names[0]) - { - xIniFile.SetAttributeValue("Name", names[0]); - } - - switch (row.FieldAsInteger(6)) - { - case WindowsInstallerConstants.MsidbIniFileActionRemoveLine: - xIniFile.SetAttributeValue("Action", "removeLine"); - break; - case WindowsInstallerConstants.MsidbIniFileActionRemoveTag: - xIniFile.SetAttributeValue("Action", "removeTag"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); - break; - } - - this.AddChildToParent("Component", xIniFile, row, 7); - } - } - - /// - /// Decompile the RemoveRegistry table. - /// - /// The table to decompile. - private void DecompileRemoveRegistryTable(Table table) - { - foreach (var row in table.Rows) - { - if ("-" == row.FieldAsString(3)) - { - var xRemoveRegistryKey = new XElement(Names.RemoveRegistryKeyElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2)), - new XAttribute("Action", "removeOnInstall")); - - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) - { - xRemoveRegistryKey.SetAttributeValue("Root", registryRootType); - } - - this.AddChildToParent("Component", xRemoveRegistryKey, row, 4); - } - else - { - var xRemoveRegistryValue = new XElement(Names.RemoveRegistryValueElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Key", row.FieldAsString(2)), - XAttributeIfNotNull("Name", row, 3)); - - if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) - { - xRemoveRegistryValue.SetAttributeValue("Root", registryRootType); - } - - this.AddChildToParent("Component", xRemoveRegistryValue, row, 4); - } - } - } - - /// - /// Decompile the ReserveCost table. - /// - /// The table to decompile. - private void DecompileReserveCostTable(Table table) - { - foreach (var row in table.Rows) - { - var xReserveCost = new XElement(Names.ReserveCostElement, - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("Directory", row, 2), - new XAttribute("RunLocal", row.FieldAsString(3)), - new XAttribute("RunFromSource", row.FieldAsString(4))); - - this.AddChildToParent("Component", xReserveCost, row, 4); - } - } - - /// - /// Decompile the SelfReg table. - /// - /// The table to decompile. - private void DecompileSelfRegTable(Table table) - { - foreach (var row in table.Rows) - { - if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) - { - xFile.SetAttributeValue("SelfRegCost", row.IsColumnNull(1) ? 0 : row.FieldAsInteger(1)); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); - } - } - } - - /// - /// Decompile the ServiceControl table. - /// - /// The table to decompile. - private void DecompileServiceControlTable(Table table) - { - foreach (var row in table.Rows) - { - var xServiceControl = new XElement(Names.ServiceControlElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(1))); - - var eventValue = row.FieldAsInteger(2); - if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart) && - WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) - { - xServiceControl.SetAttributeValue("Start", "both"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart)) - { - xServiceControl.SetAttributeValue("Start", "install"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) - { - xServiceControl.SetAttributeValue("Start", "uninstall"); - } - - if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop) && - WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) - { - xServiceControl.SetAttributeValue("Stop", "both"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop)) - { - xServiceControl.SetAttributeValue("Stop", "install"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) - { - xServiceControl.SetAttributeValue("Stop", "uninstall"); - } - - if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete) && - WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) - { - xServiceControl.SetAttributeValue("Remove", "both"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete)) - { - xServiceControl.SetAttributeValue("Remove", "install"); - } - else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) - { - xServiceControl.SetAttributeValue("Remove", "uninstall"); - } - - if (!row.IsColumnNull(3)) - { - var arguments = NullSplitter.Split(row.FieldAsString(3)); - - foreach (var argument in arguments) - { - var xServiceArgument = new XElement(Names.ServiceArgumentElement, - new XAttribute("Value", argument)); - - xServiceControl.Add(xServiceArgument); - } - } - - if (!row.IsColumnNull(4)) - { - xServiceControl.SetAttributeValue("Wait", row.FieldAsInteger(4) == 0 ? "no" : "yes"); - } - - this.AddChildToParent("Component", xServiceControl, row, 5); - } - } - - /// - /// Decompile the ServiceInstall table. - /// - /// The table to decompile. - private void DecompileServiceInstallTable(Table table) - { - foreach (var row in table.Rows) - { - var xServiceInstall = new XElement(Names.ServiceInstallElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Name", row.FieldAsString(1)), - XAttributeIfNotNull("DisplayName", row, 2), - XAttributeIfNotNull("LoadOrderGroup", row, 6), - XAttributeIfNotNull("Account", row, 8), - XAttributeIfNotNull("Password", row, 9), - XAttributeIfNotNull("Arguments", row, 10), - XAttributeIfNotNull("Description", row, 12)); - - var serviceType = row.FieldAsInteger(3); - if (WindowsInstallerConstants.MsidbServiceInstallInteractive == (serviceType & WindowsInstallerConstants.MsidbServiceInstallInteractive)) - { - xServiceInstall.SetAttributeValue("Interactive", "yes"); - } - - if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess) && - WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) - { - // TODO: warn - } - else if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess)) - { - xServiceInstall.SetAttributeValue("Type", "ownProcess"); - } - else if (WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) - { - xServiceInstall.SetAttributeValue("Type", "shareProcess"); - } - - var startType = row.FieldAsInteger(4); - if (WindowsInstallerConstants.MsidbServiceInstallDisabled == startType) - { - xServiceInstall.SetAttributeValue("Start", "disabled"); - } - else if (WindowsInstallerConstants.MsidbServiceInstallDemandStart == startType) - { - xServiceInstall.SetAttributeValue("Start", "demand"); - } - else if (WindowsInstallerConstants.MsidbServiceInstallAutoStart == startType) - { - xServiceInstall.SetAttributeValue("Start", "auto"); - } - else - { - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); - } - - var errorControl = row.FieldAsInteger(5); - if (WindowsInstallerConstants.MsidbServiceInstallErrorCritical == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorCritical)) - { - xServiceInstall.SetAttributeValue("ErrorControl", "critical"); - } - else if (WindowsInstallerConstants.MsidbServiceInstallErrorNormal == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorNormal)) - { - xServiceInstall.SetAttributeValue("ErrorControl", "normal"); - } - else - { - xServiceInstall.SetAttributeValue("ErrorControl", "ignore"); - } - - if (WindowsInstallerConstants.MsidbServiceInstallErrorControlVital == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorControlVital)) - { - xServiceInstall.SetAttributeValue("Vital", "yes"); - } - - if (!row.IsColumnNull(7)) - { - var dependencies = NullSplitter.Split(row.FieldAsString(7)); - - foreach (var dependency in dependencies) - { - if (0 < dependency.Length) - { - var xServiceDependency = new XElement(Names.ServiceDependencyElement); - - if (dependency.StartsWith("+", StringComparison.Ordinal)) - { - xServiceDependency.SetAttributeValue("Group", "yes"); - xServiceDependency.SetAttributeValue("Id", dependency.Substring(1)); - } - else - { - xServiceDependency.SetAttributeValue("Id", dependency); - } - - xServiceInstall.Add(xServiceDependency); - } - } - } - - this.AddChildToParent("Component", xServiceInstall, row, 11); - this.IndexElement(row, xServiceInstall); - } - } - - /// - /// Decompile the SFPCatalog table. - /// - /// The table to decompile. - private void DecompileSFPCatalogTable(Table table) - { - foreach (var row in table.Rows) - { - var xSfpCatalog = new XElement(Names.SFPCatalogElement, - new XAttribute("Name", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1))); - - this.IndexElement(row, xSfpCatalog); - } - - // nest the SFPCatalog elements - foreach (var row in table.Rows) - { - var xSfpCatalog = this.GetIndexedElement(row); - - if (!row.IsColumnNull(2)) - { - if (this.TryGetIndexedElement("SFPCatalog", out var xParentSFPCatalog, row.FieldAsString(2))) - { - xParentSFPCatalog.Add(xSfpCatalog); - } - else - { - xSfpCatalog.SetAttributeValue("Dependency", row.FieldAsString(2)); - - this.RootElement.Add(xSfpCatalog); - } - } - else - { - this.RootElement.Add(xSfpCatalog); - } - } - } - - /// - /// Decompile the Shortcut table. - /// - /// The table to decompile. - private void DecompileShortcutTable(Table table) - { - foreach (var row in table.Rows) - { - var xShortcut = new XElement(Names.ShortcutElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Directory", row.FieldAsString(1)), - XAttributeIfNotNull("Arguments", row, 5), - XAttributeIfNotNull("Description", row, 6), - XAttributeIfNotNull("Hotkey", row, 7), - XAttributeIfNotNull("Icon", row, 8), - XAttributeIfNotNull("IconIndex", row, 9), - XAttributeIfNotNull("WorkingDirectory", row, 11)); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); - if (null != names[0] && null != names[1]) - { - xShortcut.SetAttributeValue("ShortName", names[0]); - xShortcut.SetAttributeValue("Name", names[1]); - } - else if (null != names[0]) - { - xShortcut.SetAttributeValue("Name", names[0]); - } - - if (!row.IsColumnNull(10)) - { - switch (row.FieldAsInteger(10)) - { - case 1: - xShortcut.SetAttributeValue("Show", "normal"); - break; - case 3: - xShortcut.SetAttributeValue("Show", "maximized"); - break; - case 7: - xShortcut.SetAttributeValue("Show", "minimized"); - break; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10])); - break; - } - } - - // Only try to read the MSI 4.0-specific columns if they actually exist - if (15 < row.Fields.Length) - { - if (!row.IsColumnNull(12)) - { - xShortcut.SetAttributeValue("DisplayResourceDll", row.FieldAsString(12)); - } - - if (null != row[13]) - { - xShortcut.SetAttributeValue("DisplayResourceId", row.FieldAsInteger(13)); - } - - if (null != row[14]) - { - xShortcut.SetAttributeValue("DescriptionResourceDll", row.FieldAsString(14)); - } - - if (null != row[15]) - { - xShortcut.SetAttributeValue("DescriptionResourceId", row.FieldAsInteger(15)); - } - } - - this.AddChildToParent("Component", xShortcut, row, 3); - this.IndexElement(row, xShortcut); - } - } - - /// - /// Decompile the Signature table. - /// - /// The table to decompile. - private void DecompileSignatureTable(Table table) - { - foreach (var row in table.Rows) - { - var fileSearch = new XElement(Names.FileSearchElement, - new XAttribute("Id", row.FieldAsString(0)), - XAttributeIfNotNull("MinVersion", row, 2), - XAttributeIfNotNull("MaxVersion", row, 3), - XAttributeIfNotNull("MinSize", row, 4), - XAttributeIfNotNull("MaxSize", row, 5), - XAttributeIfNotNull("Languages", row, 8)); - - var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); - if (null != names[0]) - { - // it is permissable to just have a long name - if (!this.BackendHelper.IsValidShortFilename(names[0], false) && null == names[1]) - { - fileSearch.SetAttributeValue("Name", names[0]); - } - else - { - fileSearch.SetAttributeValue("ShortName", names[0]); - } - } - - if (null != names[1]) - { - fileSearch.SetAttributeValue("Name", names[1]); - } - - if (!row.IsColumnNull(6)) - { - fileSearch.SetAttributeValue("MinDate", ConvertIntegerToDateTime(row.FieldAsInteger(6))); - } - - if (!row.IsColumnNull(7)) - { - fileSearch.SetAttributeValue("MaxDate", ConvertIntegerToDateTime(row.FieldAsInteger(7))); - } - - this.IndexElement(row, fileSearch); - } - } - - /// - /// Decompile the TargetFiles_OptionalData table. - /// - /// The table to decompile. - private void DecompileTargetFiles_OptionalDataTable(Table table) - { - foreach (var row in table.Rows) - { - if (!this.PatchTargetFiles.TryGetValue(row.FieldAsString(0), out var xPatchTargetFile)) - { - xPatchTargetFile = new XElement(Names.TargetFileElement, - new XAttribute("Id", row.FieldAsString(1))); - - if (this.TryGetIndexedElement("TargetImages", out var xTargetImage, row.FieldAsString(0))) - { - xTargetImage.Add(xPatchTargetFile); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); - } - - this.PatchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), xPatchTargetFile); - } - - AddSymbolPaths(row, 2, xPatchTargetFile); - - if (!row.IsColumnNull(3) && !row.IsColumnNull(4)) - { - var ignoreOffsets = row.FieldAsString(3).Split(','); - var ignoreLengths = row.FieldAsString(4).Split(','); - - if (ignoreOffsets.Length == ignoreLengths.Length) - { - for (var i = 0; i < ignoreOffsets.Length; i++) - { - var xIgnoreRange = new XElement(Names.IgnoreRangeElement); - - if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) - { - xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); - } - else - { - xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); - } - - if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) - { - xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); - } - else - { - xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); - } - - xPatchTargetFile.Add(xIgnoreRange); - } - } - else - { - // TODO: warn - } - } - else if (!row.IsColumnNull(3) || !row.IsColumnNull(4)) - { - // TODO: warn about mismatch between columns - } - - // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable - } - } - - /// - /// Decompile the TargetImages table. - /// - /// The table to decompile. - private void DecompileTargetImagesTable(Table table) - { - foreach (var row in table.Rows) - { - var xTargetImage = new XElement(Names.TargetImageElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1)), - new XAttribute("Order", row.FieldAsInteger(4)), - XAttributeIfNotNull("Validation", row, 5)); - - AddSymbolPaths(row, 2, xTargetImage); - - if (0 != row.FieldAsInteger(6)) - { - xTargetImage.SetAttributeValue("IgnoreMissingFiles", "yes"); - } - - this.AddChildToParent("UpgradedImages", xTargetImage, row, 3); - this.IndexElement(row, xTargetImage); - } - } - - /// - /// Decompile the TextStyle table. - /// - /// The table to decompile. - private void DecompileTextStyleTable(Table table) - { - foreach (var row in table.Rows) - { - var xTextStyle = new XElement(Names.TextStyleElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("FaceName", row.FieldAsString(1)), - new XAttribute("Size", row.FieldAsString(2))); - - if (!row.IsColumnNull(3)) - { - var color = row.FieldAsInteger(3); - - xTextStyle.SetAttributeValue("Red", color & 0xFF); - xTextStyle.SetAttributeValue("Green", (color & 0xFF00) >> 8); - xTextStyle.SetAttributeValue("Blue", (color & 0xFF0000) >> 16); - } - - if (!row.IsColumnNull(4)) - { - var styleBits = row.FieldAsInteger(4); - - if (WindowsInstallerConstants.MsidbTextStyleStyleBitsBold == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsBold)) - { - xTextStyle.SetAttributeValue("Bold", "yes"); - } - - if (WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic)) - { - xTextStyle.SetAttributeValue("Italic", "yes"); - } - - if (WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline)) - { - xTextStyle.SetAttributeValue("Underline", "yes"); - } - - if (WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike)) - { - xTextStyle.SetAttributeValue("Strike", "yes"); - } - } - - this.UIElement.Add(xTextStyle); - } - } - - /// - /// Decompile the TypeLib table. - /// - /// The table to decompile. - private void DecompileTypeLibTable(Table table) - { - foreach (var row in table.Rows) - { - var id = row.FieldAsString(0); - var xTypeLib = new XElement(Names.TypeLibElement, - new XAttribute("Advertise", "yes"), - new XAttribute("Id", id), - new XAttribute("Language", row.FieldAsInteger(1)), - XAttributeIfNotNull("Description", row, 4), - XAttributeIfNotNull("HelpDirectory", row, 5)); - - if (!row.IsColumnNull(3)) - { - var version = row.FieldAsInteger(3); - - if (65536 == version) - { - this.Messaging.Write(WarningMessages.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, id)); - } - - xTypeLib.SetAttributeValue("MajorVersion", (version & 0xFFFF00) >> 8); - xTypeLib.SetAttributeValue("MinorVersion", version & 0xFF); - } - - if (!row.IsColumnNull(7)) - { - xTypeLib.SetAttributeValue("Cost", row.FieldAsInteger(7)); - } - - // nested under the appropriate File element in FinalizeFileTable - this.IndexElement(row, xTypeLib); - } - } - - /// - /// Decompile the Upgrade table. - /// - /// The table to decompile. - private void DecompileUpgradeTable(Table table) - { - var xUpgrades = new Dictionary(); - - foreach (UpgradeRow upgradeRow in table.Rows) - { - if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty || WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty) - { - continue; // MajorUpgrade rows processed in FinalizeUpgradeTable - } - - if (!xUpgrades.TryGetValue(upgradeRow.UpgradeCode, out var xUpgrade)) - { - xUpgrade = new XElement(Names.UpgradeElement, - new XAttribute("Id", upgradeRow.UpgradeCode)); - - this.RootElement.Add(xUpgrade); - xUpgrades.Add(upgradeRow.UpgradeCode, xUpgrade); - } - - var xUpgradeVersion = new XElement(Names.UpgradeVersionElement, - new XAttribute("Id", upgradeRow.UpgradeCode), - new XAttribute("Property", upgradeRow.ActionProperty)); - - if (null != upgradeRow.VersionMin) - { - xUpgradeVersion.SetAttributeValue("Minimum", upgradeRow.VersionMin); - } - - if (null != upgradeRow.VersionMax) - { - xUpgradeVersion.SetAttributeValue("Maximum", upgradeRow.VersionMax); - } - - if (null != upgradeRow.Language) - { - xUpgradeVersion.SetAttributeValue("Language", upgradeRow.Language); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) - { - xUpgradeVersion.SetAttributeValue("MigrateFeatures", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect)) - { - xUpgradeVersion.SetAttributeValue("OnlyDetect", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) - { - xUpgradeVersion.SetAttributeValue("IgnoreRemoveFailure", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive)) - { - xUpgradeVersion.SetAttributeValue("IncludeMinimum", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) - { - xUpgradeVersion.SetAttributeValue("IncludeMaximum", "yes"); - } - - if (WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive)) - { - xUpgradeVersion.SetAttributeValue("ExcludeLanguages", "yes"); - } - - if (null != upgradeRow.Remove) - { - xUpgradeVersion.SetAttributeValue("RemoveFeatures", upgradeRow.Remove); - } - - xUpgrade.Add(xUpgradeVersion); - } - } - - /// - /// Decompile the UpgradedFiles_OptionalData table. - /// - /// The table to decompile. - private void DecompileUpgradedFiles_OptionalDataTable(Table table) - { - foreach (var row in table.Rows) - { - var xUpgradeFile = new XElement(Names.UpgradeFileElement, - new XAttribute("File", row.FieldAsString(1)), - new XAttribute("Ignore", "no")); - - AddSymbolPaths(row, 2, xUpgradeFile); - - if (!row.IsColumnNull(3) && 1 == row.FieldAsInteger(3)) - { - xUpgradeFile.SetAttributeValue("AllowIgnoreOnError", "yes"); - } - - if (!row.IsColumnNull(4) && 0 != row.FieldAsInteger(4)) - { - xUpgradeFile.SetAttributeValue("WholeFile", "yes"); - } - - this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); - } - } - - /// - /// Decompile the UpgradedFilesToIgnore table. - /// - /// The table to decompile. - private void DecompileUpgradedFilesToIgnoreTable(Table table) - { - foreach (var row in table.Rows) - { - if ("*" != row.FieldAsString(0)) - { - var xUpgradeFile = new XElement(Names.UpgradeFileElement, - new XAttribute("File", row.FieldAsString(1)), - new XAttribute("Ignore", "yes")); - - this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); - } - else - { - this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0])); - } - } - } - - /// - /// Decompile the UpgradedImages table. - /// - /// The table to decompile. - private void DecompileUpgradedImagesTable(Table table) - { - foreach (var row in table.Rows) - { - var xUpgradeImage = new XElement(Names.UpgradeImageElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("SourceFile", row.FieldAsString(1)), - XAttributeIfNotNull("SourcePatch", row, 2)); - - AddSymbolPaths(row, 3, xUpgradeImage); - - this.AddChildToParent("ImageFamilies", xUpgradeImage, row, 4); - this.IndexElement(row, xUpgradeImage); - } - } - - private static void AddSymbolPaths(Row row, int column, XElement xParent) - { - if (!row.IsColumnNull(column)) - { - var symbolPaths = row.FieldAsString(column).Split(';'); - - foreach (var symbolPath in symbolPaths) - { - var xSymbolPath = new XElement(Names.SymbolPathElement, - new XAttribute("Path", symbolPath)); - - xParent.Add(xSymbolPath); - } - } - } - - /// - /// Decompile the UIText table. - /// - /// The table to decompile. - private void DecompileUITextTable(Table table) - { - foreach (var row in table.Rows) - { - var xUiText = new XElement(Names.UITextElement, - new XAttribute("Id", row.FieldAsString(0)), - new XAttribute("Value", row.FieldAsString(1))); - - this.UIElement.Add(xUiText); - } - } - - /// - /// Decompile the Verb table. - /// - /// The table to decompile. - private void DecompileVerbTable(Table table) - { - foreach (var row in table.Rows) - { - var verb = new XElement(Names.VerbElement, - new XAttribute("Id", row.FieldAsString(1)), - XAttributeIfNotNull("Sequence", row, 2), - XAttributeIfNotNull("Command", row, 3), - XAttributeIfNotNull("Argument", row, 4)); - - this.IndexElement(row, verb); - } - } - - /// - /// Gets the RegistryRootType from an integer representation of the root. - /// - /// The source line information for the root. - /// The name of the table containing the field. - /// The field containing the root value. - /// The strongly-typed representation of the root. - /// true if the value could be converted; false otherwise. - private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out string registryRootType) - { - switch (Convert.ToInt32(field.Data)) - { - case (-1): - registryRootType = "HKMU"; - return true; - case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: - registryRootType = "HKCR"; - return true; - case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: - registryRootType = "HKCU"; - return true; - case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: - registryRootType = "HKLM"; - return true; - case WindowsInstallerConstants.MsidbRegistryRootUsers: - registryRootType = "HKU"; - return true; - default: - this.Messaging.Write(WarningMessages.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data)); - registryRootType = null; // assign anything to satisfy the out parameter - return false; - } - } - - /// - /// Set the primary feature for a component. - /// - /// The row which specifies a primary feature. - /// The index of the column contaning the feature identifier. - /// The index of the column containing the component identifier. - private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex) - { - // only products contain primary features - if (OutputType.Product == this.OutputType) - { - var featureField = row.Fields[featureColumnIndex]; - var componentField = row.Fields[componentColumnIndex]; - - if (this.TryGetIndexedElement("FeatureComponents", out var xComponentRef, Convert.ToString(featureField.Data), Convert.ToString(componentField.Data))) - { - xComponentRef.SetAttributeValue("Primary", "yes"); - } - else - { - this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), featureField.Column.Name, Convert.ToString(featureField.Data), componentField.Column.Name, Convert.ToString(componentField.Data), "FeatureComponents")); - } - } - } - - /// - /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it. - /// - /// The collection of all tables. - private static string DetermineMajorUpgradeScheduling(TableIndexedCollection tables) - { - var sequenceRemoveExistingProducts = 0; - var sequenceInstallValidate = 0; - var sequenceInstallInitialize = 0; - var sequenceInstallFinalize = 0; - var sequenceInstallExecute = 0; - var sequenceInstallExecuteAgain = 0; - - var installExecuteSequenceTable = tables["InstallExecuteSequence"]; - if (null != installExecuteSequenceTable) - { - var removeExistingProductsRow = -1; - for (var i = 0; i < installExecuteSequenceTable.Rows.Count; i++) - { - var row = installExecuteSequenceTable.Rows[i]; - var action = row.FieldAsString(0); - var sequence = row.FieldAsInteger(2); - - switch (action) - { - case "RemoveExistingProducts": - sequenceRemoveExistingProducts = sequence; - removeExistingProductsRow = i; - break; - case "InstallValidate": - sequenceInstallValidate = sequence; - break; - case "InstallInitialize": - sequenceInstallInitialize = sequence; - break; - case "InstallExecute": - sequenceInstallExecute = sequence; - break; - case "InstallExecuteAgain": - sequenceInstallExecuteAgain = sequence; - break; - case "InstallFinalize": - sequenceInstallFinalize = sequence; - break; - } - } - - installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow); - } - - if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize) - { - return "afterInstallValidate"; - } - else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute) - { - return "afterInstallInitialize"; - } - else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain) - { - return "afterInstallExecute"; - } - else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize) - { - return "afterInstallExecuteAgain"; - } - else - { - return "afterInstallFinalize"; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs b/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs deleted file mode 100644 index db65bbf7..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Decompile/Names.cs +++ /dev/null @@ -1,160 +0,0 @@ -namespace WixToolset.Core.WindowsInstaller.Decompile -{ - using System.Xml.Linq; - - internal static class Names - { - public static readonly XNamespace WxsNamespace = "http://wixtoolset.org/schemas/v4/wxs"; - - public static readonly XName WixElement = WxsNamespace + "Wix"; - - public static readonly XName PackageElement = WxsNamespace + "Package"; - public static readonly XName ModuleElement = WxsNamespace + "Module"; - public static readonly XName PatchCreationElement = WxsNamespace + "PatchCreation"; - - public static readonly XName SummaryInformationElement = WxsNamespace + "SummaryInformation"; - - public static readonly XName CustomElement = WxsNamespace + "Custom"; - - public static readonly XName AdminExecuteSequenceElement = WxsNamespace + "AdminExecuteSequence"; - public static readonly XName AdminUISequenceElement = WxsNamespace + "AdminUISequence"; - public static readonly XName AdvertiseExecuteSequenceElement = WxsNamespace + "AdvertiseExecuteSequence"; - public static readonly XName InstallExecuteSequenceElement = WxsNamespace + "InstallExecuteSequence"; - public static readonly XName InstallUISequenceElement = WxsNamespace + "InstallUISequence"; - - public static readonly XName AppSearchElement = WxsNamespace + "AppSearch"; - - public static readonly XName PropertyElement = WxsNamespace + "Property"; - - public static readonly XName ProtectRangeElement = WxsNamespace + "ProtectRange"; - public static readonly XName ProtectFileElement = WxsNamespace + "ProtectFile"; - - public static readonly XName FileElement = WxsNamespace + "File"; - - public static readonly XName EnsureTableElement = WxsNamespace + "EnsureTable"; - public static readonly XName PatchInformationElement = WxsNamespace + "PatchInformation"; - - public static readonly XName ProgressTextElement = WxsNamespace + "ProgressText"; - public static readonly XName UIElement = WxsNamespace + "UI"; - - public static readonly XName AppIdElement = WxsNamespace + "AppId"; - - public static readonly XName ControlElement = WxsNamespace + "Control"; - - public static readonly XName BillboardElement = WxsNamespace + "Billboard"; - public static readonly XName BillboardActionElement = WxsNamespace + "BillboardAction"; - - public static readonly XName BinaryElement = WxsNamespace + "Binary"; - - public static readonly XName ClassElement = WxsNamespace + "Class"; - - public static readonly XName FileTypeMaskElement = WxsNamespace + "FileTypeMask"; - - public static readonly XName ComboBoxElement = WxsNamespace + "ComboBox"; - - public static readonly XName ListItemElement = WxsNamespace + "ListItem"; - - public static readonly XName ConditionElement = WxsNamespace + "Condition"; - public static readonly XName PublishElement = WxsNamespace + "Publish"; - public static readonly XName CustomTableElement = WxsNamespace + "CustomTable"; - public static readonly XName ColumnElement = WxsNamespace + "Column"; - public static readonly XName RowElement = WxsNamespace + "Row"; - public static readonly XName DataElement = WxsNamespace + "Data"; - public static readonly XName CreateFolderElement = WxsNamespace + "CreateFolder"; - - public static readonly XName CustomActionElement = WxsNamespace + "CustomAction"; - - public static readonly XName ComponentSearchElement = WxsNamespace + "ComponentSearch"; - public static readonly XName ComponentElement = WxsNamespace + "Component"; - - public static readonly XName LevelElement = WxsNamespace + "Level"; - public static readonly XName DialogElement = WxsNamespace + "Dialog"; - public static readonly XName StandardDirectoryElement = WxsNamespace + "StandardDirectory"; - public static readonly XName DirectoryElement = WxsNamespace + "Directory"; - public static readonly XName DirectorySearchElement = WxsNamespace + "DirectorySearch"; - public static readonly XName CopyFileElement = WxsNamespace + "CopyFile"; - public static readonly XName EnvironmentElement = WxsNamespace + "Environment"; - public static readonly XName ErrorElement = WxsNamespace + "Error"; - public static readonly XName SubscribeElement = WxsNamespace + "Subscribe"; - public static readonly XName ExtensionElement = WxsNamespace + "Extension"; - public static readonly XName ExternalFileElement = WxsNamespace + "ExternalFile"; - public static readonly XName SymbolPathElement = WxsNamespace + "SymbolPath"; - public static readonly XName IgnoreRangeElement = WxsNamespace + "IgnoreRange"; - - public static readonly XName FeatureElement = WxsNamespace + "Feature"; - public static readonly XName ComponentRefElement = WxsNamespace + "ComponentRef"; - public static readonly XName SFPFileElement = WxsNamespace + "SFPFile"; - public static readonly XName IconElement = WxsNamespace + "Icon"; - public static readonly XName FamilyElement = WxsNamespace + "Family"; - public static readonly XName IniFileElement = WxsNamespace + "IniFile"; - public static readonly XName IniFileSearchElement = WxsNamespace + "IniFileSearch"; - public static readonly XName IsolateComponentElement = WxsNamespace + "IsolateComponent"; - public static readonly XName LaunchElement = WxsNamespace + "Launch"; - public static readonly XName ListBoxElement = WxsNamespace + "ListBox"; - public static readonly XName ListViewElement = WxsNamespace + "ListView"; - public static readonly XName PermissionElement = WxsNamespace + "Permission"; - public static readonly XName MediaElement = WxsNamespace + "Media"; - public static readonly XName MIMEElement = WxsNamespace + "MIME"; - public static readonly XName ConfigurationElement = WxsNamespace + "Configuration"; - public static readonly XName DependencyElement = WxsNamespace + "Dependency"; - public static readonly XName ExclusionElement = WxsNamespace + "Exclusion"; - public static readonly XName IgnoreTableElement = WxsNamespace + "IgnoreTable"; - public static readonly XName SubstitutionElement = WxsNamespace + "Substitution"; - public static readonly XName DigitalCertificateElement = WxsNamespace + "DigitalCertificate"; - public static readonly XName DigitalSignatureElement = WxsNamespace + "DigitalSignature"; - public static readonly XName EmbeddedChainerElement = WxsNamespace + "EmbeddedChainer"; - public static readonly XName EmbeddedUIElement = WxsNamespace + "EmbeddedUI"; - public static readonly XName EmbeddedUIResourceElement = WxsNamespace + "EmbeddedUIResource"; - public static readonly XName PermissionExElement = WxsNamespace + "PermissionEx"; - public static readonly XName PackageCertificatesElement = WxsNamespace + "PackageCertificates"; - public static readonly XName PatchCertificatesElement = WxsNamespace + "PatchCertificates"; - public static readonly XName ShortcutPropertyElement = WxsNamespace + "ShortcutProperty"; - public static readonly XName ODBCDataSourceElement = WxsNamespace + "ODBCDataSource"; - public static readonly XName ODBCDriverElement = WxsNamespace + "ODBCDriver"; - public static readonly XName ODBCTranslatorElement = WxsNamespace + "ODBCTranslator"; - public static readonly XName PatchMetadataElement = WxsNamespace + "PatchMetadata"; - public static readonly XName OptimizeCustomActionsElement = WxsNamespace + "OptimizeCustomActions"; - public static readonly XName CustomPropertyElement = WxsNamespace + "CustomProperty"; - public static readonly XName PatchSequenceElement = WxsNamespace + "PatchSequence"; - public static readonly XName ProgIdElement = WxsNamespace + "ProgId"; - public static readonly XName ReplacePatchElement = WxsNamespace + "ReplacePatch"; - public static readonly XName TargetProductCodeElement = WxsNamespace + "TargetProductCode"; - public static readonly XName PatchPropertyElement = WxsNamespace + "PatchProperty"; - public static readonly XName CategoryElement = WxsNamespace + "Category"; - public static readonly XName RadioButtonElement = WxsNamespace + "RadioButton"; - public static readonly XName RadioButtonGroupElement = WxsNamespace + "RadioButtonGroup"; - public static readonly XName RegistryKeyElement = WxsNamespace + "RegistryKey"; - public static readonly XName RegistryValueElement = WxsNamespace + "RegistryValue"; - public static readonly XName MultiStringElement = WxsNamespace + "MultiString"; - public static readonly XName RegistrySearchElement = WxsNamespace + "RegistrySearch"; - public static readonly XName RemoveFolderElement = WxsNamespace + "RemoveFolder"; - public static readonly XName RemoveFileElement = WxsNamespace + "RemoveFile"; - public static readonly XName RemoveRegistryKeyElement = WxsNamespace + "RemoveRegistryKey"; - public static readonly XName RemoveRegistryValueElement = WxsNamespace + "RemoveRegistryValue"; - public static readonly XName ReserveCostElement = WxsNamespace + "ReserveCost"; - public static readonly XName ServiceControlElement = WxsNamespace + "ServiceControl"; - public static readonly XName ServiceArgumentElement = WxsNamespace + "ServiceArgument"; - public static readonly XName ServiceInstallElement = WxsNamespace + "ServiceInstall"; - public static readonly XName ServiceDependencyElement = WxsNamespace + "ServiceDependency"; - public static readonly XName SFPCatalogElement = WxsNamespace + "SFPCatalog"; - public static readonly XName ShortcutElement = WxsNamespace + "Shortcut"; - public static readonly XName FileSearchElement = WxsNamespace + "FileSearch"; - public static readonly XName TargetFileElement = WxsNamespace + "TargetFile"; - public static readonly XName TargetImageElement = WxsNamespace + "TargetImage"; - public static readonly XName TextStyleElement = WxsNamespace + "TextStyle"; - public static readonly XName TypeLibElement = WxsNamespace + "TypeLib"; - public static readonly XName UpgradeElement = WxsNamespace + "Upgrade"; - public static readonly XName UpgradeVersionElement = WxsNamespace + "UpgradeVersion"; - public static readonly XName UpgradeFileElement = WxsNamespace + "UpgradeFile"; - public static readonly XName UpgradeImageElement = WxsNamespace + "UpgradeImage"; - public static readonly XName UITextElement = WxsNamespace + "UIText"; - public static readonly XName VerbElement = WxsNamespace + "Verb"; - public static readonly XName ComplianceCheckElement = WxsNamespace + "ComplianceCheck"; - public static readonly XName FileSearchRefElement = WxsNamespace + "FileSearchRef"; - public static readonly XName ComplianceDriveElement = WxsNamespace + "ComplianceDrive"; - public static readonly XName DirectorySearchRefElement = WxsNamespace + "DirectorySearchRef"; - public static readonly XName RegistrySearchRefElement = WxsNamespace + "RegistrySearchRef"; - public static readonly XName MajorUpgradeElement = WxsNamespace + "MajorUpgrade"; - //public static readonly XName Element = WxsNamespace + ""; - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Differ.cs b/src/WixToolset.Core.WindowsInstaller/Differ.cs deleted file mode 100644 index 304d0152..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Differ.cs +++ /dev/null @@ -1,610 +0,0 @@ -// 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. - -#if DELETE - -namespace WixToolset.Core.WindowsInstaller -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using WixToolset.Core.WindowsInstaller.Msi; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - /// - /// Creates a transform by diffing two outputs. - /// - public sealed class Differ - { - private readonly List inspectorExtensions; - private bool showPedanticMessages; - private bool suppressKeepingSpecialRows; - private bool preserveUnchangedRows; - private const char sectionDelimiter = '/'; - private readonly IMessaging messaging; - private SummaryInformationStreams transformSummaryInfo; - - /// - /// Instantiates a new Differ class. - /// - public Differ(IMessaging messaging) - { - this.inspectorExtensions = new List(); - this.messaging = messaging; - } - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - public bool ShowPedanticMessages - { - get { return this.showPedanticMessages; } - set { this.showPedanticMessages = value; } - } - - /// - /// Gets or sets the option to suppress keeping special rows. - /// - /// The option to suppress keeping special rows. - public bool SuppressKeepingSpecialRows - { - get { return this.suppressKeepingSpecialRows; } - set { this.suppressKeepingSpecialRows = value; } - } - - /// - /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. - /// - /// The option to keep all rows including unchanged rows. - public bool PreserveUnchangedRows - { - get { return this.preserveUnchangedRows; } - set { this.preserveUnchangedRows = value; } - } - - /// - /// Adds an extension. - /// - /// The extension to add. - public void AddExtension(IInspectorExtension extension) - { - this.inspectorExtensions.Add(extension); - } - - /// - /// Creates a transform by diffing two outputs. - /// - /// The target output. - /// The updated output. - /// The transform. - public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput) - { - return this.Diff(targetOutput, updatedOutput, 0); - } - - /// - /// Creates a transform by diffing two outputs. - /// - /// The target output. - /// The updated output. - /// - /// The transform. - public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, TransformFlags validationFlags) - { - WindowsInstallerData transform = new WindowsInstallerData(null); - transform.Type = OutputType.Transform; - transform.Codepage = updatedOutput.Codepage; - this.transformSummaryInfo = new SummaryInformationStreams(); - - // compare the codepages - if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) - { - this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); - if (null != updatedOutput.SourceLineNumbers) - { - this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); - } - } - - // compare the output types - if (targetOutput.Type != updatedOutput.Type) - { - throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); - } - - // compare the contents of the tables - foreach (Table targetTable in targetOutput.Tables) - { - Table updatedTable = updatedOutput.Tables[targetTable.Name]; - TableOperation operation = TableOperation.None; - - List rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); - - if (TableOperation.Drop == operation) - { - Table droppedTable = transform.EnsureTable(targetTable.Definition); - droppedTable.Operation = TableOperation.Drop; - } - else if (TableOperation.None == operation) - { - Table modified = transform.EnsureTable(updatedTable.Definition); - rows.ForEach(r => modified.Rows.Add(r)); - } - } - - // added tables - foreach (Table updatedTable in updatedOutput.Tables) - { - if (null == targetOutput.Tables[updatedTable.Name]) - { - Table addedTable = transform.EnsureTable(updatedTable.Definition); - addedTable.Operation = TableOperation.Add; - - foreach (Row updatedRow in updatedTable.Rows) - { - updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; - addedTable.Rows.Add(updatedRow); - } - } - } - - // set summary information properties - if (!this.suppressKeepingSpecialRows) - { - Table summaryInfoTable = transform.Tables["_SummaryInformation"]; - this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); - } - - return transform; - } - - /// - /// Add a row to the using the primary key. - /// - /// The indexed rows. - /// The row to index. - private void AddIndexedRow(IDictionary index, Row row) - { - string primaryKey = row.GetPrimaryKey('/'); - if (null != primaryKey) - { - // Overriding WixActionRows have a primary key defined and take precedence in the index. - if (row is WixActionRow) - { - WixActionRow currentRow = (WixActionRow)row; - if (index.Contains(primaryKey)) - { - // If the current row is not overridable, see if the indexed row is. - if (!currentRow.Overridable) - { - WixActionRow indexedRow = index[primaryKey] as WixActionRow; - if (null != indexedRow && indexedRow.Overridable) - { - // The indexed key is overridable and should be replaced - // (not removed and re-added which results in two Array.Copy - // operations for SortedList, or may be re-hashing in other - // implementations of IDictionary). - index[primaryKey] = currentRow; - } - } - - // If we got this far, the row does not need to be indexed. - return; - } - } - - // Nothing else should be added more than once. - if (!index.Contains(primaryKey)) - { - index.Add(primaryKey, row); - } - else if (this.showPedanticMessages) - { - this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); - } - } - else // use the string representation of the row as its primary key (it may not be unique) - { - // this is provided for compatibility with unreal tables with no primary key - // all real tables must specify at least one column as the primary key - primaryKey = row.ToString(); - index[primaryKey] = row; - } - } - - private Row CompareRows(Table targetTable, Row targetRow, Row updatedRow, out RowOperation operation, out bool keepRow) - { - Row comparedRow = null; - keepRow = false; - operation = RowOperation.None; - - if (null == targetRow ^ null == updatedRow) - { - if (null == targetRow) - { - operation = updatedRow.Operation = RowOperation.Add; - comparedRow = updatedRow; - } - else if (null == updatedRow) - { - operation = targetRow.Operation = RowOperation.Delete; - targetRow.SectionId = targetRow.SectionId + sectionDelimiter; - comparedRow = targetRow; - keepRow = true; - } - } - else // possibly modified - { - updatedRow.Operation = RowOperation.None; - if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) - { - // ignore rows that shouldn't be in a transform - if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) - { - updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; - comparedRow = updatedRow; - keepRow = true; - operation = RowOperation.Modify; - } - } - else - { - if (this.preserveUnchangedRows) - { - keepRow = true; - } - - for (int i = 0; i < updatedRow.Fields.Length; i++) - { - ColumnDefinition columnDefinition = updatedRow.Fields[i].Column; - - if (!columnDefinition.PrimaryKey) - { - bool modified = false; - - if (i >= targetRow.Fields.Length) - { - columnDefinition.Added = true; - modified = true; - } - else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) - { - if (null == targetRow[i] ^ null == updatedRow[i]) - { - modified = true; - } - else if (null != targetRow[i] && null != updatedRow[i]) - { - modified = ((int)targetRow[i] != (int)updatedRow[i]); - } - } - else if (ColumnType.Preserved == columnDefinition.Type) - { - updatedRow.Fields[i].PreviousData = (string)targetRow.Fields[i].Data; - - // keep rows containing preserved fields so the historical data is available to the binder - keepRow = !this.suppressKeepingSpecialRows; - } - else if (ColumnType.Object == columnDefinition.Type) - { - ObjectField targetObjectField = (ObjectField)targetRow.Fields[i]; - ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i]; - - updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex; - updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri; - - // always keep a copy of the previous data even if they are identical - // This makes diff.wixmst clean and easier to control patch logic - updatedObjectField.PreviousData = (string)targetObjectField.Data; - - // always remember the unresolved data for target build - updatedObjectField.UnresolvedPreviousData = (string)targetObjectField.UnresolvedData; - - // keep rows containing object fields so the files can be compared in the binder - keepRow = !this.suppressKeepingSpecialRows; - } - else - { - modified = ((string)targetRow[i] != (string)updatedRow[i]); - } - - if (modified) - { - if (null != updatedRow.Fields[i].PreviousData) - { - updatedRow.Fields[i].PreviousData = targetRow.Fields[i].Data.ToString(); - } - - updatedRow.Fields[i].Modified = true; - operation = updatedRow.Operation = RowOperation.Modify; - keepRow = true; - } - } - } - - if (keepRow) - { - comparedRow = updatedRow; - comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; - } - } - } - - return comparedRow; - } - - private List CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) - { - List rows = new List(); - operation = TableOperation.None; - - // dropped tables - if (null == updatedTable ^ null == targetTable) - { - if (null == targetTable) - { - operation = TableOperation.Add; - rows.AddRange(updatedTable.Rows); - } - else if (null == updatedTable) - { - operation = TableOperation.Drop; - } - } - else // possibly modified tables - { - SortedList updatedPrimaryKeys = new SortedList(); - SortedList targetPrimaryKeys = new SortedList(); - - // compare the table definitions - if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) - { - // continue to the next table; may be more mismatches - this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); - } - else - { - this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); - - // diff the target and updated rows - foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys) - { - string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key; - bool keepRow = false; - RowOperation rowOperation = RowOperation.None; - - Row compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value as Row, updatedPrimaryKeys[targetPrimaryKey] as Row, out rowOperation, out keepRow); - - if (keepRow) - { - rows.Add(compared); - } - } - - // find the inserted rows - foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys) - { - string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key; - - if (!targetPrimaryKeys.Contains(updatedPrimaryKey)) - { - Row updatedRow = (Row)updatedPrimaryKeyEntry.Value; - - updatedRow.Operation = RowOperation.Add; - updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; - rows.Add(updatedRow); - } - } - } - } - - return rows; - } - - private void IndexPrimaryKeys(Table targetTable, SortedList targetPrimaryKeys, Table updatedTable, SortedList updatedPrimaryKeys) - { - // index the target rows - foreach (Row row in targetTable.Rows) - { - this.AddIndexedRow(targetPrimaryKeys, row); - - if ("Property" == targetTable.Name) - { - if ("ProductCode" == (string)row[0]) - { - this.transformSummaryInfo.TargetProductCode = (string)row[1]; - if ("*" == this.transformSummaryInfo.TargetProductCode) - { - this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); - } - } - else if ("ProductVersion" == (string)row[0]) - { - this.transformSummaryInfo.TargetProductVersion = (string)row[1]; - } - else if ("UpgradeCode" == (string)row[0]) - { - this.transformSummaryInfo.TargetUpgradeCode = (string)row[1]; - } - } - else if ("_SummaryInformation" == targetTable.Name) - { - if (1 == (int)row[0]) // PID_CODEPAGE - { - this.transformSummaryInfo.TargetSummaryInfoCodepage = (string)row[1]; - } - else if (7 == (int)row[0]) // PID_TEMPLATE - { - this.transformSummaryInfo.TargetPlatformAndLanguage = (string)row[1]; - } - else if (14 == (int)row[0]) // PID_PAGECOUNT - { - this.transformSummaryInfo.TargetMinimumVersion = (string)row[1]; - } - } - } - - // index the updated rows - foreach (Row row in updatedTable.Rows) - { - this.AddIndexedRow(updatedPrimaryKeys, row); - - if ("Property" == updatedTable.Name) - { - if ("ProductCode" == (string)row[0]) - { - this.transformSummaryInfo.UpdatedProductCode = (string)row[1]; - if ("*" == this.transformSummaryInfo.UpdatedProductCode) - { - this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); - } - } - else if ("ProductVersion" == (string)row[0]) - { - this.transformSummaryInfo.UpdatedProductVersion = (string)row[1]; - } - } - else if ("_SummaryInformation" == updatedTable.Name) - { - if (1 == (int)row[0]) // PID_CODEPAGE - { - this.transformSummaryInfo.UpdatedSummaryInfoCodepage = (string)row[1]; - } - else if (7 == (int)row[0]) // PID_TEMPLATE - { - this.transformSummaryInfo.UpdatedPlatformAndLanguage = (string)row[1]; - } - else if (14 == (int)row[0]) // PID_PAGECOUNT - { - this.transformSummaryInfo.UpdatedMinimumVersion = (string)row[1]; - } - } - } - } - - private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) - { - // calculate the minimum version of MSI required to process the transform - int targetMin; - int updatedMin; - int minimumVersion = 100; - - if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out updatedMin)) - { - minimumVersion = Math.Max(targetMin, updatedMin); - } - - Hashtable summaryRows = new Hashtable(summaryInfoTable.Rows.Count); - foreach (Row row in summaryInfoTable.Rows) - { - summaryRows[row[0]] = row; - - if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) - { - row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; - row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; - } - else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0]) - { - row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; - } - else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) - { - row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; - } - else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) - { - row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); - } - else if ((int)SummaryInformation.Transform.InstallerRequirement == (int)row[0]) - { - row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); - } - else if ((int)SummaryInformation.Transform.Security == (int)row[0]) - { - row[1] = "4"; - } - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) - { - Row summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; - summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) - { - Row summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; - summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) - { - Row summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; - summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.InstallerRequirement)) - { - Row summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; - summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); - } - - if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) - { - Row summaryRow = summaryInfoTable.CreateRow(null); - summaryRow[0] = (int)SummaryInformation.Transform.Security; - summaryRow[1] = "4"; - } - } - - private class SummaryInformationStreams - { - public string TargetSummaryInfoCodepage - { get; set; } - - public string TargetPlatformAndLanguage - { get; set; } - - public string TargetProductCode - { get; set; } - - public string TargetProductVersion - { get; set; } - - public string TargetUpgradeCode - { get; set; } - - public string TargetMinimumVersion - { get; set; } - - public string UpdatedSummaryInfoCodepage - { get; set; } - - public string UpdatedPlatformAndLanguage - { get; set; } - - public string UpdatedProductCode - { get; set; } - - public string UpdatedProductVersion - { get; set; } - - public string UpdatedMinimumVersion - { get; set; } - } - } -} - -#endif diff --git a/src/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs b/src/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs deleted file mode 100644 index 8305b5e6..00000000 --- a/src/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs +++ /dev/null @@ -1,121 +0,0 @@ -// 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.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class WindowsInstallerBackendHelper : IWindowsInstallerBackendHelper - { - private readonly IBackendHelper backendHelper; - - public WindowsInstallerBackendHelper(IServiceProvider serviceProvider) - { - this.backendHelper = serviceProvider.GetService(); - } - - #region IBackendHelper interfaces - - public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); - - public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); - - public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); - - public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); - - public string CreateGuid() => this.backendHelper.CreateGuid(); - - public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); - - public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); - - public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); - - public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); - - public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); - - public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); - - public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); - - public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); - - public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); - - public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); - - public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); - - public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); - - public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); - - public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); - - public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); - - #endregion - - #region IWindowsInstallerBackendHelper interfaces - - public Row CreateRow(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinition tableDefinition) - { - var table = data.EnsureTable(tableDefinition); - - var row = table.CreateRow(symbol.SourceLineNumbers); - row.SectionId = section.Id; - - return row; - } - - public bool TryAddSymbolToMatchingTableDefinitions(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinitionCollection tableDefinitions) - { - var tableDefinition = tableDefinitions.FirstOrDefault(t => t.SymbolDefinition?.Name == symbol.Definition.Name); - if (tableDefinition == null) - { - return false; - } - - var row = this.CreateRow(section, symbol, data, tableDefinition); - var rowOffset = 0; - - if (tableDefinition.SymbolIdIsPrimaryKey) - { - row[0] = symbol.Id.Id; - rowOffset = 1; - } - - for (var i = 0; i < symbol.Fields.Length; ++i) - { - if (i < tableDefinition.Columns.Length) - { - var column = tableDefinition.Columns[i + rowOffset]; - - switch (column.Type) - { - case ColumnType.Number: - row[i + rowOffset] = column.Nullable ? symbol.AsNullableNumber(i) : symbol.AsNumber(i); - break; - - default: - row[i + rowOffset] = symbol.AsString(i); - break; - } - } - } - - return true; - } - - #endregion - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs b/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs deleted file mode 100644 index 57f2f753..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs +++ /dev/null @@ -1,272 +0,0 @@ -// 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.Inscribe -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Runtime.InteropServices; - using System.Security.Cryptography.X509Certificates; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class InscribeMsiPackageCommand - { - public InscribeMsiPackageCommand(IInscribeContext context) - { - this.Context = context; - this.Messaging = context.ServiceProvider.GetService(); - this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService(); - this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); - } - - private IInscribeContext Context { get; } - - private IMessaging Messaging { get; } - - private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - public bool Execute() - { - // Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered - var foundUnsignedExternals = false; - var shouldCommit = false; - - var attributes = File.GetAttributes(this.Context.InputFilePath); - if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) - { - this.Messaging.Write(ErrorMessages.ReadOnlyOutputFile(this.Context.InputFilePath)); - return shouldCommit; - } - - using (var database = new Database(this.Context.InputFilePath, OpenDatabase.Transact)) - { - // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content - var codepage = 1252; - - // list of certificates for this database (hash/identifier) - var certificates = new Dictionary(); - - // Reset the in-memory tables for this new database - var digitalSignatureTable = new Table(this.TableDefinitions["MsiDigitalSignature"]); - var digitalCertificateTable = new Table(this.TableDefinitions["MsiDigitalCertificate"]); - - // If any digital signature records exist that are not of the media type, preserve them - if (database.TableExists("MsiDigitalSignature")) - { - using (var digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) - { - foreach (var digitalSignatureRecord in digitalSignatureView.Records) - { - Row digitalSignatureRow = null; - digitalSignatureRow = digitalSignatureTable.CreateRow(null); - - var table = digitalSignatureRecord.GetString(0); - var signObject = digitalSignatureRecord.GetString(1); - - digitalSignatureRow[0] = table; - digitalSignatureRow[1] = signObject; - digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); - - if (false == digitalSignatureRecord.IsNull(3)) - { - // Export to a file, because the MSI API's require us to provide a file path on disk - var hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature"); - var hashFileName = String.Concat(table, ".", signObject, ".bin"); - - Directory.CreateDirectory(hashPath); - hashPath = Path.Combine(hashPath, hashFileName); - - using (var fs = File.Create(hashPath)) - { - int bytesRead; - var buffer = new byte[1024 * 4]; - - while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) - { - fs.Write(buffer, 0, bytesRead); - } - } - - digitalSignatureRow[3] = hashFileName; - } - } - } - } - - // If any digital certificates exist, extract and preserve them - if (database.TableExists("MsiDigitalCertificate")) - { - using (var digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) - { - foreach (var digitalCertificateRecord in digitalCertificateView.Records) - { - var certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate - - // Export to a file, because the MSI API's require us to provide a file path on disk - var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); - Directory.CreateDirectory(certPath); - certPath = Path.Combine(certPath, String.Concat(certificateId, ".cer")); - - using (var fs = File.Create(certPath)) - { - int bytesRead; - var buffer = new byte[1024 * 4]; - - while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) - { - fs.Write(buffer, 0, bytesRead); - } - } - - // Add it to our "add to MsiDigitalCertificate" table dictionary - var digitalCertificateRow = digitalCertificateTable.CreateRow(null); - digitalCertificateRow[0] = certificateId; - - // Now set the file path on disk where this binary stream will be picked up at import time - digitalCertificateRow[1] = String.Concat(certificateId, ".cer"); - - // Load the cert to get it's thumbprint - var cert = X509Certificate.CreateFromCertFile(certPath); - var cert2 = new X509Certificate2(cert); - - certificates.Add(cert2.Thumbprint, certificateId); - } - } - } - - using (var mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) - { - foreach (var mediaRecord in mediaView.Records) - { - X509Certificate2 cert2 = null; - Row digitalSignatureRow = null; - - var cabName = mediaRecord.GetString(4); // get the name of the cab - // If there is no cabinet or it's an internal cab, skip it. - if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) - { - continue; - } - - var cabId = mediaRecord.GetString(1); // get the ID of the cab - var cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName); - - // If the cabs aren't there, throw an error but continue to catch the other errors - if (!File.Exists(cabPath)) - { - this.Messaging.Write(ErrorMessages.WixFileNotFound(cabPath)); - continue; - } - - try - { - // Get the certificate from the cab - var signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); - cert2 = new X509Certificate2(signedFileCert); - } - catch (System.Security.Cryptography.CryptographicException e) - { - var HResult = unchecked((uint)Marshal.GetHRForException(e)); - - // If the file has no cert, continue, but flag that we found at least one so we can later give a warning - if (0x80092009 == HResult) // CRYPT_E_NO_MATCH - { - foundUnsignedExternals = true; - continue; - } - - // todo: exactly which HRESULT corresponds to this issue? - // If it's one of these exact platforms, warn the user that it may be due to their OS. - if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 - (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP - { - this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); - } - else // otherwise, generic error - { - this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); - } - } - - // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added - if (!certificates.ContainsKey(cert2.Thumbprint)) - { - // generate a stable identifier - var certificateGeneratedId = this.WindowsInstallerBackendHelper.GenerateIdentifier("cer", cert2.Thumbprint); - - // Add it to our "add to MsiDigitalCertificate" table dictionary - var digitalCertificateRow = digitalCertificateTable.CreateRow(null); - digitalCertificateRow[0] = certificateGeneratedId; - - // Export to a file, because the MSI API's require us to provide a file path on disk - var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); - Directory.CreateDirectory(certPath); - certPath = Path.Combine(certPath, String.Concat(cert2.Thumbprint, ".cer")); - File.Delete(certPath); - - using (var writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) - { - writer.Write(cert2.RawData); - writer.Close(); - } - - // Now set the file path on disk where this binary stream will be picked up at import time - digitalCertificateRow[1] = String.Concat(cert2.Thumbprint, ".cer"); - - certificates.Add(cert2.Thumbprint, certificateGeneratedId); - } - - digitalSignatureRow = digitalSignatureTable.CreateRow(null); - - digitalSignatureRow[0] = "Media"; - digitalSignatureRow[1] = cabId; - digitalSignatureRow[2] = certificates[cert2.Thumbprint]; - } - } - - if (digitalCertificateTable.Rows.Count > 0) - { - var command = new CreateIdtFileCommand(this.Messaging, digitalCertificateTable, codepage, this.Context.IntermediateFolder, true); - command.Execute(); - - database.Import(command.IdtPath); - shouldCommit = true; - } - - if (digitalSignatureTable.Rows.Count > 0) - { - var command = new CreateIdtFileCommand(this.Messaging, digitalSignatureTable, codepage, this.Context.IntermediateFolder, true); - command.Execute(); - - database.Import(command.IdtPath); - shouldCommit = true; - } - - // TODO: if we created the table(s), then we should add the _Validation records for them. - - certificates = null; - - // If we did find external cabs but not all of them were signed, give a warning - if (foundUnsignedExternals) - { - this.Messaging.Write(WarningMessages.ExternalCabsAreNotSigned(this.Context.InputFilePath)); - } - - if (shouldCommit) - { - database.Commit(); - } - } - - return shouldCommit; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Melter.cs b/src/WixToolset.Core.WindowsInstaller/Melter.cs deleted file mode 100644 index 29e19e49..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Melter.cs +++ /dev/null @@ -1,399 +0,0 @@ -// 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 -{ - using System; - using System.CodeDom.Compiler; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Globalization; - using System.IO; - using System.Text; - using System.Text.RegularExpressions; - using WixToolset.Data; - - /// - /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. - /// - public sealed class Melter - { -#if TODO_MELT - private MelterCore core; - private Decompiler decompiler; - - private Wix.ComponentGroup componentGroup; - private Wix.DirectoryRef primaryDirectoryRef; - private Wix.Fragment fragment; - - private string id; - private string moduleId; - private const string nullGuid = "{00000000-0000-0000-0000-000000000000}"; - - public string Id - { - get { return this.id; } - set { this.id = value; } - } - - public Decompiler Decompiler - { - get { return this.decompiler; } - set { this.decompiler = value; } - } - - /// - /// Creates a new melter object. - /// - /// The decompiler to use during the melting process. - /// The Id to use for the ComponentGroup, DirectoryRef, and WixVariables. If null, defaults to the Module's Id - public Melter(Decompiler decompiler, string id) - { - this.core = new MelterCore(); - - this.componentGroup = new Wix.ComponentGroup(); - this.fragment = new Wix.Fragment(); - this.primaryDirectoryRef = new Wix.DirectoryRef(); - - this.decompiler = decompiler; - this.id = id; - - if (null == this.decompiler) - { - this.core.OnMessage(WixErrors.ExpectedDecompiler("The melting process")); - } - } - - /// - /// Converts a Module wixout into a ComponentGroup. - /// - /// The output object representing the unbound merge module to melt. - /// The converted Module as a ComponentGroup. - public Wix.Wix Melt(Output wixout) - { - this.moduleId = GetModuleId(wixout); - - // Assign the default componentGroupId if none was specified - if (null == this.id) - { - this.id = this.moduleId; - } - - this.componentGroup.Id = this.id; - this.primaryDirectoryRef.Id = this.id; - - PreDecompile(wixout); - - wixout.Type = OutputType.Product; - this.decompiler.TreatProductAsModule = true; - Wix.Wix wix = this.decompiler.Decompile(wixout); - - if (null == wix) - { - return wix; - } - - ConvertModule(wix); - - return wix; - } - - /// - /// Converts a Module to a ComponentGroup and adds all of its relevant elements to the main fragment. - /// - /// The output object representing an unbound merge module. - private void ConvertModule(Wix.Wix wix) - { - Wix.Product product = Melter.GetProduct(wix); - - List customActionsRemoved = new List(); - Dictionary customsToRemove = new Dictionary(); - - foreach (Wix.ISchemaElement child in product.Children) - { - Wix.Directory childDir = child as Wix.Directory; - if (null != childDir) - { - bool isTargetDir = this.WalkDirectory(childDir); - if (isTargetDir) - { - continue; - } - } - else - { - Wix.Dependency childDep = child as Wix.Dependency; - if (null != childDep) - { - this.AddPropertyRef(childDep.RequiredId); - continue; - } - else if (child is Wix.Package) - { - continue; - } - else if (child is Wix.CustomAction) - { - Wix.CustomAction customAction = child as Wix.CustomAction; - string directoryId; - if (StartsWithStandardDirectoryId(customAction.Id, out directoryId) && customAction.Property == customAction.Id) - { - customActionsRemoved.Add(customAction.Id); - continue; - } - } - else if (child is Wix.InstallExecuteSequence) - { - Wix.InstallExecuteSequence installExecuteSequence = child as Wix.InstallExecuteSequence; - - foreach (Wix.ISchemaElement sequenceChild in installExecuteSequence.Children) - { - Wix.Custom custom = sequenceChild as Wix.Custom; - string directoryId; - if (custom != null && StartsWithStandardDirectoryId(custom.Action, out directoryId)) - { - customsToRemove.Add(custom, installExecuteSequence); - } - } - } - } - - this.fragment.AddChild(child); - } - - // For any customaction that we removed, also remove the scheduling of that action. - foreach (Wix.Custom custom in customsToRemove.Keys) - { - if (customActionsRemoved.Contains(custom.Action)) - { - ((Wix.InstallExecuteSequence)customsToRemove[custom]).RemoveChild(custom); - } - } - - AddProperty(this.moduleId, this.id); - - wix.RemoveChild(product); - wix.AddChild(this.fragment); - - this.fragment.AddChild(this.componentGroup); - this.fragment.AddChild(this.primaryDirectoryRef); - } - - /// - /// Gets the module from the Wix object. - /// - /// The Wix object. - /// The Module in the Wix object, null if no Module was found - private static Wix.Product GetProduct(Wix.Wix wix) - { - foreach (Wix.ISchemaElement element in wix.Children) - { - Wix.Product productElement = element as Wix.Product; - if (null != productElement) - { - return productElement; - } - } - return null; - } - - /// - /// Adds a PropertyRef to the main Fragment. - /// - /// Id of the PropertyRef. - private void AddPropertyRef(string propertyRefId) - { - Wix.PropertyRef propertyRef = new Wix.PropertyRef(); - propertyRef.Id = propertyRefId; - this.fragment.AddChild(propertyRef); - } - - /// - /// Adds a Property to the main Fragment. - /// - /// Id of the Property. - /// Value of the Property. - private void AddProperty(string propertyId, string value) - { - Wix.Property property = new Wix.Property(); - property.Id = propertyId; - property.Value = value; - this.fragment.AddChild(property); - } - - /// - /// Walks a directory structure obtaining Component Id's and Standard Directory Id's. - /// - /// The Directory to walk. - /// true if the directory is TARGETDIR. - private bool WalkDirectory(Wix.Directory directory) - { - bool isTargetDir = false; - if ("TARGETDIR" == directory.Id) - { - isTargetDir = true; - } - - string standardDirectoryId = null; - if (Melter.StartsWithStandardDirectoryId(directory.Id, out standardDirectoryId) && !isTargetDir) - { - this.AddSetPropertyCustomAction(directory.Id, String.Format(CultureInfo.InvariantCulture, "[{0}]", standardDirectoryId)); - } - - foreach (Wix.ISchemaElement child in directory.Children) - { - Wix.Directory childDir = child as Wix.Directory; - if (null != childDir) - { - if (isTargetDir) - { - this.primaryDirectoryRef.AddChild(child); - } - this.WalkDirectory(childDir); - } - else - { - Wix.Component childComponent = child as Wix.Component; - if (null != childComponent) - { - if (isTargetDir) - { - this.primaryDirectoryRef.AddChild(child); - } - this.AddComponentRef(childComponent); - } - } - } - - return isTargetDir; - } - - /// - /// Gets the module Id out of the Output object. - /// - /// The output object. - /// The module Id from the Output object. - private string GetModuleId(Output wixout) - { - // get the moduleId from the wixout - Table moduleSignatureTable = wixout.Tables["ModuleSignature"]; - if (null == moduleSignatureTable || 0 >= moduleSignatureTable.Rows.Count) - { - this.core.OnMessage(WixErrors.ExpectedTableInMergeModule("ModuleSignature")); - } - return moduleSignatureTable.Rows[0].Fields[0].Data.ToString(); - } - - /// - /// Determines if the directory Id starts with a standard directory id. - /// - /// The directory id. - /// The standard directory id. - /// true if the directory starts with a standard directory id. - private static bool StartsWithStandardDirectoryId(string directoryId, out string standardDirectoryId) - { - standardDirectoryId = null; - foreach (string id in WindowsInstallerStandard.GetStandardDirectories()) - { - if (directoryId.StartsWith(id, StringComparison.Ordinal)) - { - standardDirectoryId = id; - return true; - } - } - return false; - } - - /// - /// Adds a ComponentRef to the main ComponentGroup. - /// - /// The component to add. - private void AddComponentRef(Wix.Component component) - { - Wix.ComponentRef componentRef = new Wix.ComponentRef(); - componentRef.Id = component.Id; - this.componentGroup.AddChild(componentRef); - } - - /// - /// Adds a SetProperty CA for a Directory. - /// - /// The Id of the Property to set. - /// The value to set the Property to. - private void AddSetPropertyCustomAction(string propertyId, string value) - { - // Add the action - Wix.CustomAction customAction = new Wix.CustomAction(); - customAction.Id = propertyId; - customAction.Property = propertyId; - customAction.Value = value; - this.fragment.AddChild(customAction); - - // Schedule the action - Wix.InstallExecuteSequence installExecuteSequence = new Wix.InstallExecuteSequence(); - Wix.Custom custom = new Wix.Custom(); - custom.Action = customAction.Id; - custom.Before = "CostInitialize"; - installExecuteSequence.AddChild(custom); - this.fragment.AddChild(installExecuteSequence); - } - - /// - /// Does any operations to the wixout that would need to be done before decompiling. - /// - /// The output object representing the unbound merge module. - private void PreDecompile(Output wixout) - { - string wixVariable = String.Format(CultureInfo.InvariantCulture, "!(wix.{0}", this.id); - - foreach (Table table in wixout.Tables) - { - // Determine if the table has a feature foreign key - bool hasFeatureForeignKey = false; - foreach (ColumnDefinition columnDef in table.Definition.Columns) - { - if (null != columnDef.KeyTable) - { - string[] keyTables = columnDef.KeyTable.Split(';'); - foreach (string keyTable in keyTables) - { - if ("Feature" == keyTable) - { - hasFeatureForeignKey = true; - break; - } - } - } - } - - // If this table has no foreign keys to the feature table, skip it. - if (!hasFeatureForeignKey) - { - continue; - } - - // Go through all the rows and replace the null guid with the wix variable - // for columns that are foreign keys into the feature table. - foreach (Row row in table.Rows) - { - foreach (Field field in row.Fields) - { - if (null != field.Column.KeyTable) - { - string[] keyTables = field.Column.KeyTable.Split(';'); - foreach (string keyTable in keyTables) - { - if ("Feature" == keyTable) - { - field.Data = field.Data.ToString().Replace(nullGuid, wixVariable); - break; - } - } - } - } - } - } - } -#endif - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/MelterCore.cs b/src/WixToolset.Core.WindowsInstaller/MelterCore.cs deleted file mode 100644 index 034c9465..00000000 --- a/src/WixToolset.Core.WindowsInstaller/MelterCore.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 -{ - using WixToolset.Data; - - /// - /// Melts a Module Wix document into a ComponentGroup representation. - /// - public sealed class MelterCore - { -#if TODO_MELT - /// - /// Gets whether the melter core encountered an error while processing. - /// - /// Flag if core encountered an error during processing. - public bool EncounteredError - { - get { return Messaging.Instance.EncounteredError; } - } - - /// - /// Sends a message to the message delegate if there is one. - /// - /// Message event arguments. - public void OnMessage(MessageEventArgs e) - { - Messaging.Instance.OnMessage(e); - } -#endif - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs deleted file mode 100644 index 3bd58c25..00000000 --- a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs +++ /dev/null @@ -1,85 +0,0 @@ -// 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 -{ - using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Core.WindowsInstaller.Decompile; - using WixToolset.Core.WindowsInstaller.Inscribe; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class MsiBackend : IBackend - { - public IBindResult Bind(IBindContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendBind(context); - } - - IBindResult result = null; - var dispose = true; - try - { - var command = new BindDatabaseCommand(context, backendExtensions, "darice.cub"); - result = command.Execute(); - - foreach (var extension in backendExtensions) - { - extension.PostBackendBind(result); - } - - dispose = false; - return result; - } - finally - { - if (dispose) - { - result?.Dispose(); - } - } - } - - public IDecompileResult Decompile(IDecompileContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendDecompile(context); - } - - var command = new DecompileMsiOrMsmCommand(context, backendExtensions); - var result = command.Execute(); - - foreach (var extension in backendExtensions) - { - extension.PostBackendDecompile(result); - } - - return result; - } - - public bool Inscribe(IInscribeContext context) - { - var command = new InscribeMsiPackageCommand(context); - return command.Execute(); - } - - public Intermediate Unbind(IUnbindContext context) - { - var command = new UnbindMsiOrMsmCommand(context); - return command.Execute(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs deleted file mode 100644 index 4927ee8c..00000000 --- a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs +++ /dev/null @@ -1,76 +0,0 @@ -// 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 -{ - using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Core.WindowsInstaller.Decompile; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class MsmBackend : IBackend - { - public IBindResult Bind(IBindContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendBind(context); - } - - IBindResult result = null; - try - { - var command = new BindDatabaseCommand(context, backendExtensions, "mergemod.cub"); - result = command.Execute(); - - foreach (var extension in backendExtensions) - { - extension.PostBackendBind(result); - } - - return result; - } - catch - { - result?.Dispose(); - throw; - } - } - - public IDecompileResult Decompile(IDecompileContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendDecompile(context); - } - - var command = new DecompileMsiOrMsmCommand(context, backendExtensions); - var result = command.Execute(); - - foreach (var extension in backendExtensions) - { - extension.PostBackendDecompile(result); - } - - return result; - } - - public bool Inscribe(IInscribeContext context) => false; - - public Intermediate Unbind(IUnbindContext context) - { - var command = new UnbindMsiOrMsmCommand(context); - return command.Execute(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs deleted file mode 100644 index c46b6027..00000000 --- a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs +++ /dev/null @@ -1,162 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class MspBackend : IBackend - { - public IBindResult Bind(IBindContext context) - { - var messaging = context.ServiceProvider.GetService(); - - var backendHelper = context.ServiceProvider.GetService(); - - var extensionManager = context.ServiceProvider.GetService(); - - var backendExtensions = extensionManager.GetServices(); - - foreach (var extension in backendExtensions) - { - extension.PreBackendBind(context); - } - - // Create transforms named in patch transforms. - IEnumerable patchTransforms; - { - var command = new CreatePatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, context.IntermediateFolder); - patchTransforms = command.Execute(); - } - - // Enhance the intermediate by attaching the created patch transforms. - IEnumerable subStorages; - { - var command = new AttachPatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, patchTransforms); - subStorages = command.Execute(); - } - - // Create WindowsInstallerData with patch metdata and transforms as sub-storages - // Create MSP from WindowsInstallerData - IBindResult result = null; - try - { - var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null); - result = command.Execute(); - - foreach (var extension in backendExtensions) - { - extension.PostBackendBind(result); - } - - return result; - } - catch - { - result?.Dispose(); - throw; - } - } - - public IDecompileResult Decompile(IDecompileContext context) => throw new NotImplementedException(); - - public bool Inscribe(IInscribeContext context) => throw new NotImplementedException(); - - public Intermediate Unbind(IUnbindContext context) - { -#if TODO_PATCHING - Output patch; - - // patch files are essentially database files (use a special flag to let the API know its a patch file) - try - { - using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) - { - var unbindCommand = new UnbindDatabaseCommand(context.Messaging, database, context.InputFilePath, OutputType.Patch, context.ExportBasePath, context.IntermediateFolder, context.IsAdminImage, context.SuppressDemodularization, skipSummaryInfo: false); - patch = unbindCommand.Execute(); - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(WixErrors.OpenDatabaseFailed(context.InputFilePath)); - } - - throw; - } - - // retrieve the transforms (they are in substorages) - using (Storage storage = Storage.Open(context.InputFilePath, StorageMode.Read | StorageMode.ShareDenyWrite)) - { - Table summaryInformationTable = patch.Tables["_SummaryInformation"]; - foreach (Row row in summaryInformationTable.Rows) - { - if (8 == (int)row[0]) // PID_LASTAUTHOR - { - string value = (string)row[1]; - - foreach (string decoratedSubStorageName in value.Split(';')) - { - string subStorageName = decoratedSubStorageName.Substring(1); - string transformFile = Path.Combine(context.IntermediateFolder, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst")); - - // ensure the parent directory exists - Directory.CreateDirectory(Path.GetDirectoryName(transformFile)); - - // copy the substorage to a new storage for the transform file - using (Storage subStorage = storage.OpenStorage(subStorageName)) - { - using (Storage transformStorage = Storage.CreateDocFile(transformFile, StorageMode.ReadWrite | StorageMode.ShareExclusive | StorageMode.Create)) - { - subStorage.CopyTo(transformStorage); - } - } - - // unbind the transform - var unbindCommand= new UnbindTransformCommand(context.Messaging, transformFile, (null == context.ExportBasePath ? null : Path.Combine(context.ExportBasePath, subStorageName)), context.IntermediateFolder); - var transform = unbindCommand.Execute(); - - patch.SubStorages.Add(new SubStorage(subStorageName, transform)); - } - - break; - } - } - } - - // extract the files from the cabinets - // TODO: use per-transform export paths for support of multi-product patches - if (null != context.ExportBasePath && !context.SuppressExtractCabinets) - { - using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) - { - foreach (SubStorage subStorage in patch.SubStorages) - { - // only patch transforms should carry files - if (subStorage.Name.StartsWith("#", StringComparison.Ordinal)) - { - var extractCommand = new ExtractCabinetsCommand(subStorage.Data, database, context.InputFilePath, context.ExportBasePath, context.IntermediateFolder); - extractCommand.Execute(); - } - } - } - } - - return patch; -#endif - throw new NotImplementedException(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/MstBackend.cs b/src/WixToolset.Core.WindowsInstaller/MstBackend.cs deleted file mode 100644 index a6d86c10..00000000 --- a/src/WixToolset.Core.WindowsInstaller/MstBackend.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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 -{ - using System; - using WixToolset.Core.WindowsInstaller.Unbind; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class MstBackend : IBackend - { - public IBindResult Bind(IBindContext context) - { -#if TODO_PATCHING - var command = new BindTransformCommand(); - command.Extensions = context.Extensions; - command.TempFilesLocation = context.IntermediateFolder; - command.Transform = context.IntermediateRepresentation; - command.OutputPath = context.OutputPath; - command.Execute(); - - return new BindResult(Array.Empty(), Array.Empty()); -#endif - throw new NotImplementedException(); - } - - public IDecompileResult Decompile(IDecompileContext context) - { - throw new NotImplementedException(); - } - - public bool Inscribe(IInscribeContext context) - { - throw new NotImplementedException(); - } - - public Intermediate Unbind(IUnbindContext context) - { - var command = new UnbindMsiOrMsmCommand(context); - return command.Execute(); - } - } -} \ No newline at end of file diff --git a/src/WixToolset.Core.WindowsInstaller/RowDictionary.cs b/src/WixToolset.Core.WindowsInstaller/RowDictionary.cs deleted file mode 100644 index ad7764bc..00000000 --- a/src/WixToolset.Core.WindowsInstaller/RowDictionary.cs +++ /dev/null @@ -1,71 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using WixToolset.Data.WindowsInstaller; - - /// - /// A dictionary of rows. Unlike the RowIndexedList this - /// will throw when multiple rows with the same key are added. - /// - internal sealed class RowDictionary : Dictionary where T : Row - { - /// - /// Creates an empty . - /// - public RowDictionary() - : base(StringComparer.InvariantCulture) - { - } - - /// - /// Creates and populates a with the rows from the given . - /// - /// The table to index. - /// - /// Rows added to the index are not automatically added to the given . - /// - public RowDictionary(Table table) - : this() - { - if (null != table) - { - foreach (T row in table.Rows) - { - this.Add(row); - } - } - } - - /// - /// Adds a row to the dictionary using the row key. - /// - /// Row to add to the dictionary. - public void Add(T row) - { - this.Add(row.GetKey(), row); - } - - /// - /// Gets the row by integer key. - /// - /// Integer key to look up. - /// Row or null if key is not found. - public T Get(int key) - { - return this.Get(key.ToString()); - } - - /// - /// Gets the row by string key. - /// - /// String key to look up. - /// Row or null if key is not found. - public T Get(string key) - { - return this.TryGetValue(key, out var result) ? result : null; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs deleted file mode 100644 index 8f52bed9..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs +++ /dev/null @@ -1,147 +0,0 @@ -// 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.Unbind -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Core.Native; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - - internal class ExtractCabinetsCommand - { - public ExtractCabinetsCommand(WindowsInstallerData output, Database database, string inputFilePath, string exportBasePath, string intermediateFolder, bool treatOutputAsModule = false) - { - this.Output = output; - this.Database = database; - this.InputFilePath = inputFilePath; - this.ExportBasePath = exportBasePath; - this.IntermediateFolder = intermediateFolder; - this.TreatOutputAsModule = treatOutputAsModule; - } - - public string[] ExtractedFiles { get; private set; } - - private WindowsInstallerData Output { get; } - - private Database Database { get; } - - private string InputFilePath { get; } - - private string ExportBasePath { get; } - - private string IntermediateFolder { get; } - - public bool TreatOutputAsModule { get; } - - public void Execute() - { - var databaseBasePath = Path.GetDirectoryName(this.InputFilePath); - var cabinetFiles = new List(); - var embeddedCabinets = new SortedList(); - - // index all of the cabinet files - if (OutputType.Module == this.Output.Type || this.TreatOutputAsModule) - { - embeddedCabinets.Add(0, "MergeModule.CABinet"); - } - else if (null != this.Output.Tables["Media"]) - { - foreach (MediaRow mediaRow in this.Output.Tables["Media"].Rows) - { - if (null != mediaRow.Cabinet) - { - if (OutputType.Product == this.Output.Type || - (OutputType.Transform == this.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 (var streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) - { - foreach (int diskId in embeddedCabinets.Keys) - { - using (var record = new Record(1)) - { - record.SetString(1, (string)embeddedCabinets[diskId]); - streamsView.Execute(record); - } - - using (var record = streamsView.Fetch()) - { - if (null != record) - { - // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive, - // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work - var cabinetFile = Path.Combine(this.IntermediateFolder, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); - - // ensure the parent directory exists - Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); - - using (var fs = File.Create(cabinetFile)) - { - int bytesRead; - var 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) - { - // ensure the directory exists or extraction will fail - Directory.CreateDirectory(this.ExportBasePath); - - foreach (var cabinetFile in cabinetFiles) - { - try - { - var cabinet = new Cabinet(cabinetFile); - this.ExtractedFiles = cabinet.Extract(this.ExportBasePath).ToArray(); - } - catch (FileNotFoundException) - { - throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetFile)); - } - } - } - else - { - this.ExtractedFiles = new string[0]; - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs deleted file mode 100644 index b510690e..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs +++ /dev/null @@ -1,789 +0,0 @@ -// 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.Unbind -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Text.RegularExpressions; - using WixToolset.Core.Native.Msi; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class UnbindDatabaseCommand - { - private List exportedFiles; - - public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, Database database, string databasePath, OutputType outputType, string exportBasePath, string intermediateFolder, bool isAdminImage, bool suppressDemodularization, bool skipSummaryInfo) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.Database = database; - this.DatabasePath = databasePath; - this.OutputType = outputType; - this.ExportBasePath = exportBasePath; - this.IntermediateFolder = intermediateFolder; - this.IsAdminImage = isAdminImage; - this.SuppressDemodularization = suppressDemodularization; - this.SkipSummaryInfo = skipSummaryInfo; - - this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); - } - - public IMessaging Messaging { get; } - - public IBackendHelper BackendHelper { get; } - - public Database Database { get; } - - public string DatabasePath { get; } - - public OutputType OutputType { get; } - - public string ExportBasePath { get; } - - public string IntermediateFolder { get; } - - public bool IsAdminImage { get; } - - public bool SuppressDemodularization { get; } - - public bool SkipSummaryInfo { get; } - - public TableDefinitionCollection TableDefinitions { get; } - - public IEnumerable ExportedFiles => this.exportedFiles; - - private int SectionCount { get; set; } - - public WindowsInstallerData Execute() - { - this.exportedFiles = new List(); - - string modularizationGuid = null; - var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath)); - View validationView = null; - - // set the output type - output.Type = this.OutputType; - - Directory.CreateDirectory(this.IntermediateFolder); - - // get the codepage - this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt"); - using (var sr = File.OpenText(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt"))) - { - string line; - - while (null != (line = sr.ReadLine())) - { - var 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 (!this.SkipSummaryInfo) - { - using (var summaryInformation = new SummaryInformation(this.Database)) - { - var table = new Table(this.TableDefinitions["_SummaryInformation"]); - - for (var i = 1; 19 >= i; i++) - { - var value = summaryInformation.GetProperty(i); - - if (0 < value.Length) - { - var 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 (this.Database.TableExists("_Validation")) - { - validationView = this.Database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); - } - - // get the normal tables - using (var tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables")) - { - foreach (var tableRecord in tablesView.Records) - { - var tableName = tableRecord.GetString(1); - - using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) - { - var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView); - var table = new Table(tableDefinition); - - foreach (var rowRecord in tableView.Records) - { - var recordCount = rowRecord.GetFieldCount(); - var row = table.CreateRow(output.SourceLineNumbers); - - for (var i = 0; recordCount > i && row.Fields.Length > i; i++) - { - if (rowRecord.IsNull(i + 1)) - { - if (!row.Fields[i].Column.Nullable) - { - // 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: - var success = false; - var 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.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); - } - break; - case ColumnType.Object: - var sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; - - if (null != this.ExportBasePath) - { - var relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); - sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile); - - // ensure the parent directory exists - System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName)); - - using (var fs = System.IO.File.Create(sourceFile)) - { - int bytesRead; - var buffer = new byte[512]; - - while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) - { - fs.Write(buffer, 0, bytesRead); - } - } - - this.exportedFiles.Add(sourceFile); - } - - row[i] = sourceFile; - break; - default: - var 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) - { - var 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) - { - var 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 - // TODO: Evaluate this requirement. - //var matches = Common.WixVariableRegex.Matches(value); - //for (var 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) - { - var table = output.Tables["_SummaryInformation"]; - - foreach (var row in table.Rows) - { - if (9 == (int)row[0]) // PID_REVNUMBER - { - row[1] = modularizationGuid; - } - } - } - - if (this.IsAdminImage) - { - this.GenerateWixFileTable(this.DatabasePath, output); - this.GenerateSectionIds(output); - } - - return output; - } - - private TableDefinition GetTableDefinition(string tableName, View tableView, View validationView) - { - // Use our table definitions whenever possible since they will be used when compiling the source code anyway. - // This also allows us to take advantage of WiX concepts like localizable columns which current code assumes. - if (this.TableDefinitions.Contains(tableName)) - { - return this.TableDefinitions[tableName]; - } - - ColumnDefinition[] columns; - using (Record columnNameRecord = tableView.GetColumnNames(), - columnTypeRecord = tableView.GetColumnTypes()) - { - // index the primary keys - var tablePrimaryKeys = new HashSet(); - using (var primaryKeysRecord = this.Database.PrimaryKeys(tableName)) - { - var primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); - - for (var i = 1; i <= primaryKeysFieldCount; i++) - { - tablePrimaryKeys.Add(primaryKeysRecord.GetString(i)); - } - } - - var columnCount = columnNameRecord.GetFieldCount(); - columns = new ColumnDefinition[columnCount]; - for (var i = 1; i <= columnCount; i++) - { - var columnName = columnNameRecord.GetString(i); - var idtType = columnTypeRecord.GetString(i); - - ColumnType columnType; - int length; - bool nullable; - - var columnCategory = ColumnCategory.Unknown; - var columnModularizeType = ColumnModularizeType.None; - var primary = tablePrimaryKeys.Contains(columnName); - int? minValue = null; - int? maxValue = null; - string keyTable = null; - int? keyColumn = null; - 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 (var validationRecord = new Record(2)) - { - validationRecord.SetString(1, tableName); - validationRecord.SetString(2, columnName); - - validationView.Execute(validationRecord); - } - - using (var validationRecord = validationView.Fetch()) - { - if (null != validationRecord) - { - var validationNullable = validationRecord.GetString(3); - minValue = validationRecord.IsNull(4) ? null : (int?)validationRecord.GetInteger(4); - maxValue = validationRecord.IsNull(5) ? null : (int?)validationRecord.GetInteger(5); - keyTable = validationRecord.IsNull(6) ? null : validationRecord.GetString(6); - keyColumn = validationRecord.IsNull(7) ? null : (int?)validationRecord.GetInteger(7); - category = validationRecord.IsNull(8) ? null : validationRecord.GetString(8); - set = validationRecord.IsNull(9) ? null : validationRecord.GetString(9); - description = validationRecord.IsNull(10) ? null : validationRecord.GetString(10); - - // 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; - } - - columns[i - 1] = new ColumnDefinition(columnName, columnType, length, primary, nullable, columnCategory, minValue, maxValue, keyTable, keyColumn, set, description, columnModularizeType, (ColumnType.Localized == columnType), true); - } - } - - return new TableDefinition(tableName, null, columns, false); - } - - /// - /// Generates the WixFile table based on a path to an admin image msi and an Output. - /// - /// The path to the msi database file in an admin image. - /// The Output that represents the msi database. - private void GenerateWixFileTable(string databaseFile, WindowsInstallerData output) - { - throw new NotImplementedException(); -#if TODO_FIX_UNBINDING_FILES - var adminRootPath = Path.GetDirectoryName(databaseFile); - - var componentDirectoryIndex = new Hashtable(); - var componentTable = output.Tables["Component"]; - foreach (var row in componentTable.Rows) - { - componentDirectoryIndex.Add(row[0], row[2]); - } - - // Index full source paths for all directories - var directoryDirectoryParentIndex = new Hashtable(); - var directoryFullPathIndex = new Hashtable(); - var directorySourceNameIndex = new Hashtable(); - var directoryTable = output.Tables["Directory"]; - foreach (var row in directoryTable.Rows) - { - directoryDirectoryParentIndex.Add(row[0], row[1]); - if (null == row[1]) - { - directoryFullPathIndex.Add(row[0], adminRootPath); - } - else - { - directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2])); - } - } - - foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) - { - if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) - { - this.GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); - } - } - - var fileTable = output.Tables["File"]; - var wixFileTable = output.EnsureTable(this.TableDefinitions["WixFile"]); - foreach (var row in fileTable.Rows) - { - var wixFileRow = new WixFileRow(null, this.TableDefinitions["WixFile"]); - wixFileRow.File = (string)row[0]; - wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]]; - wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2])); - - if (!File.Exists(wixFileRow.Source)) - { - throw new WixException(ErrorMessages.WixFileNotFound(wixFileRow.Source)); - } - - wixFileTable.Rows.Add(wixFileRow); - } -#endif - } - - /// - /// Gets the full path of a directory. Populates the full path index with the directory's full path and all of its parent directorie's full paths. - /// - /// The directory identifier. - /// The Hashtable containing all the directory to directory parent mapping. - /// The Hashtable containing all the directory to source name mapping. - /// The Hashtable containing a mapping between all of the directories and their previously calculated full paths. - /// The full path to the directory. - private string GetAdminFullPath(string directory, Hashtable directoryDirectoryParentIndex, Hashtable directorySourceNameIndex, Hashtable directoryFullPathIndex) - { - var parent = (string)directoryDirectoryParentIndex[directory]; - var sourceName = (string)directorySourceNameIndex[directory]; - - string parentFullPath; - if (directoryFullPathIndex.ContainsKey(parent)) - { - parentFullPath = (string)directoryFullPathIndex[parent]; - } - else - { - parentFullPath = this.GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); - } - - if (null == sourceName) - { - sourceName = String.Empty; - } - - var fullPath = Path.Combine(parentFullPath, sourceName); - directoryFullPathIndex.Add(directory, fullPath); - - return fullPath; - } - - /// - /// Get the source name in an admin image. - /// - /// The Filename value. - /// The source name of the directory in an admin image. - private string GetAdminSourceName(string value) - { - string name = null; - string[] names; - string shortname = null; - string shortsourcename = null; - string sourcename = null; - - names = this.BackendHelper.SplitMsiFileName(value); - - if (null != names[0] && "." != names[0]) - { - if (null != names[1]) - { - shortname = names[0]; - } - else - { - name = names[0]; - } - } - - if (null != names[1]) - { - name = names[1]; - } - - if (null != names[2]) - { - if (null != names[3]) - { - shortsourcename = names[2]; - } - else - { - sourcename = names[2]; - } - } - - if (null != names[3]) - { - sourcename = names[3]; - } - - if (null != sourcename) - { - return sourcename; - } - else if (null != shortsourcename) - { - return shortsourcename; - } - else if (null != name) - { - return name; - } - else - { - return shortname; - } - } - - /// - /// Creates section ids on rows which form logical groupings of resources. - /// - /// The Output that represents the msi database. - private void GenerateSectionIds(WindowsInstallerData output) - { - // First assign and index section ids for the tables that are in their own sections. - this.AssignSectionIdsToTable(output.Tables["Binary"], 0); - var componentSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Component"], 0); - var customActionSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["CustomAction"], 0); - this.AssignSectionIdsToTable(output.Tables["Directory"], 0); - var featureSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Feature"], 0); - this.AssignSectionIdsToTable(output.Tables["Icon"], 0); - var digitalCertificateSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0); - this.AssignSectionIdsToTable(output.Tables["Property"], 0); - - // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here. - var fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0); - var appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5); - var odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0); - var odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0); - var registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0); - var serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0); - - // Now handle all the tables which only rely on previous indexes and order does not matter. - foreach (var table in output.Tables) - { - switch (table.Name) - { - case "WixFile": - case "MsiFileHash": - ConnectTableToSection(table, fileSectionIdIndex, 0); - break; - case "MsiAssembly": - case "MsiAssemblyName": - ConnectTableToSection(table, componentSectionIdIndex, 0); - break; - case "MsiPackageCertificate": - case "MsiPatchCertificate": - ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1); - break; - case "CreateFolder": - case "FeatureComponents": - case "MoveFile": - case "ReserveCost": - case "ODBCTranslator": - ConnectTableToSection(table, componentSectionIdIndex, 1); - break; - case "TypeLib": - ConnectTableToSection(table, componentSectionIdIndex, 2); - break; - case "Shortcut": - case "Environment": - ConnectTableToSection(table, componentSectionIdIndex, 3); - break; - case "RemoveRegistry": - ConnectTableToSection(table, componentSectionIdIndex, 4); - break; - case "ServiceControl": - ConnectTableToSection(table, componentSectionIdIndex, 5); - break; - case "IniFile": - case "RemoveIniFile": - ConnectTableToSection(table, componentSectionIdIndex, 7); - break; - case "AppId": - ConnectTableToSection(table, appIdSectionIdIndex, 0); - break; - case "Condition": - ConnectTableToSection(table, featureSectionIdIndex, 0); - break; - case "ODBCSourceAttribute": - ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0); - break; - case "ODBCAttribute": - ConnectTableToSection(table, odbcDriverSectionIdIndex, 0); - break; - case "AdminExecuteSequence": - case "AdminUISequence": - case "AdvtExecuteSequence": - case "AdvtUISequence": - case "InstallExecuteSequence": - case "InstallUISequence": - ConnectTableToSection(table, customActionSectionIdIndex, 0); - break; - case "LockPermissions": - case "MsiLockPermissions": - foreach (var row in table.Rows) - { - var lockObject = (string)row[0]; - var tableName = (string)row[1]; - switch (tableName) - { - case "File": - row.SectionId = (string)fileSectionIdIndex[lockObject]; - break; - case "Registry": - row.SectionId = (string)registrySectionIdIndex[lockObject]; - break; - case "ServiceInstall": - row.SectionId = (string)serviceInstallSectionIdIndex[lockObject]; - break; - } - } - break; - } - } - - // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids. - //foreach (IUnbinderExtension extension in this.unbinderExtensions) - //{ - // extension.GenerateSectionIds(output); - //} - } - - /// - /// Creates new section ids on all the rows in a table. - /// - /// The table to add sections to. - /// The index of the column which is used by other tables to reference this table. - /// A Hashtable containing the tables key for each row paired with its assigned section id. - private Hashtable AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex) - { - var hashtable = new Hashtable(); - if (null != table) - { - foreach (var row in table.Rows) - { - row.SectionId = this.GetNewSectionId(); - hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId); - } - } - return hashtable; - } - - /// - /// Connects a table's rows to an already sectioned table. - /// - /// The table containing rows that need to be connected to sections. - /// A hashtable containing keys to map table to its section. - /// The index of the column which is used as the foreign key in to the sectionIdIndex. - private static void ConnectTableToSection(Table table, Hashtable sectionIdIndex, int rowIndex) - { - if (null != table) - { - foreach (var row in table.Rows) - { - if (sectionIdIndex.ContainsKey(row[rowIndex])) - { - row.SectionId = (string)sectionIdIndex[row[rowIndex]]; - } - } - } - } - - /// - /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it. - /// - /// The table containing rows that need to be connected to sections. - /// A hashtable containing keys to map table to its section. - /// The index of the column which is used as the foreign key in to the sectionIdIndex. - /// The index of the column which is used by other tables to reference this table. - /// A Hashtable containing the tables key for each row paired with its assigned section id. - private static Hashtable ConnectTableToSectionAndIndex(Table table, Hashtable sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex) - { - var newHashTable = new Hashtable(); - if (null != table) - { - foreach (var row in table.Rows) - { - if (!sectionIdIndex.ContainsKey(row[rowIndex])) - { - continue; - } - - row.SectionId = (string)sectionIdIndex[row[rowIndex]]; - if (null != row[rowPrimaryKeyIndex]) - { - newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId); - } - } - } - return newHashTable; - } - - /// - /// Creates a new section identifier to be used when adding a section to an output. - /// - /// A string representing a new section id. - private string GetNewSectionId() - { - this.SectionCount++; - return "wix.section." + this.SectionCount.ToString(CultureInfo.InvariantCulture); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs deleted file mode 100644 index 75ee6307..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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.Unbind -{ - using System; - using System.ComponentModel; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Core.Native.Msi; - - internal class UnbindMsiOrMsmCommand - { - public UnbindMsiOrMsmCommand(IUnbindContext context) - { - this.Context = context; - } - - public IUnbindContext Context { get; } - - public Intermediate Execute() - { -#if TODO_PATCHING - Output output; - - try - { - using (Database database = new Database(this.Context.InputFilePath, OpenDatabase.ReadOnly)) - { - var unbindCommand = new UnbindDatabaseCommand(this.Context.Messaging, database, this.Context.InputFilePath, OutputType.Product, this.Context.ExportBasePath, this.Context.IntermediateFolder, this.Context.IsAdminImage, this.Context.SuppressDemodularization, skipSummaryInfo: false); - output = unbindCommand.Execute(); - - // extract the files from the cabinets - if (!String.IsNullOrEmpty(this.Context.ExportBasePath) && !this.Context.SuppressExtractCabinets) - { - var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.InputFilePath, this.Context.ExportBasePath, this.Context.IntermediateFolder); - extractCommand.Execute(); - } - } - } - catch (Win32Exception e) - { - if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED - { - throw new WixException(WixErrors.OpenDatabaseFailed(this.Context.InputFilePath)); - } - - throw; - } - - return output; -#endif - throw new NotImplementedException(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs deleted file mode 100644 index f40aed4e..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs +++ /dev/null @@ -1,309 +0,0 @@ -// 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.Unbind -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.ComponentModel; - using System.Globalization; - using System.IO; - using System.Linq; - using WixToolset.Core.Native.Msi; - using WixToolset.Core.WindowsInstaller.Bind; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Services; - - internal class UnbindTransformCommand - { - public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, string transformFile, string exportBasePath, string intermediateFolder) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - this.TransformFile = transformFile; - this.ExportBasePath = exportBasePath; - this.IntermediateFolder = intermediateFolder; - - this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private string TransformFile { get; } - - private string ExportBasePath { get; } - - private string IntermediateFolder { get; } - - private TableDefinitionCollection TableDefinitions { get; } - - private string EmptyFile { get; set; } - - public WindowsInstallerData Execute() - { - var transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile)); - transform.Type = OutputType.Transform; - - // get the summary information table - using (var summaryInformation = new SummaryInformation(this.TransformFile)) - { - var table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); - - for (var i = 1; 19 >= i; i++) - { - var value = summaryInformation.GetProperty(i); - - if (0 < value.Length) - { - var row = table.CreateRow(transform.SourceLineNumbers); - row[0] = i; - row[1] = value; - } - } - } - - // create a schema msi which hopefully matches the table schemas in the transform - var schemaOutput = new WindowsInstallerData(null); - var msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi"); - foreach (var tableDefinition in this.TableDefinitions) - { - // skip unreal tables and the Patch table - if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name) - { - schemaOutput.EnsureTable(tableDefinition); - } - } - - var addedRows = new Dictionary(); - Table transformViewTable; - - // Bind the schema msi. - this.GenerateDatabase(schemaOutput, msiDatabaseFile); - - // apply the transform to the database and retrieve the modifications - using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) - { - // apply the transform with the ViewTransform option to collect all the modifications - msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); - - // unbind the database - var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); - var transformViewOutput = unbindCommand.Execute(); - - // index the added and possibly modified rows (added rows may also appears as modified rows) - transformViewTable = transformViewOutput.Tables["_TransformView"]; - var modifiedRows = new Hashtable(); - foreach (var row in transformViewTable.Rows) - { - var tableName = (string)row[0]; - var columnName = (string)row[1]; - var primaryKeys = (string)row[2]; - - if ("INSERT" == columnName) - { - var index = String.Concat(tableName, ':', primaryKeys); - - addedRows.Add(index, null); - } - else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row - { - var index = String.Concat(tableName, ':', primaryKeys); - - modifiedRows[index] = row; - } - } - - // create placeholder rows for modified rows to make the transform insert the updated values when its applied - foreach (Row row in modifiedRows.Values) - { - var tableName = (string)row[0]; - var columnName = (string)row[1]; - var primaryKeys = (string)row[2]; - - var index = String.Concat(tableName, ':', primaryKeys); - - // ignore information for added rows - if (!addedRows.ContainsKey(index)) - { - var table = schemaOutput.Tables[tableName]; - this.CreateRow(table, primaryKeys, true); - } - } - } - - // Re-bind the schema output with the placeholder rows. - this.GenerateDatabase(schemaOutput, msiDatabaseFile); - - // apply the transform to the database and retrieve the modifications - using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) - { - try - { - // apply the transform - msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All); - - // commit the database to guard against weird errors with streams - msiDatabase.Commit(); - } - catch (Win32Exception ex) - { - if (0x65B == ex.NativeErrorCode) - { - // this commonly happens when the transform was built - // against a database schema different from the internal - // table definitions - throw new WixException(ErrorMessages.TransformSchemaMismatch()); - } - } - - // unbind the database - var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); - var output = unbindCommand.Execute(); - - // index all the rows to easily find modified rows - var rows = new Dictionary(); - foreach (var table in output.Tables) - { - foreach (var row in table.Rows) - { - rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); - } - } - - // process the _TransformView rows into transform rows - foreach (var row in transformViewTable.Rows) - { - var tableName = (string)row[0]; - var columnName = (string)row[1]; - var primaryKeys = (string)row[2]; - - var table = transform.EnsureTable(this.TableDefinitions[tableName]); - - if ("CREATE" == columnName) // added table - { - table.Operation = TableOperation.Add; - } - else if ("DELETE" == columnName) // deleted row - { - var deletedRow = this.CreateRow(table, primaryKeys, false); - deletedRow.Operation = RowOperation.Delete; - } - else if ("DROP" == columnName) // dropped table - { - table.Operation = TableOperation.Drop; - } - else if ("INSERT" == columnName) // added row - { - var index = String.Concat(tableName, ':', primaryKeys); - var addedRow = rows[index]; - addedRow.Operation = RowOperation.Add; - table.Rows.Add(addedRow); - } - else if (null != primaryKeys) // modified row - { - var index = String.Concat(tableName, ':', primaryKeys); - - // the _TransformView table includes information for added rows - // that looks like modified rows so it sometimes needs to be ignored - if (!addedRows.ContainsKey(index)) - { - var modifiedRow = rows[index]; - - // mark the field as modified - var indexOfModifiedValue = -1; - for (var i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++i) - { - if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal)) - { - indexOfModifiedValue = i; - break; - } - } - modifiedRow.Fields[indexOfModifiedValue].Modified = true; - - // move the modified row into the transform the first time its encountered - if (RowOperation.None == modifiedRow.Operation) - { - modifiedRow.Operation = RowOperation.Modify; - table.Rows.Add(modifiedRow); - } - } - } - else // added column - { - var column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal)); - column.Added = true; - } - } - } - - return transform; - } - - /// - /// Create a deleted or modified row. - /// - /// The table containing the row. - /// The primary keys of the row. - /// Option to set all required fields with placeholder values. - /// The new row. - private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields) - { - var row = table.CreateRow(null); - - var primaryKeyParts = primaryKeys.Split('\t'); - var primaryKeyPartIndex = 0; - - for (var i = 0; i < table.Definition.Columns.Length; i++) - { - var columnDefinition = table.Definition.Columns[i]; - - if (columnDefinition.PrimaryKey) - { - if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) - { - row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture); - } - else - { - row[i] = primaryKeyParts[primaryKeyPartIndex++]; - } - } - else if (setRequiredFields) - { - if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) - { - row[i] = 1; - } - else if (ColumnType.Object == columnDefinition.Type) - { - if (null == this.EmptyFile) - { - this.EmptyFile = Path.Combine(this.IntermediateFolder, ".empty"); - using (var fileStream = File.Create(this.EmptyFile)) - { - } - } - - row[i] = this.EmptyFile; - } - else - { - row[i] = "1"; - } - } - } - - return row; - } - - private void GenerateDatabase(WindowsInstallerData output, string databaseFile) - { - var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); - command.Execute(); - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs deleted file mode 100644 index 0c15ad05..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 -{ - using WixToolset.Data; - - internal static class WindowsInstallerBackendErrors - { - //public static Message ReplaceThisWithTheFirstError(SourceLineNumber sourceLineNumbers) - //{ - // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstError, "format string", arg1, arg2); - //} - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); - } - - public enum Ids - { - // ReplaceThisWithTheFirstError = 7500, - } // last available is 7999. 8000 is BurnBackendErrors. - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs deleted file mode 100644 index f72acb21..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 -{ - using System; - using System.IO; - using WixToolset.Extensibility; - - internal class WindowsInstallerBackendFactory : IBackendFactory - { - public bool TryCreateBackend(string outputType, string outputFile, out IBackend backend) - { - if (String.IsNullOrEmpty(outputType)) - { - outputType = Path.GetExtension(outputFile); - } - - switch (outputType?.ToLowerInvariant()) - { - case "module": - case ".msm": - backend = new MsmBackend(); - return true; - - case "msipackage": - case "package": - case "product": - case ".msi": - backend = new MsiBackend(); - return true; - - case "patch": - case ".msp": - backend = new MspBackend(); - return true; - - //case "patchcreation": - //case ".pcp": - // return new PatchCreationBackend(); - - case "transform": - case ".mst": - backend = new MstBackend(); - return true; - } - - backend = null; - return false; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs deleted file mode 100644 index d0986a4d..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 -{ - using WixToolset.Data; - - internal static class WindowsInstallerBackendWarnings - { - //public static Message ReplaceThisWithTheFirstWarning(SourceLineNumber sourceLineNumbers) - //{ - // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstWarning, "format string", arg1, arg2); - //} - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); - } - - public enum Ids - { - // ReplaceThisWithTheFirstWarning = 7100, - } // last available is 7499. 7500 is WindowsInstallerBackendErrors. - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs deleted file mode 100644 index 7b12fc8c..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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 -{ - using System; - using WixToolset.Extensibility; - - internal class WindowsInstallerExtensionFactory : IExtensionFactory - { - public bool TryCreateExtension(Type extensionType, out object extension) - { - extension = null; - - if (extensionType == typeof(IBackendFactory)) - { - extension = new WindowsInstallerBackendFactory(); - } - - return extension != null; - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj b/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj deleted file mode 100644 index b08f337f..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - netstandard2.0 - $(TargetFrameworks);net461;net472 - Core Windows Installer - WiX Toolset Core Windows Installer - embedded - true - true - - - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs b/src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs deleted file mode 100644 index e686fa49..00000000 --- a/src/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using WixToolset.Core.WindowsInstaller.ExtensibilityServices; - using WixToolset.Extensibility.Services; - - /// - /// Extensions methods for adding WindowsInstaller services. - /// - public static class WixToolsetCoreServiceProviderExtensions - { - /// - /// Adds WindowsInstaller services. - /// - /// - /// - public static IWixToolsetCoreServiceProvider AddWindowsInstallerBackend(this IWixToolsetCoreServiceProvider coreProvider) - { - AddServices(coreProvider); - - var extensionManager = coreProvider.GetService(); - extensionManager.Add(typeof(WindowsInstallerExtensionFactory).Assembly); - - return coreProvider; - } - - private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) - { - // Singletons. - coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new WindowsInstallerBackendHelper(provider))); - } - - private static T AddSingleton(Dictionary singletons, T service) where T : class - { - singletons.Add(typeof(T), service); - return service; - } - } -} diff --git a/src/WixToolset.Core/Bind/DelayedField.cs b/src/WixToolset.Core/Bind/DelayedField.cs deleted file mode 100644 index 25641516..00000000 --- a/src/WixToolset.Core/Bind/DelayedField.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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.Bind -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - /// - /// Holds a symbol and field that contain binder variables, which need to be resolved - /// later, once the files have been resolved. - /// - internal class DelayedField : IDelayedField - { - /// - /// Creates a delayed field. - /// - /// Symbol for the field. - /// Field needing further resolution. - public DelayedField(IntermediateSymbol symbol, IntermediateField field) - { - this.Symbol = symbol; - this.Field = field; - } - - /// - /// The row containing the field. - /// - public IntermediateSymbol Symbol { get; } - - /// - /// The field needing further resolving. - /// - public IntermediateField Field { get; } - } -} diff --git a/src/WixToolset.Core/Bind/ExpectedExtractFile.cs b/src/WixToolset.Core/Bind/ExpectedExtractFile.cs deleted file mode 100644 index b27cdfee..00000000 --- a/src/WixToolset.Core/Bind/ExpectedExtractFile.cs +++ /dev/null @@ -1,16 +0,0 @@ -// 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.Bind -{ - using System; - using WixToolset.Extensibility.Data; - - internal class ExpectedExtractFile : IExpectedExtractFile - { - public Uri Uri { get; set; } - - public string EmbeddedFileId { get; set; } - - public string OutputPath { get; set; } - } -} diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs deleted file mode 100644 index a0798e62..00000000 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs +++ /dev/null @@ -1,92 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - - /// - /// Internal helper class used to extract embedded files. - /// - internal class ExtractEmbeddedFiles - { - private readonly Dictionary> filesWithEmbeddedFiles = new Dictionary>(); - - public IEnumerable Uris => this.filesWithEmbeddedFiles.Keys; - - /// - /// Adds an embedded file index to track and returns the path where the embedded file will be extracted. Duplicates will return the same extract path. - /// - /// Uri to file containing the embedded files. - /// Id of the embedded file to extract. - /// Folder where extracted files should be placed. - /// The extract path for the embedded file. - public string AddEmbeddedFileToExtract(Uri uri, string embeddedFileId, string extractFolder) - { - // If the uri to the file that contains the embedded file does not already have embedded files - // being extracted, create the dictionary to track that. - if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) - { - extracts = new SortedList(StringComparer.OrdinalIgnoreCase); - this.filesWithEmbeddedFiles.Add(uri, extracts); - } - - // If the embedded file is not already tracked in the dictionary of extracts, add it. - if (!extracts.TryGetValue(embeddedFileId, out var extractPath)) - { - var localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath); - var unique = this.HashUri(uri.AbsoluteUri); - var extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileId); - - extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName)); - extracts.Add(embeddedFileId, extractPath); - } - - return extractPath; - } - - public IReadOnlyList GetExpectedEmbeddedFiles() - { - var files = new List(); - - foreach (var uriWithExtracts in this.filesWithEmbeddedFiles) - { - foreach (var extracts in uriWithExtracts.Value) - { - files.Add(new ExpectedExtractFile - { - Uri = uriWithExtracts.Key, - EmbeddedFileId = extracts.Key, - OutputPath = extracts.Value, - }); - } - } - - return files; - } - - public IEnumerable GetExtractFilesForUri(Uri uri) - { - if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) - { - extracts = new SortedList(StringComparer.OrdinalIgnoreCase); - } - - return extracts.Select(e => new ExpectedExtractFile { Uri = uri, EmbeddedFileId = e.Key, OutputPath = e.Value }); - } - - private string HashUri(string uri) - { - using (SHA1 sha1 = new SHA1CryptoServiceProvider()) - { - var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(uri)); - return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); - } - } - } -} diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs deleted file mode 100644 index ec2d8896..00000000 --- a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs +++ /dev/null @@ -1,53 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ExtractEmbeddedFilesCommand - { - public ExtractEmbeddedFilesCommand(IBackendHelper backendHelper, IEnumerable embeddedFiles) - { - this.BackendHelper = backendHelper; - this.FilesWithEmbeddedFiles = embeddedFiles; - } - - public IReadOnlyList TrackedFiles { get; private set; } - - private IBackendHelper BackendHelper { get; } - - private IEnumerable FilesWithEmbeddedFiles { get; } - - public void Execute() - { - var trackedFiles = new List(); - var group = this.FilesWithEmbeddedFiles.GroupBy(e => e.Uri); - - foreach (var expectedEmbeddedFileByUri in group) - { - var baseUri = expectedEmbeddedFileByUri.Key; - - using (var wixout = WixOutput.Read(baseUri)) - { - var uniqueIds = new SortedSet(StringComparer.OrdinalIgnoreCase); - - foreach (var embeddedFile in expectedEmbeddedFileByUri) - { - if (uniqueIds.Add(embeddedFile.EmbeddedFileId)) - { - wixout.ExtractEmbeddedFile(embeddedFile.EmbeddedFileId, embeddedFile.OutputPath); - trackedFiles.Add(this.BackendHelper.TrackFile(embeddedFile.OutputPath, TrackedFileType.Temporary)); - } - } - } - } - - this.TrackedFiles = trackedFiles; - } - } -} diff --git a/src/WixToolset.Core/Bind/FileResolver.cs b/src/WixToolset.Core/Bind/FileResolver.cs deleted file mode 100644 index eb878239..00000000 --- a/src/WixToolset.Core/Bind/FileResolver.cs +++ /dev/null @@ -1,207 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class FileResolver - { - private const string BindPathOpenString = "!(bindpath."; - - private FileResolver(IEnumerable bindPaths) - { - this.BindPaths = (bindPaths ?? Array.Empty()).ToLookup(b => b.Stage); - this.RebaseTarget = this.BindPaths[BindStage.Target].Any(); - this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any(); - } - - public FileResolver(IEnumerable bindPaths, IEnumerable extensions) : this(bindPaths) - { - this.ResolverExtensions = extensions ?? Array.Empty(); - } - - public FileResolver(IEnumerable bindPaths, IEnumerable extensions) : this(bindPaths) - { - this.LibrarianExtensions = extensions ?? Array.Empty(); - } - - private ILookup BindPaths { get; } - - public bool RebaseTarget { get; } - - public bool RebaseUpdated { get; } - - private IEnumerable ResolverExtensions { get; } - - private IEnumerable LibrarianExtensions { get; } - - public string Resolve(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string source) - { - var checkedPaths = new List(); - - foreach (var extension in this.LibrarianExtensions) - { - var resolved = extension.ResolveFile(sourceLineNumbers, symbolDefinition, source); - - if (resolved?.CheckedPaths != null) - { - checkedPaths.AddRange(resolved.CheckedPaths); - } - - if (!String.IsNullOrEmpty(resolved?.Path)) - { - return resolved?.Path; - } - } - - return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, BindStage.Normal, checkedPaths); - } - - /// - /// Resolves the source path of a file using binder extensions. - /// - /// Original source value. - /// Optional type of source file being resolved. - /// Optional source line of source file being resolved. - /// The binding stage used to determine what collection of bind paths will be used - /// Optional collection of paths already checked. - /// Should return a valid path for the stream to be imported. - public string ResolveFile(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, IEnumerable alreadyCheckedPaths = null) - { - var checkedPaths = new List(); - - if (alreadyCheckedPaths != null) - { - checkedPaths.AddRange(alreadyCheckedPaths); - } - - foreach (var extension in this.ResolverExtensions) - { - var resolved = extension.ResolveFile(source, symbolDefinition, sourceLineNumbers, bindStage); - - if (resolved?.CheckedPaths != null) - { - checkedPaths.AddRange(resolved.CheckedPaths); - } - - if (!String.IsNullOrEmpty(resolved?.Path)) - { - return resolved?.Path; - } - } - - return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindStage, checkedPaths); - } - - private string MustResolveUsingBindPaths(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, List checkedPaths) - { - string resolved = null; - - // If the file exists, we're good to go. - checkedPaths.Add(source); - if (CheckFileExists(source)) - { - resolved = source; - } - else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist. - { - resolved = null; - } - else // not a rooted path so let's try applying all the different source resolution options. - { - var bindName = String.Empty; - var path = source; - var pathWithoutSourceDir = String.Empty; - - if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal)) - { - var closeParen = source.IndexOf(')', BindPathOpenString.Length); - - if (-1 != closeParen) - { - bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length); - path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace. - path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted. - } - } - else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal)) - { - pathWithoutSourceDir = path.Substring(10); - } - - var bindPaths = this.BindPaths[bindStage]; - - foreach (var bindPath in bindPaths) - { - if (String.IsNullOrEmpty(bindName)) - { - if (String.IsNullOrEmpty(bindPath.Name)) - { - if (!String.IsNullOrEmpty(pathWithoutSourceDir)) - { - var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) - { - resolved = filePath; - } - } - - if (String.IsNullOrEmpty(resolved)) - { - var filePath = Path.Combine(bindPath.Path, path); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) - { - resolved = filePath; - } - } - } - } - else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase)) - { - var filePath = Path.Combine(bindPath.Path, path); - - checkedPaths.Add(filePath); - if (CheckFileExists(filePath)) - { - resolved = filePath; - } - } - - if (!String.IsNullOrEmpty(resolved)) - { - break; - } - } - } - - if (null == resolved) - { - throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, source, symbolDefinition.Name, checkedPaths)); - } - - return resolved; - } - - private static bool CheckFileExists(string path) - { - try - { - return File.Exists(path); - } - catch (ArgumentException) - { - throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); - } - } - } -} diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs deleted file mode 100644 index 4ad8f764..00000000 --- a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs +++ /dev/null @@ -1,164 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Text; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Resolves the fields which had variables that needed to be resolved after the file information - /// was loaded. - /// - internal class ResolveDelayedFieldsCommand - { - /// - /// Resolve delayed fields. - /// - /// - /// The fields which had resolution delayed. - /// The cached variable values used when resolving delayed fields. - public ResolveDelayedFieldsCommand(IMessaging messaging, IEnumerable delayedFields, Dictionary variableCache) - { - this.Messaging = messaging; - this.DelayedFields = delayedFields; - this.VariableCache = variableCache; - } - - private IMessaging Messaging { get; } - - private IEnumerable DelayedFields { get;} - - private IDictionary VariableCache { get; } - - public void Execute() - { - var deferredFields = new List(); - - foreach (var delayedField in this.DelayedFields) - { - try - { - var propertySymbol = delayedField.Symbol; - - // process properties first in case they refer to other binder variables - if (delayedField.Symbol.Definition.Type == SymbolDefinitionType.Property) - { - var value = this.ResolveDelayedVariables(propertySymbol.SourceLineNumbers, delayedField.Field.AsString()); - - // update the variable cache with the new value - var key = String.Concat("property.", propertySymbol.Id.Id); - this.VariableCache[key] = value; - - // update the field data - delayedField.Field.Set(value); - } - else - { - deferredFields.Add(delayedField); - } - } - catch (WixException we) - { - this.Messaging.Write(we.Error); - continue; - } - } - - // add specialization for ProductVersion fields - var keyProductVersion = "property.ProductVersion"; - if (this.VariableCache.TryGetValue(keyProductVersion, out var versionValue) && Version.TryParse(versionValue, out var productVersion)) - { - // Don't add the variable if it already exists (developer defined a property with the same name). - var fieldKey = String.Concat(keyProductVersion, ".Major"); - if (!this.VariableCache.ContainsKey(fieldKey)) - { - this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture); - } - - fieldKey = String.Concat(keyProductVersion, ".Minor"); - if (!this.VariableCache.ContainsKey(fieldKey)) - { - this.VariableCache[fieldKey] = productVersion.Minor.ToString(CultureInfo.InvariantCulture); - } - - fieldKey = String.Concat(keyProductVersion, ".Build"); - if (!this.VariableCache.ContainsKey(fieldKey)) - { - this.VariableCache[fieldKey] = productVersion.Build.ToString(CultureInfo.InvariantCulture); - } - - fieldKey = String.Concat(keyProductVersion, ".Revision"); - if (!this.VariableCache.ContainsKey(fieldKey)) - { - this.VariableCache[fieldKey] = productVersion.Revision.ToString(CultureInfo.InvariantCulture); - } - } - - // process the remaining fields in case they refer to property binder variables - foreach (var delayedField in deferredFields) - { - try - { - var value = this.ResolveDelayedVariables(delayedField.Symbol.SourceLineNumbers, delayedField.Field.AsString()); - delayedField.Field.Set(value); - } - catch (WixException we) - { - this.Messaging.Write(we.Error); - } - } - } - - private string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value) - { - var start = 0; - - while (Common.TryParseWixVariable(value, start, out var parsed)) - { - if (parsed.Namespace == "bind") - { - var key = String.Concat(parsed.Name, ".", parsed.Scope); - - if (!this.VariableCache.TryGetValue(key, out var resolvedValue)) - { - resolvedValue = parsed.DefaultValue; - } - - // insert the resolved value if it was found or display an error - if (null != resolvedValue) - { - if (parsed.Index == 0 && parsed.Length == value.Length) - { - value = resolvedValue; - } - else - { - var sb = new StringBuilder(value); - sb.Remove(parsed.Index, parsed.Length); - sb.Insert(parsed.Index, resolvedValue); - value = sb.ToString(); - } - - start = parsed.Index; - } - else - { - this.Messaging.Write(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value)); - break; - } - } - else - { - start = parsed.Index + parsed.Length; - } - } - - return value; - } - } -} diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs deleted file mode 100644 index 794208e5..00000000 --- a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs +++ /dev/null @@ -1,276 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Resolve source fields in the tables included in the output - /// - internal class ResolveFieldsCommand - { - public IMessaging Messaging { private get; set; } - - public bool BuildingPatch { private get; set; } - - public IVariableResolver VariableResolver { private get; set; } - - public IEnumerable BindPaths { private get; set; } - - public IEnumerable Extensions { private get; set; } - - public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } - - public string IntermediateFolder { private get; set; } - - public Intermediate Intermediate { private get; set; } - - public bool SupportDelayedResolution { private get; set; } - - public bool AllowUnresolvedVariables { private get; set; } - - public IReadOnlyCollection DelayedFields { get; private set; } - - public void Execute() - { - var delayedFields = this.SupportDelayedResolution ? new List() : null; - - var fileResolver = new FileResolver(this.BindPaths, this.Extensions); - - // Build the column lookup only when needed. - Dictionary customColumnsById = null; - - foreach (var sections in this.Intermediate.Sections) - { - foreach (var symbol in sections.Symbols) - { - foreach (var field in symbol.Fields) - { - if (field.IsNull()) - { - continue; - } - - var fieldType = field.Type; - - // Custom table cells require an extra look up to the column definition as the - // cell's data type is always a string (because strings can store anything) but - // the column definition may be more specific. - if (symbol.Definition.Type == SymbolDefinitionType.WixCustomTableCell) - { - // We only care about the Data in a CustomTable cell. - if (field.Name != nameof(WixCustomTableCellSymbolFields.Data)) - { - continue; - } - - if (customColumnsById == null) - { - customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Symbols.OfType()).ToDictionary(t => t.Id.Id); - } - - if (customColumnsById.TryGetValue(symbol.Fields[(int)WixCustomTableCellSymbolFields.TableRef].AsString() + "/" + symbol.Fields[(int)WixCustomTableCellSymbolFields.ColumnRef].AsString(), out var customColumn)) - { - fieldType = customColumn.Type; - } - } - - // Check to make sure we're in a scenario where we can handle variable resolution. - if (null != delayedFields) - { - // resolve localization and wix variables - if (fieldType == IntermediateFieldType.String) - { - var original = field.AsString(); - if (!String.IsNullOrEmpty(original)) - { - var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables); - if (resolution.UpdatedValue) - { - field.Set(resolution.Value); - } - - if (resolution.DelayedResolve) - { - delayedFields.Add(new DelayedField(symbol, field)); - } - } - } - } - - // Move to next symbol if we've hit an error resolving variables. - if (this.Messaging.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. - { - continue; - } - - // Resolve file paths - if (fieldType == IntermediateFieldType.Path) - { - this.ResolvePathField(fileResolver, symbol, field); - -#if TODO_PATCHING - if (null != objectField.PreviousData) - { - objectField.PreviousData = this.BindVariableResolver.ResolveVariables(symbol.SourceLineNumbers, objectField.PreviousData, false, out isDefault); - - if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. - { - // file is compressed in a cabinet (and not modified above) - if (objectField.PreviousEmbeddedFileIndex.HasValue && isDefault) - { - // when loading transforms from disk, PreviousBaseUri may not have been set - if (null == objectField.PreviousBaseUri) - { - objectField.PreviousBaseUri = objectField.BaseUri; - } - - string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.IntermediateFolder); - - // set the path to the file once its extracted from the cabinet - objectField.PreviousData = extractPath; - } - else if (null != objectField.PreviousData) // non-compressed file (or localized value) - { - try - { - if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) - { - // resolve the path to the file - objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Normal); - } - else - { - if (fileResolver.RebaseTarget) - { - // if -bt is used, it come here - // Try to use the original unresolved source from either target build or update build - // If both target and updated are of old wixpdb, it behaves the same as today, no re-base logic here - // If target is old version and updated is new version, it uses unresolved path from updated build - // If both target and updated are of new versions, it uses unresolved path from target build - if (null != objectField.UnresolvedPreviousData || null != objectField.UnresolvedData) - { - objectField.PreviousData = objectField.UnresolvedPreviousData ?? objectField.UnresolvedData; - } - } - - // resolve the path to the file - objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Target); - - } - } - catch (WixFileNotFoundException) - { - // display the error with source line information - Messaging.Instance.Write(WixErrors.FileNotFound(symbol.SourceLineNumbers, (string)objectField.PreviousData)); - } - } - } - } -#endif - } - } - } - } - - this.DelayedFields = delayedFields; - } - - private void ResolvePathField(FileResolver fileResolver, IntermediateSymbol symbol, IntermediateField field) - { - var fieldValue = field.AsPath(); - var originalFieldPath = fieldValue.Path; - -#if TODO_PATCHING - // Skip file resolution if the file is to be deleted. - if (RowOperation.Delete == symbol.Operation) - { - continue; - } -#endif - - // If the file is embedded and if the previous value has a bind variable in the path - // which gets modified by resolving the previous value again then switch to that newly - // resolved path instead of using the embedded file. - if (fieldValue.Embed && field.PreviousValue != null) - { - var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, field.PreviousValue.AsString(), errorOnUnknown: false); - - if (resolution.UpdatedValue && !resolution.IsDefault) - { - fieldValue = new IntermediateFieldPathValue { Path = resolution.Value }; - } - } - - // If we're still using the embedded file. - if (fieldValue.Embed) - { - // Set the path to the embedded file once where it will be extracted. - var extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileToExtract(fieldValue.BaseUri, fieldValue.Path, this.IntermediateFolder); - - field.Set(extractPath); - } - else if (fieldValue.Path != null) - { - try - { - var resolvedPath = fieldValue.Path; - - if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe) - { -#if TODO_PATCHING - // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file - if (null == objectField.UnresolvedData) - { - objectField.UnresolvedData = (string)objectField.Data; - } -#endif - resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); - } - else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic) - { - resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); - } -#if TODO_PATCHING - else // Re-base binding path scenario caused by pyro.exe -bt -bu - { - // by default, use the resolved Data for file lookup - string filePathToResolve = (string)objectField.Data; - - // if -bu is used in pyro command, this condition holds true and the tool - // will use pre-resolved source for new wixpdb file - if (fileResolver.RebaseUpdated) - { - // try to use the unResolved Source if it exists. - // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll - // Old version of winpdb file does not contain this attribute and the value is null. - if (null != objectField.UnresolvedData) - { - filePathToResolve = objectField.UnresolvedData; - } - } - - objectField.Data = fileResolver.ResolveFile(filePathToResolve, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Updated); - } -#endif - - if (!String.Equals(originalFieldPath, resolvedPath, StringComparison.OrdinalIgnoreCase)) - { - field.Set(resolvedPath); - } - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - } - } - } -} diff --git a/src/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/WixToolset.Core/Bind/TransferFilesCommand.cs deleted file mode 100644 index b3b74fbc..00000000 --- a/src/WixToolset.Core/Bind/TransferFilesCommand.cs +++ /dev/null @@ -1,196 +0,0 @@ -// 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.Bind -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Core.Native; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class TransferFilesCommand - { - public TransferFilesCommand(IMessaging messaging, IEnumerable extensions, IEnumerable fileTransfers, bool resetAcls) - { - this.Extensions = extensions; - this.Messaging = messaging; - this.FileTransfers = fileTransfers; - this.ResetAcls = resetAcls; - } - - private IMessaging Messaging { get; } - - private IEnumerable Extensions { get; } - - private IEnumerable FileTransfers { get; } - - private bool ResetAcls { get; } - - public void Execute() - { - var destinationFiles = new List(); - - foreach (var fileTransfer in this.FileTransfers) - { - // If the source and destination are identical, then there's nothing to do here - if (0 == String.Compare(fileTransfer.Source, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase)) - { - fileTransfer.Redundant = true; - continue; - } - - var retry = false; - do - { - try - { - if (fileTransfer.Move) - { - this.Messaging.Write(VerboseMessages.MoveFile(fileTransfer.Source, fileTransfer.Destination)); - this.MoveFile(fileTransfer.Source, fileTransfer.Destination); - } - else - { - this.Messaging.Write(VerboseMessages.CopyFile(fileTransfer.Source, fileTransfer.Destination)); - this.CopyFile(fileTransfer.Source, fileTransfer.Destination); - } - - retry = false; - destinationFiles.Add(fileTransfer.Destination); - } - catch (FileNotFoundException e) - { - throw new WixException(ErrorMessages.FileNotFound(fileTransfer.SourceLineNumbers, e.FileName)); - } - catch (DirectoryNotFoundException) - { - // if we already retried, give up - if (retry) - { - throw; - } - - var directory = Path.GetDirectoryName(fileTransfer.Destination); - this.Messaging.Write(VerboseMessages.CreateDirectory(directory)); - Directory.CreateDirectory(directory); - retry = true; - } - catch (UnauthorizedAccessException) - { - // if we already retried, give up - if (retry) - { - throw; - } - - if (File.Exists(fileTransfer.Destination)) - { - this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination)); - - // try to ensure the file is not read-only - var attributes = File.GetAttributes(fileTransfer.Destination); - try - { - File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly); - } - catch (ArgumentException) // thrown for unauthorized access errors - { - throw new WixException(ErrorMessages.UnauthorizedAccess(fileTransfer.Destination)); - } - - // try to delete the file - try - { - File.Delete(fileTransfer.Destination); - } - catch (IOException) - { - throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination)); - } - - retry = true; - } - else // no idea what just happened, bail - { - throw; - } - } - catch (IOException) - { - // if we already retried, give up - if (retry) - { - throw; - } - - if (File.Exists(fileTransfer.Destination)) - { - this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination)); - - // ensure the file is not read-only, then delete it - var attributes = File.GetAttributes(fileTransfer.Destination); - File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly); - try - { - File.Delete(fileTransfer.Destination); - } - catch (IOException) - { - throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination)); - } - - retry = true; - } - else // no idea what just happened, bail - { - throw; - } - } - } while (retry); - } - - // Finally, if directed then reset remove ACLs that may may have been picked up - // during the file transfer process. - if (this.ResetAcls && 0 < destinationFiles.Count) - { - try - { - FileSystem.ResetAcls(destinationFiles); - } - catch (Exception e) - { - this.Messaging.Write(WarningMessages.UnableToResetAcls(e.Message)); - } - } - } - - private void CopyFile(string source, string destination) - { - foreach (var extension in this.Extensions) - { - if (extension.CopyFile(source, destination)) - { - return; - } - } - - FileSystem.CopyFile(source, destination, allowHardlink: true); - } - - private void MoveFile(string source, string destination) - { - foreach (var extension in this.Extensions) - { - if (extension.MoveFile(source, destination)) - { - return; - } - } - - FileSystem.MoveFile(source, destination); - } - } -} diff --git a/src/WixToolset.Core/BindContext.cs b/src/WixToolset.Core/BindContext.cs deleted file mode 100644 index 052382f1..00000000 --- a/src/WixToolset.Core/BindContext.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class BindContext : IBindContext - { - internal BindContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IReadOnlyCollection BindPaths { get; set; } - - public string BurnStubPath { get; set; } - - public int CabbingThreadCount { get; set; } - - public string CabCachePath { get; set; } - - public CompressionLevel? DefaultCompressionLevel { get; set; } - - public IReadOnlyCollection DelayedFields { get; set; } - - public IReadOnlyCollection ExpectedEmbeddedFiles { get; set; } - - public IReadOnlyCollection Extensions { get; set; } - - public IReadOnlyCollection FileSystemExtensions { get; set; } - - public IReadOnlyCollection Ices { get; set; } - - public string IntermediateFolder { get; set; } - - public Intermediate IntermediateRepresentation { get; set; } - - public string OutputPath { get; set; } - - public PdbType PdbType { get; set; } - - public string PdbPath { get; set; } - - public int? ResolvedCodepage { get; set; } - - public int? ResolvedSummaryInformationCodepage { get; set; } - - public int? ResolvedLcid { get; set; } - - public IReadOnlyCollection SuppressIces { get; set; } - - public bool SuppressValidation { get; set; } - - public bool SuppressLayout { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/BindFileWithPath.cs b/src/WixToolset.Core/BindFileWithPath.cs deleted file mode 100644 index 539600d3..00000000 --- a/src/WixToolset.Core/BindFileWithPath.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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 -{ - using WixToolset.Extensibility.Data; - - /// - /// Bind file with its path. - /// - internal class BindFileWithPath : IBindFileWithPath - { - /// - /// Gets or sets the identifier of the file with this path. - /// - public string Id { get; set; } - - /// - /// Gets or sets the file path. - /// - public string Path { get; set; } - } -} diff --git a/src/WixToolset.Core/BindPath.cs b/src/WixToolset.Core/BindPath.cs deleted file mode 100644 index f70d5e36..00000000 --- a/src/WixToolset.Core/BindPath.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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 -{ - using System.Diagnostics; - using WixToolset.Extensibility.Data; - - /// - /// Bind path representation. - /// - [DebuggerDisplay("Name={Name,nq} Path={Path,nq}")] - internal class BindPath : IBindPath - { - public string Name { get; set; } - - public string Path { get; set; } - - public BindStage Stage { get; set; } - } -} diff --git a/src/WixToolset.Core/BindResult.cs b/src/WixToolset.Core/BindResult.cs deleted file mode 100644 index 9785484c..00000000 --- a/src/WixToolset.Core/BindResult.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class BindResult : IBindResult - { - private bool disposed; - - public IReadOnlyCollection FileTransfers { get; set; } - - public IReadOnlyCollection TrackedFiles { get; set; } - - public WixOutput Wixout { get; set; } - - #region IDisposable Support - /// - /// Disposes of the internal state of the file structure. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of the internsl state of the file structure. - /// - /// True if disposing. - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing) - { - this.Wixout?.Dispose(); - } - } - - this.disposed = true; - } - #endregion - } -} diff --git a/src/WixToolset.Core/Binder.cs b/src/WixToolset.Core/Binder.cs deleted file mode 100644 index 204ab6ee..00000000 --- a/src/WixToolset.Core/Binder.cs +++ /dev/null @@ -1,96 +0,0 @@ -// 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 -{ - using System; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Binder of the WiX toolset. - /// - internal class Binder : IBinder - { - internal Binder(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IBindResult Bind(IBindContext context) - { - // Prebind. - // - foreach (var extension in context.Extensions) - { - extension.PreBind(context); - } - - // Bind. - // - this.WriteBuildInfoSymbol(context.IntermediateRepresentation, context.OutputPath, context.PdbPath); - - var bindResult = this.BackendBind(context); - - if (bindResult != null) - { - // Postbind. - // - foreach (var extension in context.Extensions) - { - extension.PostBind(bindResult); - } - } - - return bindResult; - } - - private IBindResult BackendBind(IBindContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendFactories = extensionManager.GetServices(); - - var entrySection = context.IntermediateRepresentation.Sections.First(); - - foreach (var factory in backendFactories) - { - if (factory.TryCreateBackend(entrySection.Type.ToString(), context.OutputPath, out var backend)) - { - var result = backend.Bind(context); - return result; - } - } - - // TODO: messaging that a backend could not be found to bind the output type? - - return null; - } - - private void WriteBuildInfoSymbol(Intermediate output, string outputFile, string outputPdbPath) - { - var entrySection = output.Sections.First(s => s.Type != SectionType.Fragment); - - var executingAssembly = Assembly.GetExecutingAssembly(); - var fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location); - - var buildInfoSymbol = entrySection.AddSymbol(new WixBuildInfoSymbol() - { - WixVersion = fileVersion.FileVersion, - WixOutputFile = outputFile, - }); - - if (!String.IsNullOrEmpty(outputPdbPath)) - { - buildInfoSymbol.WixPdbFile = outputPdbPath; - } - } - } -} diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs deleted file mode 100644 index 5f618b81..00000000 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ /dev/null @@ -1,912 +0,0 @@ -// 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.CommandLine -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class BuildCommand : ICommandLineCommand - { - private readonly CommandLine commandLine; - - public BuildCommand(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); - this.ExtensionManager = serviceProvider.GetService(); - this.commandLine = new CommandLine(this.ServiceProvider, this.Messaging); - } - - public bool ShowLogo => this.commandLine.ShowLogo; - - public bool StopParsing => this.commandLine.ShowHelp; - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private IExtensionManager ExtensionManager { get; } - - private string IntermediateFolder { get; set; } - - private OutputType OutputType { get; set; } - - private List IncludeSearchPaths { get; set; } - - public string PdbFile { get; set; } - - public PdbType PdbType { get; set; } - - private Platform Platform { get; set; } - - private string OutputFile { get; set; } - - private CompressionLevel? DefaultCompressionLevel { get; set; } - - private string ContentsFile { get; set; } - - private string OutputsFile { get; set; } - - private string BuiltOutputsFile { get; set; } - - public Task ExecuteAsync(CancellationToken cancellationToken) - { - if (this.commandLine.ShowHelp) - { - Console.WriteLine("TODO: Show build command help"); - return Task.FromResult(-1); - } - - this.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); - - this.OutputType = this.commandLine.CalculateOutputType(); - - this.IncludeSearchPaths = this.commandLine.IncludeSearchPaths; - - this.PdbFile = this.commandLine.PdbFile; - - this.PdbType = this.commandLine.PdbType; - - this.Platform = this.commandLine.Platform; - - this.ContentsFile = this.commandLine.ContentsFile; - - this.OutputsFile = this.commandLine.OutputsFile; - - this.BuiltOutputsFile = this.commandLine.BuiltOutputsFile; - - this.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel; - - var preprocessorVariables = this.commandLine.GatherPreprocessorVariables(); - - var sourceFiles = this.commandLine.GatherSourceFiles(this.IntermediateFolder); - - var filterCultures = this.commandLine.CalculateFilterCultures(); - - var creator = this.ServiceProvider.GetService(); - - this.EvaluateSourceFiles(sourceFiles, creator, out var codeFiles, out var wixipl); - - this.OutputFile = this.commandLine.OutputFile; - - if (String.IsNullOrEmpty(this.OutputFile)) - { - if (codeFiles.Count == 1) - { - // If output type is unknown, the extension will be replaced with the right default based on output type. - this.OutputFile = Path.ChangeExtension(codeFiles[0].OutputPath, DefaultExtensionForOutputType(this.OutputType)); - } - else - { - this.Messaging.Write(ErrorMessages.MustSpecifyOutputWithMoreThanOneInput()); - } - } - - if (this.Messaging.EncounteredError) - { - return Task.FromResult(this.Messaging.LastErrorNumber); - } - - var wixobjs = this.CompilePhase(preprocessorVariables, codeFiles, cancellationToken); - - var wxls = this.LoadLocalizationFiles(this.commandLine.LocalizationFilePaths, preprocessorVariables, cancellationToken); - - if (this.Messaging.EncounteredError) - { - return Task.FromResult(this.Messaging.LastErrorNumber); - } - - if (this.OutputType == OutputType.Library) - { - using (new IntermediateFieldContext("wix.lib")) - { - var wixlib = this.LibraryPhase(wixobjs, wxls, this.commandLine.BindFiles, this.commandLine.BindPaths, cancellationToken); - - if (!this.Messaging.EncounteredError) - { - wixlib.Save(this.OutputFile); - } - } - } - else - { - using (new IntermediateFieldContext("wix.link")) - { - if (wixipl == null) - { - wixipl = this.LinkPhase(wixobjs, this.commandLine.LibraryFilePaths, creator, cancellationToken); - } - - if (!this.Messaging.EncounteredError) - { - var outputExtension = Path.GetExtension(this.OutputFile); - if (String.IsNullOrEmpty(outputExtension) || ".wix" == outputExtension) - { - var entrySectionType = wixipl.Sections.Single().Type; - this.OutputFile = Path.ChangeExtension(this.OutputFile, DefaultExtensionForSectionType(entrySectionType)); - } - - if (this.OutputType == OutputType.IntermediatePostLink) - { - wixipl.Save(this.OutputFile); - } - else - { - using (new IntermediateFieldContext("wix.bind")) - { - this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths, cancellationToken); - } - } - } - } - } - - return Task.FromResult(this.Messaging.LastErrorNumber); - } - - public bool TryParseArgument(ICommandLineParser parser, string argument) - { - return this.commandLine.TryParseArgument(argument, parser); - } - - private void EvaluateSourceFiles(IEnumerable sourceFiles, ISymbolDefinitionCreator creator, out List codeFiles, out Intermediate wixipl) - { - codeFiles = new List(); - - wixipl = null; - - foreach (var sourceFile in sourceFiles) - { - var extension = Path.GetExtension(sourceFile.SourcePath); - - if (wixipl != null || ".wxs".Equals(extension, StringComparison.OrdinalIgnoreCase)) - { - codeFiles.Add(sourceFile); - } - else - { - try - { - wixipl = Intermediate.Load(sourceFile.SourcePath, creator); - } - catch (WixException) - { - // We'll assume anything that isn't a valid intermediate is source code to compile. - codeFiles.Add(sourceFile); - } - } - } - - if (wixipl == null && codeFiles.Count == 0) - { - this.Messaging.Write(ErrorMessages.NoSourceFiles()); - } - else if (wixipl != null && codeFiles.Count != 0) - { - this.Messaging.Write(ErrorMessages.WixiplSourceFileIsExclusive()); - } - } - - private IReadOnlyList CompilePhase(IDictionary preprocessorVariables, IEnumerable sourceFiles, CancellationToken cancellationToken) - { - var intermediates = new List(); - - foreach (var sourceFile in sourceFiles) - { - var document = this.Preprocess(preprocessorVariables, sourceFile.SourcePath, cancellationToken); - - if (this.Messaging.EncounteredError) - { - continue; - } - - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ExtensionManager.GetServices(); - context.Platform = this.Platform; - context.Source = document; - context.CancellationToken = cancellationToken; - - Intermediate intermediate = null; - try - { - var compiler = this.ServiceProvider.GetService(); - intermediate = compiler.Compile(context); - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - - if (this.Messaging.EncounteredError) - { - continue; - } - - intermediates.Add(intermediate); - } - - return intermediates; - } - - private Intermediate LibraryPhase(IReadOnlyCollection intermediates, IReadOnlyCollection localizations, bool bindFiles, IReadOnlyCollection bindPaths, CancellationToken cancellationToken) - { - var context = this.ServiceProvider.GetService(); - context.BindFiles = bindFiles; - context.BindPaths = bindPaths; - context.Extensions = this.ExtensionManager.GetServices(); - context.Localizations = localizations; - context.Intermediates = intermediates; - context.CancellationToken = cancellationToken; - - Intermediate library = null; - try - { - var librarian = this.ServiceProvider.GetService(); - library = librarian.Combine(context); - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - - return library; - } - - private Intermediate LinkPhase(IEnumerable intermediates, IEnumerable libraryFiles, ISymbolDefinitionCreator creator, CancellationToken cancellationToken) - { - var libraries = this.LoadLibraries(libraryFiles, creator); - - if (this.Messaging.EncounteredError) - { - return null; - } - - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ExtensionManager.GetServices(); - context.ExtensionData = this.ExtensionManager.GetServices(); - context.ExpectedOutputType = this.OutputType; - context.Intermediates = intermediates.Concat(libraries).ToList(); - context.SymbolDefinitionCreator = creator; - context.CancellationToken = cancellationToken; - - var linker = this.ServiceProvider.GetService(); - return linker.Link(context); - } - - private void BindPhase(Intermediate output, IReadOnlyCollection localizations, IReadOnlyCollection filterCultures, string cabCachePath, IReadOnlyCollection bindPaths, CancellationToken cancellationToken) - { - var intermediateFolder = this.IntermediateFolder; - if (String.IsNullOrEmpty(intermediateFolder)) - { - intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - } - - IResolveResult resolveResult; - { - var context = this.ServiceProvider.GetService(); - context.BindPaths = bindPaths; - context.Extensions = this.ExtensionManager.GetServices(); - context.ExtensionData = this.ExtensionManager.GetServices(); - context.FilterCultures = filterCultures; - context.IntermediateFolder = intermediateFolder; - context.IntermediateRepresentation = output; - context.Localizations = localizations; - context.CancellationToken = cancellationToken; - - var resolver = this.ServiceProvider.GetService(); - resolveResult = resolver.Resolve(context); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - IBindResult bindResult = null; - try - { - { - var context = this.ServiceProvider.GetService(); - //context.CabbingThreadCount = this.CabbingThreadCount; - context.CabCachePath = cabCachePath; - context.ResolvedCodepage = resolveResult.Codepage; - context.ResolvedSummaryInformationCodepage = resolveResult.SummaryInformationCodepage; - context.ResolvedLcid = resolveResult.PackageLcid; - context.DefaultCompressionLevel = this.DefaultCompressionLevel; - context.DelayedFields = resolveResult.DelayedFields; - context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; - context.Extensions = this.ExtensionManager.GetServices(); - context.FileSystemExtensions = this.ExtensionManager.GetServices(); - context.Ices = this.commandLine.Ices; - context.IntermediateFolder = intermediateFolder; - context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; - context.OutputPath = this.OutputFile; - context.PdbType = this.PdbType; - context.PdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(this.OutputFile, ".wixpdb"); - context.SuppressIces = this.commandLine.SuppressIces; - context.SuppressValidation = this.commandLine.SuppressValidation; - context.CancellationToken = cancellationToken; - - var binder = this.ServiceProvider.GetService(); - bindResult = binder.Bind(context); - } - - if (this.Messaging.EncounteredError) - { - return; - } - - { - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ExtensionManager.GetServices(); - context.TrackedFiles = bindResult.TrackedFiles; - context.FileTransfers = bindResult.FileTransfers; - context.IntermediateFolder = intermediateFolder; - context.ContentsFile = this.ContentsFile; - context.OutputsFile = this.OutputsFile; - context.BuiltOutputsFile = this.BuiltOutputsFile; - context.ResetAcls = this.commandLine.ResetAcls; - context.CancellationToken = cancellationToken; - - var layout = this.ServiceProvider.GetService(); - layout.Layout(context); - } - } - finally - { - bindResult?.Dispose(); - } - } - - private IEnumerable LoadLibraries(IEnumerable libraryFiles, ISymbolDefinitionCreator creator) - { - try - { - return Intermediate.Load(libraryFiles, creator); - } - catch (WixCorruptFileException e) - { - this.Messaging.Write(e.Error); - } - catch (WixUnexpectedFileFormatException e) - { - this.Messaging.Write(e.Error); - } - - return Array.Empty(); - } - - private IReadOnlyList LoadLocalizationFiles(IEnumerable locFiles, IDictionary preprocessorVariables, CancellationToken cancellationToken) - { - var localizations = new List(); - var parser = this.ServiceProvider.GetService(); - - foreach (var loc in locFiles) - { - var document = this.Preprocess(preprocessorVariables, loc, cancellationToken); - - if (this.Messaging.EncounteredError) - { - continue; - } - - var localization = parser.ParseLocalization(document); - localizations.Add(localization); - } - - return localizations; - } - - private XDocument Preprocess(IDictionary preprocessorVariables, string sourcePath, CancellationToken cancellationToken) - { - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ExtensionManager.GetServices(); - context.Platform = this.Platform; - context.IncludeSearchPaths = this.IncludeSearchPaths; - context.SourcePath = sourcePath; - context.Variables = preprocessorVariables; - context.CancellationToken = cancellationToken; - - IPreprocessResult result = null; - try - { - var preprocessor = this.ServiceProvider.GetService(); - result = preprocessor.Preprocess(context); - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - - return result?.Document; - } - - private static string DefaultExtensionForSectionType(SectionType sectionType) - { - switch (sectionType) - { - case SectionType.Bundle: - return ".exe"; - case SectionType.Module: - return ".msm"; - case SectionType.Product: - return ".msi"; - case SectionType.PatchCreation: - return ".pcp"; - case SectionType.Patch: - return ".msp"; - case SectionType.Fragment: - case SectionType.Unknown: - default: - return ".wix"; - } - } - - private static string DefaultExtensionForOutputType(OutputType outputType) - { - switch (outputType) - { - case OutputType.Bundle: - return ".exe"; - case OutputType.Library: - return ".wixlib"; - case OutputType.Module: - return ".msm"; - case OutputType.Patch: - return ".msp"; - case OutputType.PatchCreation: - return ".pcp"; - case OutputType.Product: - return ".msi"; - case OutputType.Transform: - return ".mst"; - case OutputType.IntermediatePostLink: - return ".wixipl"; - case OutputType.Unknown: - default: - return ".wix"; - } - } - - private class CommandLine - { - private static readonly char[] BindPathSplit = { '=' }; - - public bool BindFiles { get; private set; } - - public List BindPaths { get; } = new List(); - - public string CabCachePath { get; private set; } - - public List Cultures { get; } = new List(); - - public List Defines { get; } = new List(); - - public List IncludeSearchPaths { get; } = new List(); - - public List LocalizationFilePaths { get; } = new List(); - - public List LibraryFilePaths { get; } = new List(); - - public List SourceFilePaths { get; } = new List(); - - public Platform Platform { get; private set; } - - public string PdbFile { get; private set; } - - public PdbType PdbType { get; private set; } - - public bool ShowLogo { get; private set; } - - public bool ShowHelp { get; private set; } - - public string IntermediateFolder { get; private set; } - - public string OutputFile { get; private set; } - - public string OutputType { get; private set; } - - public CompressionLevel? DefaultCompressionLevel { get; private set; } - - public string ContentsFile { get; private set; } - - public string OutputsFile { get; private set; } - - public string BuiltOutputsFile { get; private set; } - - public List Ices { get; } = new List(); - - public List SuppressIces { get; } = new List(); - - public bool SuppressValidation { get; set; } - - public bool ResetAcls { get; set; } - - public CommandLine(IServiceProvider serviceProvider, IMessaging messaging) - { - this.ServiceProvider = serviceProvider; - this.Messaging = messaging; - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - public bool TryParseArgument(string arg, ICommandLineParser parser) - { - if (parser.IsSwitch(arg)) - { - var parameter = arg.Substring(1).ToLowerInvariant(); - switch (parameter) - { - case "?": - case "h": - case "help": - this.ShowHelp = true; - return true; - - case "arch": - case "platform": - { - var value = parser.GetNextArgumentOrError(arg); - if (Enum.TryParse(value, true, out Platform platform)) - { - this.Platform = platform; - return true; - } - break; - } - - case "bf": - case "bindfiles": - this.BindFiles = true; - return true; - - case "bindpath": - { - var value = parser.GetNextArgumentOrError(arg); - if (value != null && this.TryParseBindPath(value, out var bindPath)) - { - this.BindPaths.Add(bindPath); - return true; - } - return false; - } - - case "cc": - this.CabCachePath = parser.GetNextArgumentOrError(arg); - return true; - - case "culture": - parser.GetNextArgumentOrError(arg, this.Cultures); - return true; - - case "contentsfile": - this.ContentsFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "outputsfile": - this.OutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "builtoutputsfile": - this.BuiltOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "d": - case "define": - parser.GetNextArgumentOrError(arg, this.Defines); - return true; - - case "dcl": - case "defaultcompressionlevel": - { - var value = parser.GetNextArgumentOrError(arg); - if (Enum.TryParse(value, true, out CompressionLevel compressionLevel)) - { - this.DefaultCompressionLevel = compressionLevel; - return true; - } - return false; - } - - case "i": - case "includepath": - parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths); - return true; - - case "ice": - { - var value = parser.GetNextArgumentOrError(arg); - this.Ices.Add(value); - return true; - } - - case "intermediatefolder": - this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); - return true; - - case "loc": - parser.GetNextArgumentAsFilePathOrError(arg, "localization files", this.LocalizationFilePaths); - return true; - - case "lib": - parser.GetNextArgumentAsFilePathOrError(arg, "library files", this.LibraryFilePaths); - return true; - - case "o": - case "out": - this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "outputtype": - this.OutputType = parser.GetNextArgumentOrError(arg); - return true; - - case "pdb": - this.PdbFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "pdbtype": - { - var value = parser.GetNextArgumentOrError(arg); - if (Enum.TryParse(value, true, out PdbType pdbType)) - { - this.PdbType = pdbType; - return true; - } - return false; - } - - case "sice": - { - var value = parser.GetNextArgumentOrError(arg); - this.SuppressIces.Add(value); - return true; - } - - case "nologo": - this.ShowLogo = false; - return true; - - case "v": - case "verbose": - this.Messaging.ShowVerboseMessages = true; - return true; - - case "sval": - this.SuppressValidation = true; - return true; - - case "resetacls": - this.ResetAcls = true; - return true; - } - - if (parameter.StartsWith("sw")) - { - this.ParseSuppressWarning(parameter, "sw".Length, parser); - return true; - } - else if (parameter.StartsWith("suppresswarning")) - { - this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser); - return true; - } - else if (parameter.StartsWith("wx")) - { - this.ParseWarningAsError(parameter, "wx".Length, parser); - return true; - } - - return false; - } - else - { - parser.GetArgumentAsFilePathOrError(arg, "source code", this.SourceFilePaths); - return true; - } - } - - public string CalculateIntermedateFolder() - { - return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; - } - - public OutputType CalculateOutputType() - { - if (String.IsNullOrEmpty(this.OutputType)) - { - this.OutputType = Path.GetExtension(this.OutputFile); - } - - switch (this.OutputType?.ToLowerInvariant()) - { - case "bundle": - case ".exe": - return Data.OutputType.Bundle; - - case "library": - case ".wixlib": - return Data.OutputType.Library; - - case "module": - case ".msm": - return Data.OutputType.Module; - - case "patch": - case ".msp": - return Data.OutputType.Patch; - - case ".pcp": - return Data.OutputType.PatchCreation; - - case "product": - case "package": - case ".msi": - return Data.OutputType.Product; - - case "transform": - case ".mst": - return Data.OutputType.Transform; - - case "intermediatepostlink": - case ".wixipl": - return Data.OutputType.IntermediatePostLink; - } - - return Data.OutputType.Unknown; - } - - public IReadOnlyList CalculateFilterCultures() - { - var result = new List(); - - if (this.Cultures == null) - { - } - else if (this.Cultures.Count == 1 && this.Cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase)) - { - // When null is used treat it as if cultures wasn't specified. This is - // needed for batching in the MSBuild task since MSBuild doesn't support - // empty items. - } - else - { - foreach (var culture in this.Cultures) - { - // Neutral is different from null. For neutral we still want to do culture filtering. - // Set the culture to the empty string = identifier for the invariant culture. - var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture; - result.Add(filter); - } - } - - return result; - } - - public IDictionary GatherPreprocessorVariables() - { - var variables = new Dictionary(); - - foreach (var pair in this.Defines) - { - var value = pair.Split(new[] { '=' }, 2); - - if (variables.ContainsKey(value[0])) - { - this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]])); - continue; - } - - variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]); - } - - return variables; - } - - public IEnumerable GatherSourceFiles(string intermediateDirectory) - { - var files = new List(); - - foreach (var item in this.SourceFilePaths) - { - var sourcePath = item; - var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir"); - - files.Add(new SourceFile(sourcePath, outputPath)); - } - - return files; - } - - private bool TryParseBindPath(string bindPath, out IBindPath bp) - { - var namedPath = bindPath.Split(BindPathSplit, 2); - - bp = this.ServiceProvider.GetService(); - - if (1 == namedPath.Length) - { - bp.Path = namedPath[0]; - } - else - { - bp.Name = namedPath[0]; - bp.Path = namedPath[1]; - } - - if (File.Exists(bp.Path)) - { - this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path)); - return false; - } - - return true; - } - - private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser) - { - var paramArg = parameter.Substring(offset); - if (paramArg.Length == 0) - { - this.Messaging.SuppressAllWarnings = true; - } - else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0) - { - this.Messaging.SuppressWarningMessage(suppressWarning); - } - else - { - parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg)); - } - } - - private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser) - { - var paramArg = parameter.Substring(offset); - if (paramArg.Length == 0) - { - this.Messaging.WarningsAsError = true; - } - else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0) - { - this.Messaging.ElevateWarningMessage(elevateWarning); - } - else - { - parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg)); - } - } - } - } -} diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs deleted file mode 100644 index b87b6a5d..00000000 --- a/src/WixToolset.Core/CommandLine/CommandLine.cs +++ /dev/null @@ -1,199 +0,0 @@ -// 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.CommandLine -{ - using System; - using System.Collections.Generic; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal enum CommandTypes - { - Unknown, - Build, - Preprocess, - Compile, - Link, - Bind, - Decompile, - } - - internal class CommandLine : ICommandLine - { - public CommandLine(IServiceProvider serviceProvider) => this.ServiceProvider = serviceProvider; - - private IServiceProvider ServiceProvider { get; } - - public ICommandLineCommand CreateCommand(string[] args) - { - var arguments = this.ServiceProvider.GetService(); - arguments.Populate(args); - - this.LoadExtensions(arguments.Extensions); - - return this.ParseStandardCommandLine(arguments); - } - - public ICommandLineCommand CreateCommand(string commandLine) - { - var arguments = this.ServiceProvider.GetService(); - arguments.Populate(commandLine); - - this.LoadExtensions(arguments.Extensions); - - return this.ParseStandardCommandLine(arguments); - } - - public ICommandLineCommand ParseStandardCommandLine(ICommandLineArguments arguments) - { - var context = this.ServiceProvider.GetService(); - context.ExtensionManager = this.ServiceProvider.GetService(); - context.Arguments = arguments; - - var command = this.Parse(context); - - if (command.ShowLogo) - { - var branding = this.ServiceProvider.GetService(); - Console.WriteLine(branding.ReplacePlaceholders("[AssemblyProduct] [AssemblyDescription] version [FileVersion]")); - Console.WriteLine(branding.ReplacePlaceholders("[AssemblyCopyright]")); - } - - return command; - } - - private void LoadExtensions(string[] extensions) - { - var extensionManager = this.ServiceProvider.GetService(); - - foreach (var extension in extensions) - { - extensionManager.Load(extension); - } - } - - private ICommandLineCommand Parse(ICommandLineContext context) - { - var branding = context.ServiceProvider.GetService(); - var extensions = context.ExtensionManager.GetServices(); - - foreach (var extension in extensions) - { - extension.PreParse(context); - } - - ICommandLineCommand command = null; - var parser = context.Arguments.Parse(); - - while (command?.StopParsing != true && - String.IsNullOrEmpty(parser.ErrorArgument) && - parser.TryGetNextSwitchOrArgument(out var arg)) - { - if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments. - { - continue; - } - - // First argument must be the command or global switch (that creates a command). - if (command == null) - { - if (!this.TryParseCommand(arg, parser, extensions, out command)) - { - parser.ReportErrorArgument(arg); - } - } - else if (parser.IsSwitch(arg)) - { - if (!command.TryParseArgument(parser, arg) && !TryParseCommandLineArgumentWithExtension(arg, parser, extensions)) - { - parser.ReportErrorArgument(arg); - } - } - else if (!TryParseCommandLineArgumentWithExtension(arg, parser, extensions) && !command.TryParseArgument(parser, arg)) - { - parser.ReportErrorArgument(arg); - } - } - - foreach (var extension in extensions) - { - extension.PostParse(); - } - - return command ?? new HelpCommand(extensions, branding); - } - - private bool TryParseCommand(string arg, ICommandLineParser parser, IEnumerable extensions, out ICommandLineCommand command) - { - command = null; - - if (parser.IsSwitch(arg)) - { - var parameter = arg.Substring(1); - switch (parameter.ToLowerInvariant()) - { - case "?": - case "h": - case "help": - case "-help": - var branding = this.ServiceProvider.GetService(); - command = new HelpCommand(extensions, branding); - break; - - case "version": - case "-version": - command = new VersionCommand(); - break; - } - } - else - { - if (Enum.TryParse(arg, true, out CommandTypes commandType)) - { - switch (commandType) - { - case CommandTypes.Build: - command = new BuildCommand(this.ServiceProvider); - break; - - case CommandTypes.Compile: - command = new CompileCommand(this.ServiceProvider); - break; - - case CommandTypes.Decompile: - command = new DecompileCommand(this.ServiceProvider); - break; - } - } - else - { - foreach (var extension in extensions) - { - if (extension.TryParseCommand(parser, arg, out command)) - { - break; - } - - command = null; - } - } - } - - return command != null; - } - - private static bool TryParseCommandLineArgumentWithExtension(string arg, ICommandLineParser parse, IEnumerable extensions) - { - foreach (var extension in extensions) - { - if (extension.TryParseArgument(parse, arg)) - { - return true; - } - } - - return false; - } - } -} diff --git a/src/WixToolset.Core/CommandLine/CommandLineArguments.cs b/src/WixToolset.Core/CommandLine/CommandLineArguments.cs deleted file mode 100644 index 40b8b320..00000000 --- a/src/WixToolset.Core/CommandLine/CommandLineArguments.cs +++ /dev/null @@ -1,207 +0,0 @@ -// 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.CommandLine -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Text.RegularExpressions; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CommandLineArguments : ICommandLineArguments - { - public CommandLineArguments(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - } - - public string[] OriginalArguments { get; set; } - - public string[] Arguments { get; set; } - - public string[] Extensions { get; set; } - - public string ErrorArgument { get; set; } - - private IMessaging Messaging { get; } - - public void Populate(string commandLine) - { - var args = CommandLineArguments.ParseArgumentsToArray(commandLine); - - this.Populate(args.ToArray()); - } - - public void Populate(string[] args) - { - this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(args); - - this.ProcessArgumentsAndParseExtensions(this.OriginalArguments); - } - - public ICommandLineParser Parse() => new CommandLineParser(this.Messaging, this.Arguments, this.ErrorArgument); - - private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments) - { - var args = new List(); - - foreach (var arg in commandLineArguments) - { - if (arg != null) - { - if ('@' == arg[0]) - { - var responseFileArguments = CommandLineArguments.ParseResponseFile(arg.Substring(1)); - args.AddRange(responseFileArguments); - } - else - { - args.Add(arg); - } - } - } - - this.OriginalArguments = args.ToArray(); - } - - private void ProcessArgumentsAndParseExtensions(string[] args) - { - var arguments = new List(); - var extensions = new List(); - - for (var i = 0; i < args.Length; ++i) - { - var arg = args[i]; - - if ("-ext" == arg || "/ext" == arg) - { - if (!CommandLineArguments.IsSwitchAt(args, ++i)) - { - extensions.Add(args[i]); - } - else - { - this.ErrorArgument = arg; - break; - } - } - else - { - arguments.Add(arg); - } - } - - this.Arguments = arguments.ToArray(); - this.Extensions = extensions.ToArray(); - } - - private static List ParseResponseFile(string responseFile) - { - string arguments; - - using (var reader = new StreamReader(responseFile)) - { - arguments = reader.ReadToEnd(); - } - - return CommandLineArguments.ParseArgumentsToArray(arguments); - } - - private static List ParseArgumentsToArray(string arguments) - { - // Scan and parse the arguments string, dividing up the arguments based on whitespace. - // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed. - // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments. - // Escaped quotes and escaped backslashes also need to be unescaped by this process. - - // Collects the final list of arguments to be returned. - var argsList = new List(); - - // True if we are inside an unescaped quote, meaning whitespace should be ignored. - var insideQuote = false; - - // Index of the start of the current argument substring; either the start of the argument - // or the start of a quoted or unquoted sequence within it. - var partStart = 0; - - // The current argument string being built; when completed it will be added to the list. - var arg = new StringBuilder(); - - for (var i = 0; i <= arguments.Length; i++) - { - if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote)) - { - // Reached a whitespace separator or the end of the string. - - // Finish building the current argument. - arg.Append(arguments.Substring(partStart, i - partStart)); - - // Skip over the whitespace character. - partStart = i + 1; - - // Add the argument to the list if it's not empty. - if (arg.Length > 0) - { - argsList.Add(CommandLineArguments.ExpandEnvironmentVariables(arg.ToString())); - arg.Length = 0; - } - } - else if (i > partStart && arguments[i - 1] == '\\') - { - // Check the character following an unprocessed backslash. - // Unescape quotes, and backslashes followed by a quote. - if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"')) - { - // Unescape the quote or backslash by skipping the preceeding backslash. - arg.Append(arguments.Substring(partStart, i - 1 - partStart)); - arg.Append(arguments[i]); - partStart = i + 1; - } - } - else if (arguments[i] == '"') - { - // Add the quoted or unquoted section to the argument string. - arg.Append(arguments.Substring(partStart, i - partStart)); - - // And skip over the quote character. - partStart = i + 1; - - insideQuote = !insideQuote; - } - } - - return argsList; - } - - private static string ExpandEnvironmentVariables(string arguments) - { - var id = Environment.GetEnvironmentVariables(); - - var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)"); - var matches = regex.Matches(arguments); - - var value = String.Empty; - for (var i = 0; i <= (matches.Count - 1); i++) - { - try - { - var key = matches[i].Value; - regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)")); - value = id[key].ToString(); - arguments = regex.Replace(arguments, value); - } - catch (NullReferenceException) - { - // Collapse unresolved environment variables. - arguments = regex.Replace(arguments, value); - } - } - - return arguments; - } - - private static bool IsSwitchAt(string[] args, int index) => args.Length > index && !String.IsNullOrEmpty(args[index]) && ('/' == args[index][0] || '-' == args[index][0]); - } -} diff --git a/src/WixToolset.Core/CommandLine/CommandLineContext.cs b/src/WixToolset.Core/CommandLine/CommandLineContext.cs deleted file mode 100644 index 8d5cf120..00000000 --- a/src/WixToolset.Core/CommandLine/CommandLineContext.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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.CommandLine -{ - using System; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CommandLineContext : ICommandLineContext - { - public CommandLineContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IExtensionManager ExtensionManager { get; set; } - - public ICommandLineArguments Arguments { get; set; } - } -} diff --git a/src/WixToolset.Core/CommandLine/CommandLineParser.cs b/src/WixToolset.Core/CommandLine/CommandLineParser.cs deleted file mode 100644 index 015d3e62..00000000 --- a/src/WixToolset.Core/CommandLine/CommandLineParser.cs +++ /dev/null @@ -1,270 +0,0 @@ -// 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.CommandLine -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - internal class CommandLineParser : ICommandLineParser - { - private const string ExpectedArgument = "expected argument"; - - public string ErrorArgument { get; private set; } - - private Queue RemainingArguments { get; } - - private IMessaging Messaging { get; } - - public CommandLineParser(IMessaging messaging, string[] arguments, string errorArgument) - { - this.Messaging = messaging; - this.RemainingArguments = new Queue(arguments); - this.ErrorArgument = errorArgument; - } - - public bool IsSwitch(string arg) - { - return !String.IsNullOrEmpty(arg) && '-' == arg[0]; - } - - public string GetArgumentAsFilePathOrError(string argument, string fileType) - { - if (!File.Exists(argument)) - { - this.Messaging.Write(ErrorMessages.FileNotFound(null, argument, fileType)); - return null; - } - - return argument; - } - - public void GetArgumentAsFilePathOrError(string argument, string fileType, IList paths) - { - foreach (var path in this.GetFiles(argument, fileType)) - { - paths.Add(path); - } - } - - public string GetNextArgumentOrError(string commandLineSwitch) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var argument)) - { - return argument; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return null; - } - - public bool GetNextArgumentOrError(string commandLineSwitch, IList args) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) - { - args.Add(arg); - return true; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return false; - } - - public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory)) - { - return directory; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return null; - } - - public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList directories) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory)) - { - directories.Add(directory); - return true; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return false; - } - - public string GetNextArgumentAsFilePathOrError(string commandLineSwitch) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path)) - { - return path; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return null; - } - - public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList paths) - { - if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) - { - foreach (var path in this.GetFiles(arg, fileType)) - { - paths.Add(path); - } - - return true; - } - - this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); - return false; - } - - public void ReportErrorArgument(string argument, Message message = null) - { - this.Messaging.Write(message ?? ErrorMessages.AdditionalArgumentUnexpected(argument)); - this.ErrorArgument = argument; - } - - public bool TryGetNextSwitchOrArgument(out string arg) - { - if (this.RemainingArguments.Count > 0) - { - arg = this.RemainingArguments.Dequeue(); - return true; - } - - arg = null; - return false; - } - - private bool TryGetNextNonSwitchArgumentOrError(out string arg) - { - var result = this.TryGetNextSwitchOrArgument(out arg); - - if (!result || this.IsSwitch(arg)) - { - this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument; - return false; - } - - return result; - } - - private bool TryGetDirectory(string commandlineSwitch, string arg, out string directory) - { - directory = null; - - if (File.Exists(arg)) - { - this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg)); - return false; - } - - directory = this.VerifyPath(arg); - return directory != null; - } - - private bool TryGetFile(string commandlineSwitch, string arg, out string path) - { - path = null; - - if (String.IsNullOrEmpty(arg) || '-' == arg[0]) - { - this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch)); - } - else if (Directory.Exists(arg)) - { - this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg)); - } - else - { - path = this.VerifyPath(arg); - } - - return path != null; - } - - /// - /// Get a set of files that possibly have a search pattern in the path (such as '*'). - /// - /// Search path to find files in. - /// Type of file; typically "Source". - /// An array of files matching the search path. - /// - /// This method is written in this verbose way because it needs to support ".." in the path. - /// It needs the directory path isolated from the file name in order to use Directory.GetFiles - /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since - /// Path.GetDirectoryName does not support ".." in the path. - /// - private string[] GetFiles(string searchPath, string fileType) - { - if (null == searchPath) - { - throw new ArgumentNullException(nameof(searchPath)); - } - - // Convert alternate directory separators to the standard one. - var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); - var files = new string[0]; - - try - { - if (0 > lastSeparator) - { - files = Directory.GetFiles(".", filePath); - } - else // found directory separator - { - files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1)); - } - } - catch (DirectoryNotFoundException) - { - // Don't let this function throw the DirectoryNotFoundException. This exception - // occurs for non-existant directories and invalid characters in the searchPattern. - } - catch (ArgumentException) - { - // Don't let this function throw the ArgumentException. This exception - // occurs in certain situations such as when passing a malformed UNC path. - } - catch (IOException) - { - } - - if (0 == files.Length) - { - this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType)); - } - - return files; - } - - private string VerifyPath(string path) - { - string fullPath; - - if (0 <= path.IndexOf('\"')) - { - this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path)); - return null; - } - - try - { - fullPath = Path.GetFullPath(path); - } - catch (Exception e) - { - this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message)); - return null; - } - - return fullPath; - } - } -} diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs deleted file mode 100644 index 6e31b241..00000000 --- a/src/WixToolset.Core/CommandLine/CompileCommand.cs +++ /dev/null @@ -1,94 +0,0 @@ -// 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.CommandLine -{ - using System; - using System.Collections.Generic; - using System.Threading; - using System.Threading.Tasks; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class CompileCommand : ICommandLineCommand - { - public CompileCommand(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); - this.ExtensionManager = serviceProvider.GetService(); - } - - public CompileCommand(IServiceProvider serviceProvider, IEnumerable sources, IDictionary preprocessorVariables, Platform platform) - { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); - this.ExtensionManager = serviceProvider.GetService(); - this.SourceFiles = sources; - this.PreprocessorVariables = preprocessorVariables; - this.Platform = platform; - } - - private IServiceProvider ServiceProvider { get; } - - public IMessaging Messaging { get; } - - public IExtensionManager ExtensionManager { get; } - - private IEnumerable SourceFiles { get; } - - private IDictionary PreprocessorVariables { get; } - - private Platform Platform { get; } - - public IReadOnlyCollection IncludeSearchPaths { get; } - - public bool ShowLogo => throw new NotImplementedException(); - - public bool StopParsing => throw new NotImplementedException(); - - public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => throw new NotImplementedException(); - - public Task ExecuteAsync(CancellationToken _) - { - foreach (var sourceFile in this.SourceFiles) - { - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ExtensionManager.GetServices(); - context.Platform = this.Platform; - context.IncludeSearchPaths = this.IncludeSearchPaths; - context.SourcePath = sourceFile.SourcePath; - context.Variables = this.PreprocessorVariables; - - IPreprocessResult result = null; - try - { - var preprocessor = this.ServiceProvider.GetService(); - result = preprocessor.Preprocess(context); - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - - if (this.Messaging.EncounteredError) - { - continue; - } - - var compileContext = this.ServiceProvider.GetService(); - compileContext.Extensions = this.ExtensionManager.GetServices(); - compileContext.Platform = this.Platform; - compileContext.Source = result?.Document; - - var compiler = this.ServiceProvider.GetService(); - var intermediate = compiler.Compile(compileContext); - - intermediate.Save(sourceFile.OutputPath); - } - - return Task.FromResult(0); - } - } -} diff --git a/src/WixToolset.Core/CommandLine/DecompileCommand.cs b/src/WixToolset.Core/CommandLine/DecompileCommand.cs deleted file mode 100644 index fc0ab0c9..00000000 --- a/src/WixToolset.Core/CommandLine/DecompileCommand.cs +++ /dev/null @@ -1,256 +0,0 @@ -// 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.CommandLine -{ - using System; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class DecompileCommand : ICommandLineCommand - { - private readonly CommandLine commandLine; - - public DecompileCommand(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); - this.commandLine = new CommandLine(this.Messaging); - } - - public bool ShowLogo => this.commandLine.ShowLogo; - - public bool StopParsing => this.commandLine.ShowHelp; - - private IServiceProvider ServiceProvider { get; } - - public IMessaging Messaging { get; } - - public Task ExecuteAsync(CancellationToken _) - { - if (this.commandLine.ShowHelp || String.IsNullOrEmpty(this.commandLine.DecompileFilePath)) - { - Console.WriteLine("TODO: Show decompile command help"); - return Task.FromResult(-1); - } - - var context = this.ServiceProvider.GetService(); - context.Extensions = this.ServiceProvider.GetService().GetServices(); - context.DecompilePath = this.commandLine.DecompileFilePath; - context.DecompileType = this.commandLine.CalculateDecompileType(); - context.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); - context.OutputPath = this.commandLine.CalculateOutputPath(); - - try - { - var decompiler = this.ServiceProvider.GetService(); - var result = decompiler.Decompile(context); - - if (!this.Messaging.EncounteredError) - { - Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(context.OutputPath))); - result.Document.Save(context.OutputPath, SaveOptions.OmitDuplicateNamespaces); - } - } - catch (WixException e) - { - this.Messaging.Write(e.Error); - } - - if (this.Messaging.EncounteredError) - { - return Task.FromResult(1); - } - - return Task.FromResult(0); - } - - public bool TryParseArgument(ICommandLineParser parser, string argument) - { - return this.commandLine.TryParseArgument(argument, parser); - } - - private class CommandLine - { - public CommandLine(IMessaging messaging) - { - this.Messaging = messaging; - } - - private IMessaging Messaging { get; } - - public string DecompileFilePath { get; private set; } - - public string DecompileType { get; private set; } - - public Platform Platform { get; private set; } - - public bool ShowLogo { get; private set; } - - public bool ShowHelp { get; private set; } - - public string IntermediateFolder { get; private set; } - - public string OutputFile { get; private set; } - - public bool TryParseArgument(string arg, ICommandLineParser parser) - { - if (parser.IsSwitch(arg)) - { - var parameter = arg.Substring(1); - switch (parameter.ToLowerInvariant()) - { - case "?": - case "h": - case "help": - this.ShowHelp = true; - return true; - - case "intermediatefolder": - this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); - return true; - - case "o": - case "out": - this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); - return true; - - case "nologo": - this.ShowLogo = false; - return true; - - case "v": - case "verbose": - this.Messaging.ShowVerboseMessages = true; - return true; - } - - if (parameter.StartsWith("sw")) - { - this.ParseSuppressWarning(parameter, "sw".Length, parser); - return true; - } - else if (parameter.StartsWith("suppresswarning")) - { - this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser); - return true; - } - else if (parameter.StartsWith("wx")) - { - this.ParseWarningAsError(parameter, "wx".Length, parser); - return true; - } - } - else - { - if (String.IsNullOrEmpty(this.DecompileFilePath)) - { - this.DecompileFilePath = parser.GetArgumentAsFilePathOrError(arg, "decompile file"); - return true; - } - else if (String.IsNullOrEmpty(this.OutputFile)) - { - this.OutputFile = parser.GetArgumentAsFilePathOrError(arg, "output file"); - return true; - } - } - - return false; - } - - public OutputType CalculateDecompileType() - { - if (String.IsNullOrEmpty(this.DecompileType)) - { - this.DecompileType = Path.GetExtension(this.DecompileFilePath); - } - - switch (this.DecompileType.ToLowerInvariant()) - { - case "bundle": - case ".exe": - return OutputType.Bundle; - - case "library": - case ".wixlib": - return OutputType.Library; - - case "module": - case ".msm": - return OutputType.Module; - - case "patch": - case ".msp": - return OutputType.Patch; - - case ".pcp": - return OutputType.PatchCreation; - - case "product": - case "package": - case ".msi": - return OutputType.Product; - - case "transform": - case ".mst": - return OutputType.Transform; - - case "intermediatepostlink": - case ".wixipl": - return OutputType.IntermediatePostLink; - } - - return OutputType.Unknown; - } - - public string CalculateIntermedateFolder() - { - return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; - } - - public string CalculateOutputPath() - { - return String.IsNullOrEmpty(this.OutputFile) ? Path.ChangeExtension(this.DecompileFilePath, ".wxs") : this.OutputFile; - } - - private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser) - { - var paramArg = parameter.Substring(offset); - if (paramArg.Length == 0) - { - this.Messaging.SuppressAllWarnings = true; - } - else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0) - { - this.Messaging.SuppressWarningMessage(suppressWarning); - } - else - { - parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg)); - } - } - - private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser) - { - var paramArg = parameter.Substring(offset); - if (paramArg.Length == 0) - { - this.Messaging.WarningsAsError = true; - } - else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0) - { - this.Messaging.ElevateWarningMessage(elevateWarning); - } - else - { - parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg)); - } - } - } - } -} diff --git a/src/WixToolset.Core/CommandLine/HelpCommand.cs b/src/WixToolset.Core/CommandLine/HelpCommand.cs deleted file mode 100644 index 6a5ac183..00000000 --- a/src/WixToolset.Core/CommandLine/HelpCommand.cs +++ /dev/null @@ -1,66 +0,0 @@ -// 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.CommandLine -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class HelpCommand : ICommandLineCommand - { - private static readonly ExtensionCommandLineSwitch[] BuiltInSwitches = new ExtensionCommandLineSwitch[] - { - new ExtensionCommandLineSwitch { Switch = "build", Description = "Build a wixlib, package or bundle." }, - new ExtensionCommandLineSwitch { Switch = "decompile", Description = "Decompile a package or bundle into source code." }, - }; - - public HelpCommand(IEnumerable extensions, IWixBranding branding) - { - this.Extensions = extensions; - this.Branding = branding; - } - - public bool ShowLogo => true; - - public bool StopParsing => true; - - private IEnumerable Extensions { get; } - - private IWixBranding Branding { get; } - - public Task ExecuteAsync(CancellationToken _) - { - var commandLineSwitches = new List(BuiltInSwitches); - commandLineSwitches.AddRange(this.Extensions.SelectMany(e => e.CommandLineSwitches).OrderBy(s => s.Switch, StringComparer.Ordinal)); - - Console.WriteLine(); - Console.WriteLine("Usage: wix [option]"); - Console.WriteLine("Usage: wix [command]"); - Console.WriteLine(); - Console.WriteLine("Options:"); - Console.WriteLine(" -h|--help Show command line help."); - Console.WriteLine(" --version Display WiX Toolset version in use."); - Console.WriteLine(); - - Console.WriteLine("Commands:"); - foreach (var commandLineSwitch in commandLineSwitches) - { - Console.WriteLine(" {0,-17} {1}", commandLineSwitch.Switch, commandLineSwitch.Description); - } - - Console.WriteLine(); - Console.WriteLine("Run 'wix [command] --help' for more information on a command."); - Console.WriteLine(); - Console.WriteLine(this.Branding.ReplacePlaceholders("For more information see: [SupportUrl]")); - - return Task.FromResult(-1); - } - - public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments - } -} diff --git a/src/WixToolset.Core/CommandLine/VersionCommand.cs b/src/WixToolset.Core/CommandLine/VersionCommand.cs deleted file mode 100644 index 01a7d0e6..00000000 --- a/src/WixToolset.Core/CommandLine/VersionCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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.CommandLine -{ - using System; - using System.Threading; - using System.Threading.Tasks; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class VersionCommand : ICommandLineCommand - { - public bool ShowLogo => true; - - public bool StopParsing => true; - - public Task ExecuteAsync(CancellationToken cancellationToken) - { - Console.WriteLine(ThisAssembly.AssemblyInformationalVersion); - - return Task.FromResult(0); - } - - public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments - } -} diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs deleted file mode 100644 index 848f009a..00000000 --- a/src/WixToolset.Core/Common.cs +++ /dev/null @@ -1,832 +0,0 @@ -// 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 -{ - using System; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Security.Cryptography; - using System.Text; - using System.Xml; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - /// - /// Common Wix utility methods and types. - /// - internal static class Common - { - private static readonly char[] IllegalShortFilenameCharacters = new[] { '\\', '?', '|', '>', '<', ':', '/', '*', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; - private static readonly char[] IllegalWildcardShortFilenameCharacters = new[] { '\\', '|', '>', '<', ':', '/', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; - - internal static readonly char[] IllegalLongFilenameCharacters = new[] { '\\', '/', '?', '*', '|', '>', '<', ':', '\"' }; // illegal: \ / ? | > < : / * " - internal static readonly char[] IllegalRelativeLongFilenameCharacters = new[] { '?', '*', '|', '>', '<', ':', '\"' }; // like illegal, but we allow '\' and '/' - internal static readonly char[] IllegalWildcardLongFilenameCharacters = new[] { '\\', '/', '|', '>', '<', ':', '\"' }; // like illegal: but we allow '*' and '?' - - public static string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath, IMessaging messageHandler) - { - const string root = @"C:\"; - if (!Path.IsPathRooted(relativePath)) - { - var normalizedPath = Path.GetFullPath(root + relativePath); - if (normalizedPath.StartsWith(root)) - { - var canonicalizedPath = normalizedPath.Substring(root.Length); - if (canonicalizedPath != relativePath) - { - messageHandler.Write(WarningMessages.PathCanonicalized(sourceLineNumbers, elementName, attributeName, relativePath, canonicalizedPath)); - } - return canonicalizedPath; - } - } - - messageHandler.Write(ErrorMessages.PayloadMustBeRelativeToCache(sourceLineNumbers, elementName, attributeName, relativePath)); - return relativePath; - } - - /// - /// Gets a valid code page from the given web name or integer value. - /// - /// A code page web name or integer value as a string. - /// Whether to allow -1 which does not change the database code pages. This may be the case with wxl files. - /// Whether to allow Unicode (UCS) or UTF code pages. - /// Source line information for the current authoring. - /// A valid code page number. - /// The value is an integer less than 0 or greater than 65535. - /// is null. - /// The value doesn't not represent a valid code page name or integer value. - /// The code page is invalid for summary information. - public static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - - try - { - Encoding encoding; - - // Check if a integer as a string was passed. - if (Int32.TryParse(value, out var codePage)) - { - if (0 == codePage) - { - // 0 represents a neutral database - return 0; - } - else if (allowNoChange && -1 == codePage) - { - // -1 means no change to the database code page - return -1; - } - - encoding = Encoding.GetEncoding(codePage); - } - else - { - encoding = Encoding.GetEncoding(value); - } - - // Windows Installer parses some code page references - // as unsigned shorts which fail to open the database. - if (onlyAnsi) - { - codePage = encoding.CodePage; - if (0 > codePage || Int16.MaxValue < codePage) - { - throw new WixException(ErrorMessages.InvalidSummaryInfoCodePage(sourceLineNumbers, codePage)); - } - } - - if (encoding == null) - { - throw new WixException(ErrorMessages.IllegalCodepage(sourceLineNumbers, codePage)); - } - - return encoding.CodePage; - } - catch (ArgumentException ex) - { - // Rethrow as NotSupportedException since either can be thrown - // if the system does not support the specified code page. - throw new NotSupportedException(ex.Message, ex); - } - } - - /// - /// Verifies if an identifier is a valid binder variable name. - /// - /// Binder variable name to verify. - /// True if the identifier is a valid binder variable name. - public static bool IsValidBinderVariable(string variable) - { - return TryParseWixVariable(variable, 0, out var parsed) && parsed.Index == 0 && parsed.Length == variable.Length && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); - } - - /// - /// Verifies if a string contains a valid binder variable name. - /// - /// String to verify. - /// True if the string contains a valid binder variable name. - public static bool ContainsValidBinderVariable(string verify) - { - return TryParseWixVariable(verify, 0, out var parsed) && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); - } - - /// - /// Verifies the given string is a valid 4-part version module or bundle version. - /// - /// The version to verify. - /// True if version is a valid module or bundle version. - public static bool IsValidFourPartVersion(string version) - { - if (!Common.IsValidBinderVariable(version)) - { - if (!Version.TryParse(version, out var ver) || 65535 < ver.Major || 65535 < ver.Minor || 65535 < ver.Build || 65535 < ver.Revision) - { - return false; - } - } - - return true; - } - - public static bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) - { - if (String.IsNullOrEmpty(filename)) - { - return false; - } - else if (filename.Length > 259) - { - return false; - } - - // Check for a non-period character (all periods is not legal) - var allPeriods = true; - foreach (var character in filename) - { - if ('.' != character) - { - allPeriods = false; - break; - } - } - - if (allPeriods) - { - return false; - } - - if (allowWildcards) - { - return filename.IndexOfAny(Common.IllegalWildcardLongFilenameCharacters) == -1; - } - else if (allowRelative) - { - return filename.IndexOfAny(Common.IllegalRelativeLongFilenameCharacters) == -1; - } - else - { - return filename.IndexOfAny(Common.IllegalLongFilenameCharacters) == -1; - } - } - - public static bool IsValidShortFilename(string filename, bool allowWildcards) - { - if (String.IsNullOrEmpty(filename)) - { - return false; - } - - if (allowWildcards) - { - var expectedDot = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters); - if (expectedDot == -1) - { - } - else if (filename[expectedDot] != '.') - { - return false; - } - else if (expectedDot < filename.Length) - { - var extensionInvalids = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters, expectedDot + 1); - if (extensionInvalids != -1) - { - return false; - } - } - - var foundPeriod = false; - var beforePeriod = 0; - var afterPeriod = 0; - - // count the number of characters before and after the period - // '*' is not counted because it may represent zero characters - foreach (var character in filename) - { - if ('.' == character) - { - foundPeriod = true; - } - else if ('*' != character) - { - if (foundPeriod) - { - afterPeriod++; - } - else - { - beforePeriod++; - } - } - } - - if (8 >= beforePeriod && 3 >= afterPeriod) - { - return true; - } - - return false; - } - else - { - if (filename.Length > 12) - { - return false; - } - - var expectedDot = filename.IndexOfAny(IllegalShortFilenameCharacters); - if (expectedDot == -1) - { - return filename.Length < 9; - } - else if (expectedDot > 8 || filename[expectedDot] != '.' || expectedDot + 4 < filename.Length) - { - return false; - } - - var validExtension = filename.IndexOfAny(IllegalShortFilenameCharacters, expectedDot + 1); - return validExtension == -1; - } - } - - /// - /// Generate a new Windows Installer-friendly guid. - /// - /// A new guid. - public static string GenerateGuid() - { - return Guid.NewGuid().ToString("B").ToUpperInvariant(); - } - - /// - /// Generate an identifier by hashing data from the row. - /// - /// Three letter or less prefix for generated row identifier. - /// Information to hash. - /// The generated identifier. - public static string GenerateIdentifier(string prefix, params string[] args) - { - string base64; - - using (var sha1 = new SHA1CryptoServiceProvider()) - { - var combined = String.Join("|", args); - var data = Encoding.UTF8.GetBytes(combined); - var hash = sha1.ComputeHash(data); - base64 = Convert.ToBase64String(hash); - } - - var identifier = new StringBuilder(32); - identifier.Append(prefix); - identifier.Append(base64); - identifier.Length -= 1; // removes the trailing '=' from base64 - identifier.Replace('+', '.'); - identifier.Replace('/', '_'); - - return identifier.ToString(); - } - - /// - /// Return an identifier based on provided file or directory name - /// - /// File/directory name to generate identifer from - /// A version of the name that is a legal identifier. - internal static string GetIdentifierFromName(string name) - { - StringBuilder sb = null; - var offset = 0; - - // MSI identifiers must begin with an alphabetic character or an - // underscore. Prefix all other values with an underscore. - if (!ValidIdentifierChar(name[0], true)) - { - sb = new StringBuilder("_" + name); - offset = 1; - } - - for (var i = 0; i < name.Length; ++i) - { - if (!ValidIdentifierChar(name[i], false)) - { - if (sb == null) - { - sb = new StringBuilder(name); - } - - sb[i + offset] = '_'; - } - } - - return sb?.ToString() ?? name; - } - - /// - /// Checks if the string contains a property (i.e. "foo[Property]bar") - /// - /// String to evaluate for properties. - /// True if a property is found in the string. - internal static bool ContainsProperty(string possibleProperty) - { - var start = possibleProperty.IndexOf('['); - if (start != -1 && start < possibleProperty.Length - 2) - { - var end = possibleProperty.IndexOf(']', start + 1); - if (end > start + 1) - { - // Skip supported property modifiers. - if (possibleProperty[start + 1] == '#' || possibleProperty[start + 1] == '$' || possibleProperty[start + 1] == '!') - { - ++start; - } - - var id = possibleProperty.Substring(start + 1, end - 1); - - if (Common.IsIdentifier(id)) - { - return true; - } - } - } - - return false; - } - - /// - /// Recursively loops through a directory, changing an attribute on all of the underlying files. - /// An example is to add/remove the ReadOnly flag from each file. - /// - /// The directory path to start deleting from. - /// The FileAttribute to change on each file. - /// The message handler. - /// If true, add the attribute to each file. If false, remove it. - private static void RecursiveFileAttributes(string path, FileAttributes fileAttribute, bool markAttribute, IMessaging messageHandler) - { - foreach (var subDirectory in Directory.GetDirectories(path)) - { - RecursiveFileAttributes(subDirectory, fileAttribute, markAttribute, messageHandler); - } - - foreach (var filePath in Directory.GetFiles(path)) - { - var attributes = File.GetAttributes(filePath); - if (markAttribute) - { - attributes = attributes | fileAttribute; // add to list of attributes - } - else if (fileAttribute == (attributes & fileAttribute)) // if attribute set - { - attributes = attributes ^ fileAttribute; // remove from list of attributes - } - - try - { - File.SetAttributes(filePath, attributes); - } - catch (UnauthorizedAccessException) - { - messageHandler.Write(WarningMessages.AccessDeniedForSettingAttributes(null, filePath)); - } - } - } - - /// - /// Takes an id, and demodularizes it (if possible). - /// - /// - /// If the output type is a module, returns a demodularized version of an id. Otherwise, returns the id. - /// - /// The type of the output to bind. - /// The modularization GUID. - /// The id to demodularize. - /// The demodularized id. - public static string Demodularize(OutputType outputType, string modularizationGuid, string id) - { - if (OutputType.Module == outputType && id.EndsWith(String.Concat(".", modularizationGuid), StringComparison.Ordinal)) - { - id = id.Substring(0, id.Length - 37); - } - - return id; - } - - /// - /// Get the source/target and short/long file names from an MSI Filename column. - /// - /// The Filename value. - /// An array of strings of length 4. The contents are: short target, long target, short source, and long source. - /// - /// If any particular file name part is not parsed, its set to null in the appropriate location of the returned array of strings. - /// Thus the returned array will always be of length 4. - /// - public static string[] GetNames(string value) - { - var targetSeparator = value.IndexOf(':'); - - // split source and target - string sourceName = null; - var targetName = value; - if (0 <= targetSeparator) - { - sourceName = value.Substring(targetSeparator + 1); - targetName = value.Substring(0, targetSeparator); - } - - // split the source short and long names - string sourceLongName = null; - if (null != sourceName) - { - var sourceLongNameSeparator = sourceName.IndexOf('|'); - if (0 <= sourceLongNameSeparator) - { - sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); - sourceName = sourceName.Substring(0, sourceLongNameSeparator); - } - } - - // split the target short and long names - var targetLongNameSeparator = targetName.IndexOf('|'); - string targetLongName = null; - if (0 <= targetLongNameSeparator) - { - targetLongName = targetName.Substring(targetLongNameSeparator + 1); - targetName = targetName.Substring(0, targetLongNameSeparator); - } - - // Remove the long source name when its identical to the short source name. - if (null != sourceName && sourceName == sourceLongName) - { - sourceLongName = null; - } - - // Remove the long target name when its identical to the long target name. - if (null != targetName && targetName == targetLongName) - { - targetLongName = null; - } - - // Remove the source names when they are identical to the target names. - if (sourceName == targetName && sourceLongName == targetLongName) - { - sourceName = null; - sourceLongName = null; - } - - // target name(s) - if ("." == targetName) - { - targetName = null; - } - - if ("." == targetLongName) - { - targetLongName = null; - } - - // source name(s) - if ("." == sourceName) - { - sourceName = null; - } - - if ("." == sourceLongName) - { - sourceLongName = null; - } - - return new[] { targetName, targetLongName, sourceName, sourceLongName }; - } - - /// - /// Get a source/target and short/long file name from an MSI Filename column. - /// - /// The Filename value. - /// true to get a source name; false to get a target name - /// true to get a long name; false to get a short name - /// The name. - public static string GetName(string value, bool source, bool longName) - { - var names = GetNames(value); - - if (source) - { - if (longName && null != names[3]) - { - return names[3]; - } - else if (null != names[2]) - { - return names[2]; - } - } - - if (longName && null != names[1]) - { - return names[1]; - } - else - { - return names[0]; - } - } - - /// - /// Get an attribute value. - /// - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// A rule for the contents of the value. If the contents do not follow the rule, an error is thrown. - /// The attribute's value. - internal static string GetAttributeValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule) - { - var value = attribute.Value; - - if ((emptyRule == EmptyRule.MustHaveNonWhitespaceCharacters && String.IsNullOrEmpty(value.Trim())) || - (emptyRule == EmptyRule.CanBeWhitespaceOnly && String.IsNullOrEmpty(value))) - { - messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); - return String.Empty; - } - - return value; - } - - /// - /// Verifies that a value is a legal identifier. - /// - /// The value to verify. - /// true if the value is an identifier; false otherwise. - public static bool IsIdentifier(string value) - { - if (String.IsNullOrEmpty(value)) - { - return false; - } - - for (var i = 0; i < value.Length; ++i) - { - if (!ValidIdentifierChar(value[i], i == 0)) - { - return false; - } - } - - return true; - } - - /// - /// Get an identifier attribute value and displays an error for an illegal identifier value. - /// - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's identifier value or a special value if an error occurred. - internal static string GetAttributeIdentifierValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); - - if (Common.IsIdentifier(value)) - { - if (72 < value.Length) - { - messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - - return value; - } - else - { - if (value.StartsWith("[", StringComparison.Ordinal) && value.EndsWith("]", StringComparison.Ordinal)) - { - messaging.Write(ErrorMessages.IllegalIdentifierLooksLikeFormatted(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - else - { - messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - - return String.Empty; - } - } - - /// - /// Get an integer attribute value and displays an error for an illegal integer value. - /// - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The minimum legal value. - /// The maximum legal value. - /// The attribute's integer value or a special value if an error occurred during conversion. - public static int GetAttributeIntegerValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) - { - Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); - - var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); - var integer = CompilerConstants.IllegalInteger; - - if (0 < value.Length) - { - if (Int32.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out integer)) - { - if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) - { - messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); - } - else if (minimum > integer || maximum < integer) - { - messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); - integer = CompilerConstants.IllegalInteger; - } - } - else - { - messaging.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return integer; - } - - /// - /// Gets a yes/no value and displays an error for an illegal yes/no value. - /// - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's YesNoType value. - internal static YesNoType GetAttributeYesNoValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); - var yesNo = YesNoType.IllegalValue; - - if ("yes".Equals(value) || "true".Equals(value)) - { - yesNo = YesNoType.Yes; - } - else if ("no".Equals(value) || "false".Equals(value)) - { - yesNo = YesNoType.No; - } - else - { - messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - - return yesNo; - } - - /// - /// Gets the text of an XElement. - /// - /// Element to get text. - /// The element's text. - internal static string GetInnerText(XElement node) - { - var text = node.Nodes().Where(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType).Cast().FirstOrDefault(); - return text?.Value; - } - - internal static bool TryParseWixVariable(string value, int start, out ParsedWixVariable parsedVariable) - { - parsedVariable = null; - - if (String.IsNullOrEmpty(value) || start >= value.Length) - { - return false; - } - - var startWixVariable = value.IndexOf("!(", start, StringComparison.Ordinal); - if (startWixVariable == -1) - { - return false; - } - - var firstDot = value.IndexOf('.', startWixVariable + 1); - if (firstDot == -1) - { - return false; - } - - var ns = value.Substring(startWixVariable + 2, firstDot - startWixVariable - 2); - if (ns != "loc" && ns != "bind" && ns != "wix") - { - return false; - } - - var closeParen = value.IndexOf(')', firstDot); - if (closeParen == -1) - { - return false; - } - - string name; - string scope = null; - string defaultValue = null; - - var equalsDefaultValue = value.IndexOf('=', firstDot + 1, closeParen - firstDot); - var end = equalsDefaultValue == -1 ? closeParen : equalsDefaultValue; - var secondDot = value.IndexOf('.', firstDot + 1, end - firstDot); - - if (secondDot == -1) - { - name = value.Substring(firstDot + 1, end - firstDot - 1); - } - else - { - name = value.Substring(firstDot + 1, secondDot - firstDot - 1); - scope = value.Substring(secondDot + 1, end - secondDot - 1); - - if (!Common.IsIdentifier(scope)) - { - return false; - } - } - - if (!Common.IsIdentifier(name)) - { - return false; - } - - if (equalsDefaultValue != -1 && equalsDefaultValue < closeParen) - { - defaultValue = value.Substring(equalsDefaultValue + 1, closeParen - equalsDefaultValue - 1); - } - - parsedVariable = new ParsedWixVariable - { - Index = startWixVariable, - Length = closeParen - startWixVariable + 1, - Namespace = ns, - Name = name, - Scope = scope, - DefaultValue = defaultValue - }; - - return true; - } - - /// - /// Display an unexpected attribute error. - /// - /// - /// Source line information about the owner element. - /// The attribute. - public static void UnexpectedAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - // Ignore elements defined by the W3C because we'll assume they are always right. - if (!((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || - attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))) - { - messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); - } - } - - /// - /// Display an unsupported extension attribute error. - /// - /// - /// Source line information about the owner element. - /// The extension attribute. - internal static void UnsupportedExtensionAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute extensionAttribute) - { - // Ignore elements defined by the W3C because we'll assume they are always right. - if (!((String.IsNullOrEmpty(extensionAttribute.Name.NamespaceName) && extensionAttribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || - extensionAttribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))) - { - messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, extensionAttribute.Parent.Name.LocalName, extensionAttribute.Name.LocalName)); - } - } - - private static bool ValidIdentifierChar(char c, bool firstChar) - { - return ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || '_' == c || - (!firstChar && (Char.IsDigit(c) || '.' == c)); - } - } -} diff --git a/src/WixToolset.Core/Compile/CompilerPayload.cs b/src/WixToolset.Core/Compile/CompilerPayload.cs deleted file mode 100644 index 3f423034..00000000 --- a/src/WixToolset.Core/Compile/CompilerPayload.cs +++ /dev/null @@ -1,291 +0,0 @@ -// 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 -{ - using System; - using System.IO; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - - internal class CompilerPayload - { - public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default; - - public string Description { get; set; } - - public string DownloadUrl { get; set; } - - public string Hash { get; set; } - - public Identifier Id { get; set; } - - public bool IsRemoteAllowed { get; set; } - - public bool IsRequired { get; set; } = true; - - public string Name { get; set; } - - public string ProductName { get; set; } - - public long? Size { get; set; } - - public string SourceFile { get; set; } - - public string Version { get; set; } - - public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element) - { - this.Core = core; - this.Element = element; - this.SourceLineNumbers = sourceLineNumbers; - } - - private CompilerCore Core { get; } - - private XElement Element { get; } - - private SourceLineNumber SourceLineNumbers { get; } - - private void CalculateAndVerifyFields() - { - var isRemote = this.IsRemoteAllowed && !String.IsNullOrEmpty(this.Hash); - - if (String.IsNullOrEmpty(this.SourceFile)) - { - if (!String.IsNullOrEmpty(this.Name) && !isRemote) - { - this.SourceFile = Path.Combine("SourceDir", this.Name); - } - } - else if (this.SourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - if (String.IsNullOrEmpty(this.Name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile", this.SourceFile)); - } - else - { - this.SourceFile = Path.Combine(this.SourceFile, Path.GetFileName(this.Name)); - } - } - - if (String.IsNullOrEmpty(this.SourceFile) && !isRemote) - { - if (this.IsRequired) - { - if (!this.IsRemoteAllowed) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile")); - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "Hash")); - } - } - } - else if (this.IsRemoteAllowed) - { - var isLocal = !String.IsNullOrEmpty(this.SourceFile); - - if (isLocal) - { - if (isRemote) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile")); - } - - if (!String.IsNullOrEmpty(this.Description)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Description", "SourceFile")); - } - - if (!String.IsNullOrEmpty(this.ProductName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "ProductName", "SourceFile")); - } - - if (this.Size.HasValue) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "SourceFile")); - } - - if (!String.IsNullOrEmpty(this.Version)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Version", "SourceFile")); - } - } - else - { - if (String.IsNullOrEmpty(this.DownloadUrl)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "Hash")); - } - - if (String.IsNullOrEmpty(this.Name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "Hash")); - } - - if (!this.Size.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "Hash")); - } - - if (YesNoDefaultType.Yes == this.Compressed) - { - this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName)); - } - - this.Compressed = YesNoDefaultType.No; - } - } - } - - public WixBundlePayloadSymbol CreatePayloadSymbol(ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown, string previousId = null) - { - WixBundlePayloadSymbol symbol = null; - - if (parentType == ComplexReferenceParentType.Container && parentId == BurnConstants.BurnUXContainerName) - { - if (this.Compressed == YesNoDefaultType.No) - { - this.Core.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(this.SourceLineNumbers, this.SourceFile)); - } - - if (!String.IsNullOrEmpty(this.DownloadUrl)) - { - this.Core.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(this.SourceLineNumbers, this.Id.Id)); - } - - this.Compressed = YesNoDefaultType.Yes; - this.DownloadUrl = null; - } - - if (!this.Core.EncounteredError) - { - symbol = this.Core.AddSymbol(new WixBundlePayloadSymbol(this.SourceLineNumbers, this.Id) - { - Name = String.IsNullOrEmpty(this.Name) ? Path.GetFileName(this.SourceFile) : this.Name, - SourceFile = new IntermediateFieldPathValue { Path = this.SourceFile }, - DownloadUrl = this.DownloadUrl, - Compressed = (this.Compressed == YesNoDefaultType.Yes) ? true : (this.Compressed == YesNoDefaultType.No) ? (bool?)false : null, - UnresolvedSourceFile = this.SourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. - DisplayName = this.ProductName, - Description = this.Description, - Hash = this.Hash, - FileSize = this.Size, - Version = this.Version, - }); - - this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, previousType, previousId); - } - - return symbol; - } - - public void FinishCompilingPackage() - { - this.CalculateAndVerifyFields(); - this.GenerateIdFromFilename(); - - if (this.Id == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Id")); - this.Id = Identifier.Invalid; - } - } - - public void FinishCompilingPackagePayload() - { - this.CalculateAndVerifyFields(); - this.GenerateIdFromFilename(); - this.GenerateIdFromPrefix("ppy"); - } - - public void FinishCompilingPayload() - { - this.CalculateAndVerifyFields(); - this.GenerateIdFromPrefix("pay"); - } - - private void GenerateIdFromFilename() - { - if (this.Id == null) - { - if (!String.IsNullOrEmpty(this.Name)) - { - this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.Name)); - } - else if (!String.IsNullOrEmpty(this.SourceFile)) - { - this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.SourceFile)); - } - } - } - - private void GenerateIdFromPrefix(string prefix) - { - if (this.Id == null) - { - this.Id = this.Core.CreateIdentifier(prefix, this.SourceFile?.ToUpperInvariant() ?? String.Empty); - } - } - - public void ParseCompressed(XAttribute attrib) - { - this.Compressed = this.Core.GetAttributeYesNoDefaultValue(this.SourceLineNumbers, attrib); - } - - public void ParseDescription(XAttribute attrib) - { - this.Description = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - public void ParseDownloadUrl(XAttribute attrib) - { - this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - public void ParseHash(XAttribute attrib) - { - this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - public void ParseId(XAttribute attrib) - { - this.Id = this.Core.GetAttributeIdentifier(this.SourceLineNumbers, attrib); - } - - public void ParseName(XAttribute attrib) - { - this.Name = this.Core.GetAttributeLongFilename(this.SourceLineNumbers, attrib, false, true); - if (!this.Core.IsValidLongFilename(this.Name, false, true)) - { - this.Core.Write(ErrorMessages.IllegalLongFilename(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", this.Name)); - } - } - - public void ParseProductName(XAttribute attrib) - { - this.ProductName = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - public void ParseSize(XAttribute attrib) - { - this.Size = this.Core.GetAttributeLongValue(this.SourceLineNumbers, attrib, 1, Int64.MaxValue); - } - - public void ParseSourceFile(XAttribute attrib) - { - this.SourceFile = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - public void ParseVersion(XAttribute attrib) - { - this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); - } - - } -} diff --git a/src/WixToolset.Core/CompileContext.cs b/src/WixToolset.Core/CompileContext.cs deleted file mode 100644 index d84d7aac..00000000 --- a/src/WixToolset.Core/CompileContext.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Threading; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class CompileContext : ICompileContext - { - internal CompileContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public string CompilationId { get; set; } - - public IReadOnlyCollection Extensions { get; set; } - - public Platform Platform { get; set; } - - public bool IsCurrentPlatform64Bit => this.Platform == Platform.ARM64 || this.Platform == Platform.X64; - - public XDocument Source { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs deleted file mode 100644 index c39bec70..00000000 --- a/src/WixToolset.Core/Compiler.cs +++ /dev/null @@ -1,8514 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB - private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) - - private const char ComponentIdPlaceholderStart = (char)167; - private const char ComponentIdPlaceholderEnd = (char)167; - private Dictionary componentIdPlaceholders; - - // If these are true you know you are building a module or product - // but if they are false you cannot not be sure they will not end - // up a product or module. Use these flags carefully. - private bool compilingModule; - private bool compilingProduct; - - private string activeName; - private string activeLanguage; - - /// - /// Type of RadioButton element in a group. - /// - private enum RadioButtonType - { - /// Not set, yet. - NotSet, - - /// Text - Text, - - /// Bitmap - Bitmap, - - /// Icon - Icon, - } - - internal Compiler(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - } - - public IMessaging Messaging { get; } - - private ICompileContext Context { get; set; } - - private CompilerCore Core { get; set; } - - /// - /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements. - /// - /// The platform which the compiler will use when defaulting 64-bit attributes and elements. - public Platform CurrentPlatform => this.Context.Platform; - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - public bool ShowPedanticMessages { get; set; } - - /// - /// Compiles the provided Xml document into an intermediate object - /// - /// Intermediate object representing compiled source document. - /// This method is not thread-safe. - public Intermediate Compile(ICompileContext context) - { - var target = new Intermediate(); - - if (String.IsNullOrEmpty(context.CompilationId)) - { - context.CompilationId = target.Id; - } - - this.Context = context; - - var extensionsByNamespace = new Dictionary(); - - foreach (var extension in this.Context.Extensions) - { - if (!extensionsByNamespace.TryGetValue(extension.Namespace, out var collidingExtension)) - { - extensionsByNamespace.Add(extension.Namespace, extension); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateExtensionXmlSchemaNamespace(extension.GetType().ToString(), extension.Namespace.NamespaceName, collidingExtension.GetType().ToString())); - } - - extension.PreCompile(this.Context); - } - - // Try to compile it. - try - { - var parseHelper = this.Context.ServiceProvider.GetService(); - - this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace); - this.Core.ShowPedanticMessages = this.ShowPedanticMessages; - this.componentIdPlaceholders = new Dictionary(); - - // parse the document - var source = this.Context.Source; - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(source.Root); - if ("Wix" == source.Root.Name.LocalName) - { - if (CompilerCore.WixNamespace == source.Root.Name.Namespace) - { - this.ParseWixElement(source.Root); - } - else // invalid or missing namespace - { - if (String.IsNullOrEmpty(source.Root.Name.NamespaceName)) - { - this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", CompilerCore.WixNamespace.ToString())); - } - else - { - this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", source.Root.Name.NamespaceName, CompilerCore.WixNamespace.ToString())); - } - } - } - else - { - this.Core.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, source.Root.Name.LocalName, "source", "Wix")); - } - - // Resolve any Component Id placeholders compiled into the intermediate. - this.ResolveComponentIdPlaceholders(target); - } - finally - { - foreach (var extension in this.Context.Extensions) - { - extension.PostCompile(target); - } - - this.Core = null; - } - - target.UpdateLevel(Data.IntermediateLevels.Compiled); - - return this.Messaging.EncounteredError ? null : target; - } - - /// - /// Parses a Wix element. - /// - /// Element to parse. - private void ParseWixElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string requiredVersion = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "RequiredVersion": - requiredVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null != requiredVersion) - { - this.Core.VerifyRequiredVersion(sourceLineNumbers, requiredVersion); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Bundle": - this.ParseBundleElement(child); - break; - case "Fragment": - this.ParseFragmentElement(child); - break; - case "Module": - this.ParseModuleElement(child); - break; - case "PatchCreation": - this.ParsePatchCreationElement(child); - break; - case "Package": - this.ParsePackageElement(child); - break; - case "Patch": - this.ParsePatchElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - private void ResolveComponentIdPlaceholders(Intermediate target) - { - if (0 < this.componentIdPlaceholders.Count) - { - foreach (var section in target.Sections) - { - foreach (var symbol in section.Symbols) - { - foreach (var field in symbol.Fields) - { - if (field != null && field.Type == IntermediateFieldType.String) - { - var data = field.AsString(); - if (!String.IsNullOrEmpty(data)) - { - var changed = false; - var start = data.IndexOf(ComponentIdPlaceholderStart); - while (start != -1) - { - var end = data.IndexOf(ComponentIdPlaceholderEnd, start + 1); - if (end == -1) - { - break; - } - - var placeholderId = data.Substring(start, end - start + 1); - if (this.componentIdPlaceholders.TryGetValue(placeholderId, out var value)) - { - var sb = new StringBuilder(data); - sb.Remove(start, end - start + 1); - sb.Insert(start, value); - - data = sb.ToString(); - changed = true; - - end = start + value.Length; - } - - start = data.IndexOf(ComponentIdPlaceholderStart, end); - } - - if (changed) - { - field.Overwrite(data); - } - } - } - } - } - } - } - } - - /// - /// Uppercases the first character of a string. - /// - /// String to uppercase first character of. - /// String with first character uppercased. - private static string UppercaseFirstChar(string s) - { - if (0 == s.Length) - { - return s; - } - - return String.Concat(s.Substring(0, 1).ToUpperInvariant(), s.Substring(1)); - } - - /// - /// Lowercases the string if present. - /// - /// String to lowercase. - /// Null if the string is null, otherwise returns the lowercase. - private static string LowercaseOrNull(string s) - { - return s?.ToLowerInvariant(); - } - - /// - /// Adds a search property to the active section. - /// - /// Current source/line number of processing. - /// Property to add to search. - /// Signature for search. - private void AddAppSearch(SourceLineNumber sourceLineNumbers, Identifier propertyId, string signature) - { - if (!this.Core.EncounteredError) - { - if (propertyId.Id != propertyId.Id.ToUpperInvariant()) - { - this.Core.Write(ErrorMessages.SearchPropertyNotUppercase(sourceLineNumbers, "Property", "Id", propertyId.Id)); - } - - this.Core.AddSymbol(new AppSearchSymbol(sourceLineNumbers, new Identifier(propertyId.Access, propertyId.Id, signature)) - { - PropertyRef = propertyId.Id, - SignatureRef = signature - }); - } - } - - /// - /// Adds a property to the active section. - /// - /// Current source/line number of processing. - /// Identifier of property to add. - /// Value of property. - /// Flag if property is an admin property. - /// Flag if property is a secure property. - /// Flag if property is to be hidden. - /// Adds the property to a new section. - private void AddProperty(SourceLineNumber sourceLineNumbers, Identifier propertyId, string value, bool admin, bool secure, bool hidden, bool fragment) - { - // properties without a valid identifier should not be processed any further - if (null == propertyId || String.IsNullOrEmpty(propertyId.Id)) - { - return; - } - - if (!String.IsNullOrEmpty(value)) - { - var start = value.IndexOf('['); - while (start != -1 && start < value.Length) - { - var end = value.IndexOf(']', start + 1); - if (end == -1) - { - break; - } - - var id = value.Substring(start + 1, end - 1); - if (Common.IsIdentifier(id)) - { - this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, id)); - } - - start = (end < value.Length) ? value.IndexOf('[', end + 1) : -1; - } - } - - if (!this.Core.EncounteredError) - { - var section = this.Core.ActiveSection; - - // Add the symbol to a separate section if requested. - if (fragment) - { - var id = String.Concat(this.Core.ActiveSection.Id, ".", propertyId.Id); - - section = this.Core.CreateSection(id, SectionType.Fragment, this.Context.CompilationId); - - // Reference the property in the active section. - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, propertyId.Id); - } - - // Allow symbol to exist with no value so that PropertyRefs can be made for *Search elements - // the linker will remove these symbols before the final output is created. - section.AddSymbol(new PropertySymbol(sourceLineNumbers, propertyId) - { - Value = value, - }); - - if (admin || hidden || secure) - { - this.AddWixPropertySymbol(sourceLineNumbers, propertyId, admin, secure, hidden, section); - } - } - } - - private void AddWixPropertySymbol(SourceLineNumber sourceLineNumbers, Identifier property, bool admin, bool secure, bool hidden, IntermediateSection section = null) - { - if (secure && property.Id != property.Id.ToUpperInvariant()) - { - this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, "Property", "Id", property.Id)); - } - - if (null == section) - { - section = this.Core.ActiveSection; - - this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Property); // Property table is always required when using WixProperty table. - } - - section.AddSymbol(new WixPropertySymbol(sourceLineNumbers) - { - PropertyRef = property.Id, - Admin = admin, - Hidden = hidden, - Secure = secure - }); - } - - /// - /// Adds a "implemented category" registry key to active section. - /// - /// Current source/line number of processing. - /// GUID for category. - /// ClassId for to mark "implemented". - /// Identifier of parent component. - private void RegisterImplementedCategories(SourceLineNumber sourceLineNumbers, string categoryId, string classId, string componentId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Implemented Categories\\", categoryId), "*", null, componentId); - } - - /// - /// Parses an application identifer element. - /// - /// Element to parse. - /// Identifier of parent component. - /// The required advertise state (set depending upon the parent). - /// Optional file identifier for CLSID when not advertised. - /// Optional TypeLib GUID for CLSID. - /// Optional TypeLib Version for CLSID Interfaces (if any). - private void ParseAppIdElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string appId = null; - string remoteServerName = null; - string localService = null; - string serviceParameters = null; - string dllSurrogate = null; - bool? activateAtStorage = null; - var appIdAdvertise = YesNoType.NotSet; - bool? runAsInteractiveUser = null; - string description = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "ActivateAtStorage": - activateAtStorage = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Advertise": - appIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DllSurrogate": - dllSurrogate = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "LocalService": - localService = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "RemoteServerName": - remoteServerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "RunAsInteractiveUser": - runAsInteractiveUser = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ServiceParameters": - serviceParameters = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == appId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if ((YesNoType.No == advertise && YesNoType.Yes == appIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == appIdAdvertise)) - { - this.Core.Write(ErrorMessages.AppIdIncompatibleAdvertiseState(sourceLineNumbers, node.Name.LocalName, "Advertise", appIdAdvertise.ToString(), advertise.ToString())); - } - else - { - advertise = appIdAdvertise; - } - - // if the advertise state has not been set, default to non-advertised - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Class": - this.ParseClassElement(child, componentId, advertise, fileServer, typeLibId, typeLibVersion, appId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (YesNoType.Yes == advertise) - { - if (null != description) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "Description")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new AppIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, appId)) - { - AppId = appId, - RemoteServerName = remoteServerName, - LocalService = localService, - ServiceParameters = serviceParameters, - DllSurrogate = dllSurrogate, - ActivateAtStorage = activateAtStorage, - RunAsInteractiveUser = runAsInteractiveUser, - }); - } - } - else if (YesNoType.No == advertise) - { - if (null != description) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), null, description, componentId); - } - else - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "+", null, componentId); - } - - if (null != remoteServerName) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RemoteServerName", remoteServerName, componentId); - } - - if (null != localService) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "LocalService", localService, componentId); - } - - if (null != serviceParameters) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ServiceParameters", serviceParameters, componentId); - } - - if (null != dllSurrogate) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "DllSurrogate", dllSurrogate, componentId); - } - - if (true == activateAtStorage) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ActivateAtStorage", "Y", componentId); - } - - if (true == runAsInteractiveUser) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RunAs", "Interactive User", componentId); - } - } - } - - /// - /// Parses an AssemblyName element. - /// - /// File element to parse. - /// Parent's component id. - private void ParseAssemblyName(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiAssemblyNameSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, componentId, id)) - { - ComponentRef = componentId, - Name = id, - Value = value, - }); - } - } - - /// - /// Parses a binary element. - /// - /// Element to parse. - /// Identifier for the new row. - private Identifier ParseBinaryElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string sourceFile = null; - var suppressModularization = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SuppressModularization": - suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (!String.IsNullOrEmpty(id.Id)) // only check legal values - { - if (55 < id.Id.Length) - { - this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 55)); - } - else if (!this.compilingProduct) // if we're not doing a product then we can't be sure that a binary identifier will fit when modularized - { - if (18 < id.Id.Length) - { - this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 18)); - } - } - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new BinarySymbol(sourceLineNumbers, id) - { - Data = new IntermediateFieldPathValue { Path = sourceFile } - }); - - if (YesNoType.Yes == suppressModularization) - { - this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) - { - SuppressIdentifier = id.Id - }); - } - } - - return id; - } - - /// - /// Parses an icon element. - /// - /// Element to parse. - /// Identifier for the new row. - private string ParseIconElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (!String.IsNullOrEmpty(id.Id)) // only check legal values - { - if (57 < id.Id.Length) - { - this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 57)); - } - else if (!this.compilingProduct) // if we're not doing a product then we can't be sure that a binary identifier will fit when modularized - { - if (20 < id.Id.Length) - { - this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 20)); - } - } - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new IconSymbol(sourceLineNumbers, id) - { - Data = new IntermediateFieldPathValue { Path = sourceFile }, - }); - } - - return id.Id; - } - - /// - /// Parses an InstanceTransforms element. - /// - /// Element to parse. - private void ParseInstanceTransformsElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string property = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Property": - property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == property) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - - // find unexpected child elements - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Instance": - this.ParseInstanceElement(child, property); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses an instance element. - /// - /// Element to parse. - /// Identifier of instance property. - private void ParseInstanceElement(XElement node, string propertyId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string productCode = null; - string productName = null; - string upgradeCode = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ProductCode": - productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); - break; - case "ProductName": - productName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UpgradeCode": - upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == productCode) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductCode")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixInstanceTransformsSymbol(sourceLineNumbers, id) - { - PropertyId = propertyId, - ProductCode = productCode, - ProductName = productName, - UpgradeCode = upgradeCode - }); - } - } - - /// - /// Parses a category element. - /// - /// Element to parse. - /// Identifier of parent component. - private void ParseCategoryElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string appData = null; - string feature = null; - string qualifier = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "AppData": - appData = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Feature": - feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); - break; - case "Qualifier": - qualifier = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == qualifier) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Qualifier")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new PublishComponentSymbol(sourceLineNumbers) - { - ComponentId = id, - Qualifier = qualifier, - ComponentRef = componentId, - AppData = appData, - FeatureRef = feature ?? Guid.Empty.ToString("B"), - }); - } - } - - /// - /// Parses a class element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional Advertise State for the parent AppId element (if any). - /// Optional file identifier for CLSID when not advertised. - /// Optional TypeLib GUID for CLSID. - /// Optional TypeLib Version for CLSID Interfaces (if any). - /// Optional parent AppId. - private void ParseClassElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion, string parentAppId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - string appId = null; - string argument = null; - var class16bit = false; - var class32bit = false; - string classId = null; - var classAdvertise = YesNoType.NotSet; - var contexts = new string[0]; - string formattedContextString = null; - var control = false; - string defaultInprocHandler = null; - string defaultProgId = null; - string description = null; - string fileTypeMask = null; - string foreignServer = null; - string icon = null; - var iconIndex = CompilerConstants.IntegerNotSet; - string insertable = null; - string localFileServer = null; - var programmable = false; - var relativePath = YesNoType.NotSet; - var safeForInit = false; - var safeForScripting = false; - var shortServerPath = false; - string threadingModel = null; - string version = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Advertise": - classAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "AppId": - appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Argument": - argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Context": - contexts = this.Core.GetAttributeValue(sourceLineNumbers, attrib).Split("\r\n\t ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); - break; - case "Control": - control = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Handler": - defaultInprocHandler = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Icon": - icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "IconIndex": - iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); - break; - case "RelativePath": - relativePath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - - // The following attributes result in rows always added to the Registry table rather than the Class table - case "Insertable": - insertable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? "Insertable" : "NotInsertable"; - break; - case "Programmable": - programmable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SafeForInitializing": - safeForInit = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SafeForScripting": - safeForScripting = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ForeignServer": - foreignServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Server": - localFileServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ShortPath": - shortServerPath = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ThreadingModel": - threadingModel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Version": - version = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == classId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - var uniqueContexts = new HashSet(); - foreach (var context in contexts) - { - if (uniqueContexts.Contains(context)) - { - this.Core.Write(ErrorMessages.DuplicateContextValue(sourceLineNumbers, context)); - } - else - { - uniqueContexts.Add(context); - } - - if (context.EndsWith("32", StringComparison.Ordinal)) - { - class32bit = true; - } - else - { - class16bit = true; - } - } - - if ((YesNoType.No == advertise && YesNoType.Yes == classAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == classAdvertise)) - { - this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, classAdvertise.ToString(), advertise.ToString())); - } - else - { - advertise = classAdvertise; - } - - // If the advertise state has not been set, default to non-advertised. - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - if (YesNoType.Yes == advertise && 0 == contexts.Length) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Context", "Advertise", "yes")); - } - - if (!String.IsNullOrEmpty(parentAppId) && !String.IsNullOrEmpty(appId)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AppId", node.Parent.Name.LocalName)); - } - - if (!String.IsNullOrEmpty(localFileServer)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, localFileServer); - } - - // Local variables used strictly for child node processing. - var fileTypeMaskIndex = 0; - var firstProgIdForClass = YesNoType.Yes; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "FileTypeMask": - if (YesNoType.Yes == advertise) - { - fileTypeMask = String.Concat(fileTypeMask, null == fileTypeMask ? String.Empty : ";", this.ParseFileTypeMaskElement(child)); - } - else if (YesNoType.No == advertise) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.CreateRegistryRow(childSourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("FileType\\", classId, "\\", fileTypeMaskIndex.ToString()), String.Empty, this.ParseFileTypeMaskElement(child), componentId); - fileTypeMaskIndex++; - } - break; - case "Interface": - this.ParseInterfaceElement(child, componentId, class16bit ? classId : null, class32bit ? classId : null, typeLibId, typeLibVersion); - break; - case "ProgId": - { - var foundExtension = false; - var progId = this.ParseProgIdElement(child, componentId, advertise, classId, description, null, ref foundExtension, firstProgIdForClass); - if (null == defaultProgId) - { - defaultProgId = progId; - } - firstProgIdForClass = YesNoType.No; - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - // If this Class is being advertised. - if (YesNoType.Yes == advertise) - { - if (null != fileServer || null != localFileServer) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Server", "Advertise", "yes")); - } - - if (null != foreignServer) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Advertise", "yes")); - } - - if (null == appId && null != parentAppId) - { - appId = parentAppId; - } - - // add a Class row for each context - if (!this.Core.EncounteredError) - { - foreach (var context in contexts) - { - var symbol = this.Core.AddSymbol(new ClassSymbol(sourceLineNumbers) - { - CLSID = classId, - Context = context, - ComponentRef = componentId, - DefaultProgIdRef = defaultProgId, - Description = description, - FileTypeMask = fileTypeMask, - DefInprocHandler = defaultInprocHandler, - Argument = argument, - FeatureRef = Guid.Empty.ToString("B"), - RelativePath = YesNoType.Yes == relativePath, - }); - - if (null != appId) - { - symbol.AppIdRef = appId; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.AppId, appId); - } - - if (null != icon) - { - symbol.IconRef = icon; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); - } - - if (CompilerConstants.IntegerNotSet != iconIndex) - { - symbol.IconIndex = iconIndex; - } - } - } - } - else if (YesNoType.No == advertise) - { - if (null == fileServer && null == localFileServer && null == foreignServer) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server")); - } - - if (null != fileServer && null != foreignServer) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "File")); - } - else if (null != localFileServer && null != foreignServer) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server")); - } - else if (null == fileServer) - { - fileServer = localFileServer; - } - - if (null != appId) // need to use nesting (not a reference) for the unadvertised Class elements - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AppId", "Advertise", "no")); - } - - // add the core registry keys for each context in the class - foreach (var context in contexts) - { - if (context.StartsWith("InprocServer", StringComparison.Ordinal)) // dll server - { - if (null != argument) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Arguments", "Context", context)); - } - - if (null != fileServer) - { - formattedContextString = String.Concat("[", shortServerPath ? "!" : "#", fileServer, "]"); - } - else if (null != foreignServer) - { - formattedContextString = foreignServer; - } - } - else if (context.StartsWith("LocalServer", StringComparison.Ordinal)) // exe server (quote the long path) - { - if (null != fileServer) - { - if (shortServerPath) - { - formattedContextString = String.Concat("[!", fileServer, "]"); - } - else - { - formattedContextString = String.Concat("\"[#", fileServer, "]\""); - } - } - else if (null != foreignServer) - { - formattedContextString = foreignServer; - } - - if (null != argument) - { - formattedContextString = String.Concat(formattedContextString, " ", argument); - } - } - else - { - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Context", context, "InprocServer", "InprocServer32", "LocalServer", "LocalServer32")); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), String.Empty, formattedContextString, componentId); // ClassId context - - if (null != icon) // ClassId default icon - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); - - icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); - - if (CompilerConstants.IntegerNotSet != iconIndex) - { - icon = String.Concat(icon, ",", iconIndex); - } - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\DefaultIcon"), String.Empty, icon, componentId); - } - } - - if (null != parentAppId) // ClassId AppId (must be specified via nesting, not with the AppId attribute) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), "AppID", parentAppId, componentId); - } - - if (null != description) // ClassId description - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), String.Empty, description, componentId); - } - - if (null != defaultInprocHandler) - { - switch (defaultInprocHandler) // ClassId Default Inproc Handler - { - case "1": - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId); - break; - case "2": - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId); - break; - case "3": - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId); - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId); - break; - default: - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, defaultInprocHandler, componentId); - break; - } - } - - if (YesNoType.NotSet != relativePath) // ClassId's RelativePath - { - this.Core.Write(ErrorMessages.RelativePathForRegistryElement(sourceLineNumbers)); - } - } - - if (null != threadingModel) - { - threadingModel = Compiler.UppercaseFirstChar(threadingModel); - - // add a threading model for each context in the class - foreach (var context in contexts) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), "ThreadingModel", threadingModel, componentId); - } - } - - if (null != typeLibId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\TypeLib"), null, typeLibId, componentId); - } - - if (null != version) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Version"), null, version, componentId); - } - - if (null != insertable) - { - // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", insertable), "*", null, componentId); - } - - if (control) - { - // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Control"), "*", null, componentId); - } - - if (programmable) - { - // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Programmable"), "*", null, componentId); - } - - if (safeForInit) - { - this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95802-9882-11CF-9FA9-00AA006C42C4}", classId, componentId); - } - - if (safeForScripting) - { - this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95801-9882-11CF-9FA9-00AA006C42C4}", classId, componentId); - } - } - - /// - /// Parses an Interface element. - /// - /// Element to parse. - /// Identifier of parent component. - /// 16-bit proxy for interface. - /// 32-bit proxy for interface. - /// Optional TypeLib GUID for CLSID. - /// Version of the TypeLib to which this interface belongs. Required if typeLibId is specified - private void ParseInterfaceElement(XElement node, string componentId, string proxyId, string proxyId32, string typeLibId, string typelibVersion) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string baseInterface = null; - string interfaceId = null; - string name = null; - var numMethods = CompilerConstants.IntegerNotSet; - var versioned = true; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - interfaceId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "BaseInterface": - baseInterface = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "NumMethods": - numMethods = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "ProxyStubClassId": - proxyId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib); - break; - case "ProxyStubClassId32": - proxyId32 = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Versioned": - versioned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == interfaceId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId), null, name, componentId); - if (null != typeLibId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), null, typeLibId, componentId); - if (versioned) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), "Version", typelibVersion, componentId); - } - } - - if (null != baseInterface) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\BaseInterface"), null, baseInterface, componentId); - } - - if (CompilerConstants.IntegerNotSet != numMethods) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\NumMethods"), null, numMethods.ToString(), componentId); - } - - if (null != proxyId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid"), null, proxyId, componentId); - } - - if (null != proxyId32) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid32"), null, proxyId32, componentId); - } - } - - /// - /// Parses a CLSID's file type mask element. - /// - /// Element to parse. - /// String representing the file type mask elements. - private string ParseFileTypeMaskElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var cb = 0; - var offset = CompilerConstants.IntegerNotSet; - string mask = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Mask": - mask = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Offset": - offset = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - - if (null == mask) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Mask")); - } - - if (CompilerConstants.IntegerNotSet == offset) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - if (mask.Length != value.Length) - { - this.Core.Write(ErrorMessages.ValueAndMaskMustBeSameLength(sourceLineNumbers)); - } - cb = mask.Length / 2; - } - - return String.Concat(offset.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", cb.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", mask, ",", value); - } - - /// - /// Parses a product search element. - /// - /// Element to parse. - /// - /// Signature for search element. - private void ParseProductSearchElement(XElement node, string propertyId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - string upgradeCode = null; - string language = null; - string maximum = null; - string minimum = null; - var excludeLanguages = false; - var maxInclusive = false; - var minInclusive = true; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ExcludeLanguages": - excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IncludeMaximum": - maxInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IncludeMinimum": - minInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Language": - language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Minimum": - minimum = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Maximum": - maximum = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UpgradeCode": - upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == minimum && null == maximum) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeCode, - VersionMin = minimum, - VersionMax = maximum, - Language = language, - ActionProperty = propertyId, - OnlyDetect = true, - ExcludeLanguages = excludeLanguages, - VersionMaxInclusive = maxInclusive, - VersionMinInclusive = minInclusive, - }); - } - } - - /// - /// Parses a registry search element. - /// - /// Element to parse. - /// Signature for search element. - private string ParseRegistrySearchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string name = null; - RegistryRootType? root = null; - RegLocatorType? type = null; - var search64bit = this.Context.IsCurrentPlatform64Bit; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Bitness": - var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (bitnessValue) - { - case "always32": - search64bit = false; - break; - case "always64": - search64bit = true; - break; - case "default": - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); - break; - } - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, false); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "directory": - type = RegLocatorType.Directory; - break; - case "file": - type = RegLocatorType.FileName; - break; - case "raw": - type = RegLocatorType.Raw; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "raw")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("reg", root.ToString(), key, name, type.ToString(), search64bit.ToString()); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (!type.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); - } - - var signature = id.Id; - var oneChild = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - - // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column - signature = this.ParseDirectorySearchElement(child, id.Id); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, id.Id); - break; - case "FileSearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); - id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures - break; - case "FileSearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures - id = new Identifier(AccessModifier.Section, newId); - signature = null; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RegLocatorSymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - Type = type.Value, - Win64 = search64bit, - }); - } - - return signature; - } - - /// - /// Parses a registry search reference element. - /// - /// Element to parse. - /// Signature of referenced search element. - private string ParseRegistrySearchRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.RegLocator, id); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return id; // the id of the RegistrySearchRef element is its signature - } - - /// - /// Parses child elements for search signatures. - /// - /// Node whose children we are parsing. - /// Returns list of string signatures. - private List ParseSearchSignatures(XElement node) - { - var signatures = new List(); - - foreach (var child in node.Elements()) - { - string signature = null; - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ComplianceDrive": - signature = this.ParseComplianceDriveElement(child); - break; - case "ComponentSearch": - signature = this.ParseComponentSearchElement(child); - break; - case "DirectorySearch": - signature = this.ParseDirectorySearchElement(child, String.Empty); - break; - case "DirectorySearchRef": - signature = this.ParseDirectorySearchRefElement(child, String.Empty); - break; - case "IniFileSearch": - signature = this.ParseIniFileSearchElement(child); - break; - case "ProductSearch": - // handled in ParsePropertyElement - break; - case "RegistrySearch": - signature = this.ParseRegistrySearchElement(child); - break; - case "RegistrySearchRef": - signature = this.ParseRegistrySearchRefElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - - - if (!String.IsNullOrEmpty(signature)) - { - signatures.Add(signature); - } - } - - return signatures; - } - - /// - /// Parses a compliance drive element. - /// - /// Element to parse. - /// Signature of nested search elements. - private string ParseComplianceDriveElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string signature = null; - - var oneChild = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchElement(child, "CCP_DRIVE"); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, "CCP_DRIVE"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null == signature) - { - this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName)); - } - - return signature; - } - - /// - /// Parses a compilance check element. - /// - /// Element to parse. - private void ParseComplianceCheckElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - string signature = null; - - // see if this property is used for appSearch - var signatures = this.ParseSearchSignatures(node); - foreach (var sig in signatures) - { - // if we haven't picked a signature for this ComplianceCheck pick - // this one - if (null == signature) - { - signature = sig; - } - else if (signature != sig) - { - // all signatures under a ComplianceCheck must be the same - this.Core.Write(ErrorMessages.MultipleIdentifiersFound(sourceLineNumbers, node.Name.LocalName, sig, signature)); - } - } - - if (null == signature) - { - this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName)); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, signature))); - } - } - - /// - /// Parses a component element. - /// - /// Element to parse. - /// Type of component's complex reference parent. Will be Uknown if there is no parent. - /// Optional identifier for component's primary parent. - /// Optional string for component's parent's language. - /// Optional disk id inherited from parent directory. - /// Optional identifier for component's directory. - /// Optional source path for files up to this point. - private void ParseComponentElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage, int diskId, string directoryId, string srcPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - var comPlusBits = CompilerConstants.IntegerNotSet; - string condition = null; - string subdirectory = null; - var encounteredODBCDataSource = false; - var files = 0; - var guid = "*"; - Identifier id = null; - string componentIdPlaceholder = null; - var keyFound = false; - string keyPath = null; - - var keyPathType = ComponentKeyPathType.Directory; - var location = ComponentLocation.LocalOnly; - var disableRegistryReflection = false; - - var neverOverwrite = false; - var permanent = false; - var shared = false; - var sharedDllRefCount = false; - var transitive = false; - var uninstallWhenSuperseded = false; - var win64 = this.Context.IsCurrentPlatform64Bit; - - var multiInstance = false; - var symbols = new List(); - string feature = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Bitness": - var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (bitnessValue) - { - case "always32": - win64 = false; - break; - case "always64": - win64 = true; - break; - case "default": - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); - break; - } - break; - case "ComPlusFlags": - comPlusBits = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "DisableRegistryReflection": - disableRegistryReflection = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "Feature": - feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Guid": - guid = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true, true); - break; - case "KeyPath": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - keyFound = true; - keyPath = null; - } - break; - case "Location": - var locationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (locationValue) - { - case "either": - location = ComponentLocation.Either; - break; - case "local": // this is the default - location = ComponentLocation.LocalOnly; - break; - case "source": - location = ComponentLocation.SourceOnly; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, locationValue, "either", "local", "source")); - break; - } - break; - case "MultiInstance": - multiInstance = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "NeverOverwrite": - neverOverwrite = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Permanent": - permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Shared": - shared = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SharedDllRefCount": - sharedDllRefCount = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Transitive": - transitive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "UninstallWhenSuperseded": - uninstallWhenSuperseded = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (id == null) - { - // Placeholder id for defaulting Component/@Id to keypath id. - componentIdPlaceholder = String.Concat(Compiler.ComponentIdPlaceholderStart, this.componentIdPlaceholders.Count, Compiler.ComponentIdPlaceholderEnd); - id = new Identifier(AccessModifier.Section, componentIdPlaceholder); - } - - if (String.IsNullOrEmpty(directoryId)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory")); - } - - if (!String.IsNullOrEmpty(subdirectory)) - { - directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); - } - - if (String.IsNullOrEmpty(guid) && shared) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Shared", "yes", "Guid", "")); - } - - if (String.IsNullOrEmpty(guid) && permanent) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Permanent", "yes", "Guid", "")); - } - - if (null != feature) - { - if (this.compilingModule) - { - this.Core.Write(ErrorMessages.IllegalAttributeInMergeModule(sourceLineNumbers, node.Name.LocalName, "Feature")); - } - else - { - if (ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Feature", node.Parent.Name.LocalName)); - } - else - { - this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); - } - } - } - - foreach (var child in node.Elements()) - { - var keyPathSet = YesNoType.NotSet; - string keyPossible = null; - ComponentKeyPathType? keyBit = null; - - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "AppId": - this.ParseAppIdElement(child, id.Id, YesNoType.NotSet, null, null, null); - break; - case "Category": - this.ParseCategoryElement(child, id.Id); - break; - case "Class": - this.ParseClassElement(child, id.Id, YesNoType.NotSet, null, null, null, null); - break; - case "CopyFile": - this.ParseCopyFileElement(child, id.Id, null); - break; - case "CreateFolder": - var createdFolder = this.ParseCreateFolderElement(child, id.Id, directoryId, win64); - break; - case "Environment": - this.ParseEnvironmentElement(child, id.Id); - break; - case "Extension": - this.ParseExtensionElement(child, id.Id, YesNoType.NotSet, null); - break; - case "File": - keyPathSet = this.ParseFileElement(child, id.Id, directoryId, diskId, srcPath, out keyPossible, win64, guid); - keyBit = ComponentKeyPathType.File; - files++; - break; - case "IniFile": - this.ParseIniFileElement(child, id.Id); - break; - case "Interface": - this.ParseInterfaceElement(child, id.Id, null, null, null, null); - break; - case "IsolateComponent": - this.ParseIsolateComponentElement(child, id.Id); - break; - case "ODBCDataSource": - keyPathSet = this.ParseODBCDataSource(child, id.Id, null, out keyPossible); - keyBit = ComponentKeyPathType.OdbcDataSource; - encounteredODBCDataSource = true; - break; - case "ODBCDriver": - this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCDriver); - break; - case "ODBCTranslator": - this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCTranslator); - break; - case "ProgId": - var foundExtension = false; - this.ParseProgIdElement(child, id.Id, YesNoType.NotSet, null, null, null, ref foundExtension, YesNoType.NotSet); - break; - case "Provides": - if (win64) - { - this.Messaging.Write(CompilerWarnings.Win64Component(sourceLineNumbers, id.Id)); - } - - keyPathSet = this.ParseProvidesElement(child, null, id.Id, out keyPossible); - keyBit = ComponentKeyPathType.Registry; - break; - - case "RegistryKey": - keyPathSet = this.ParseRegistryKeyElement(child, id.Id, null, null, win64, out keyPossible); - keyBit = ComponentKeyPathType.Registry; - break; - case "RegistryValue": - keyPathSet = this.ParseRegistryValueElement(child, id.Id, null, null, win64, out keyPossible); - keyBit = ComponentKeyPathType.Registry; - break; - case "RemoveFile": - this.ParseRemoveFileElement(child, id.Id, directoryId); - break; - case "RemoveFolder": - this.ParseRemoveFolderElement(child, id.Id, directoryId); - break; - case "RemoveRegistryKey": - this.ParseRemoveRegistryKeyElement(child, id.Id); - break; - case "RemoveRegistryValue": - this.ParseRemoveRegistryValueElement(child, id.Id); - break; - case "ReserveCost": - this.ParseReserveCostElement(child, id.Id, directoryId); - break; - case "ServiceConfig": - this.ParseServiceConfigElement(child, id.Id, null); - break; - case "ServiceConfigFailureActions": - this.ParseServiceConfigFailureActionsElement(child, id.Id, null); - break; - case "ServiceControl": - this.ParseServiceControlElement(child, id.Id); - break; - case "ServiceInstall": - this.ParseServiceInstallElement(child, id.Id, win64); - break; - case "Shortcut": - this.ParseShortcutElement(child, id.Id, node.Name.LocalName, directoryId, YesNoType.No); - break; - case "SymbolPath": - symbols.Add(this.ParseSymbolPathElement(child)); - break; - case "TypeLib": - this.ParseTypeLibElement(child, id.Id, null, win64); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "ComponentId", id?.Id }, { "DirectoryId", directoryId }, { "Win64", win64.ToString() }, }; - var possibleKeyPath = this.Core.ParsePossibleKeyPathExtensionElement(node, child, context); - if (null != possibleKeyPath) - { - if (PossibleKeyPathType.None == possibleKeyPath.Type) - { - keyPathSet = YesNoType.No; - } - else - { - keyPathSet = possibleKeyPath.Explicit ? YesNoType.Yes : YesNoType.NotSet; - - if (!String.IsNullOrEmpty(possibleKeyPath.Id)) - { - keyPossible = possibleKeyPath.Id; - } - - if (PossibleKeyPathType.Registry == possibleKeyPath.Type || PossibleKeyPathType.RegistryFormatted == possibleKeyPath.Type) - { - keyBit = ComponentKeyPathType.Registry; //MsiInterop.MsidbComponentAttributesRegistryKeyPath; - } - } - } - } - - // Verify that either the key path is not set, or it is set along with a key path ID. - Debug.Assert(YesNoType.Yes != keyPathSet || (YesNoType.Yes == keyPathSet && null != keyPossible)); - - if (keyFound && YesNoType.Yes == keyPathSet) - { - this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, node.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); - } - - // if a possible KeyPath has been found and that value was explicitly set as - // the KeyPath of the component, set it now. Alternatively, if a possible - // KeyPath has been found and no KeyPath has been previously set, use this - // value as the default KeyPath of the component - if (!String.IsNullOrEmpty(keyPossible) && (YesNoType.Yes == keyPathSet || (YesNoType.NotSet == keyPathSet && String.IsNullOrEmpty(keyPath) && !keyFound))) - { - keyFound = YesNoType.Yes == keyPathSet; - keyPath = keyPossible; - keyPathType = keyBit.Value; - } - } - - // Check for conditions that exclude this component from using implicit ids and/or generated guids. - var allowImplicitIds = true; - if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType) - { - allowImplicitIds = false; - if (guid == "*") - { - this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers)); - } - } - else if (0 < files && ComponentKeyPathType.Registry == keyPathType) - { - allowImplicitIds = false; - if (guid == "*") - { - this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true)); - } - } - - // Check for implicit KeyPath which can easily be accidentally changed - if (this.ShowPedanticMessages && !keyFound && !allowImplicitIds) - { - this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id)); - } - - // If there isn't an @Id attribute value, replace the placeholder with the id of the keypath. - // either an explicit KeyPath="yes" attribute must be specified or requirements for - // generatable guid must be met. - if (componentIdPlaceholder == id.Id) - { - if (allowImplicitIds || keyFound && !String.IsNullOrEmpty(keyPath)) - { - this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath); - - id = new Identifier(AccessModifier.Section, keyPath); - } - else - { - this.Core.Write(ErrorMessages.CannotDefaultComponentId(sourceLineNumbers)); - } - } - - // finally add the Component table row - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) - { - ComponentId = guid, - DirectoryRef = directoryId, - Location = location, - Condition = condition, - KeyPath = keyPath, - KeyPathType = keyPathType, - DisableRegistryReflection = disableRegistryReflection, - NeverOverwrite = neverOverwrite, - Permanent = permanent, - SharedDllRefCount = sharedDllRefCount, - Shared = shared, - Transitive = transitive, - UninstallWhenSuperseded = uninstallWhenSuperseded, - Win64 = win64, - }); - - if (multiInstance) - { - this.Core.AddSymbol(new WixInstanceComponentSymbol(sourceLineNumbers, id) - { - ComponentRef = id.Id, - }); - } - - if (0 < symbols.Count) - { - this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Component, id.Id)) - { - SymbolType = SymbolPathType.Component, - SymbolId = id.Id, - SymbolPaths = String.Join(";", symbols), - }); - } - - // Complus - if (CompilerConstants.IntegerNotSet != comPlusBits) - { - this.Core.AddSymbol(new ComplusSymbol(sourceLineNumbers) - { - ComponentRef = id.Id, - ExpType = comPlusBits, - }); - } - - // if this is a module, automatically add this component to the references to ensure it gets in the ModuleComponents table - if (this.compilingModule) - { - this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, ComplexReferenceChildType.Component, id.Id, false); - } - else if (ComplexReferenceParentType.Unknown != parentType && null != parentId) // if parent was provided, add a complex reference to that. - { - // If the Component is defined directly under a feature, then mark the complex reference primary. - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id.Id, ComplexReferenceParentType.Feature == parentType); - } - } - } - - /// - /// Parses a component group element. - /// - /// Element to parse. - /// - /// - private void ParseComponentGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string directoryId = null; - string subdirectory = null; - string source = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "Source": - source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); - - if (!String.IsNullOrEmpty(source) && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - source = String.Concat(source, Path.DirectorySeparatorChar); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ComponentGroupRef": - this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null); - break; - case "ComponentRef": - this.ParseComponentRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null, CompilerConstants.IntegerNotSet, directoryId, source); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixComponentGroupSymbol(sourceLineNumbers, id)); - - // Add this componentGroup and its parent in WixGroup. - this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ComponentGroup, id.Id); - } - } - - /// - /// Parses a component group reference element. - /// - /// Element to parse. - /// ComplexReferenceParentType of parent element. - /// Identifier of parent element (usually a Feature or Module). - /// Optional language of parent (only useful for Modules). - private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) - { - Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var primary = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixComponentGroup, id); - break; - case "Primary": - primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.ComponentGroup, id, (YesNoType.Yes == primary)); - } - - /// - /// Parses a component reference element. - /// - /// Element to parse. - /// ComplexReferenceParentType of parent element. - /// Identifier of parent element (usually a Feature or Module). - /// Optional language of parent (only useful for Modules). - private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) - { - Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var primary = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, id); - break; - case "Primary": - primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id, (YesNoType.Yes == primary)); - } - - /// - /// Parses a component search element. - /// - /// Element to parse. - /// Signature for search element. - private string ParseComponentSearchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string componentId = null; - var type = LocatorType.Filename; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Guid": - componentId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "directory": - type = LocatorType.Directory; - break; - case "file": - type = LocatorType.Filename; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "directory", "file")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("cmp", componentId, type.ToString()); - } - - var signature = id.Id; - var oneChild = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - - // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column - signature = this.ParseDirectorySearchElement(child, id.Id); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, id.Id); - break; - case "FileSearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); - id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures - break; - case "FileSearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures - id = new Identifier(AccessModifier.Section, newId); - signature = null; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CompLocatorSymbol(sourceLineNumbers, id) - { - SignatureRef = id.Id, - ComponentId = componentId, - Type = type, - }); - } - - return signature; - } - - /// - /// Parses a create folder element. - /// - /// Element to parse. - /// Identifier for parent component. - /// Default identifier for directory to create. - /// true if the component is 64-bit. - /// Identifier for the directory that will be created - private string ParseCreateFolderElement(XElement node, string componentId, string directoryId, bool win64Component) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string subdirectory = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Shortcut": - this.ParseShortcutElement(child, componentId, node.Name.LocalName, directoryId, YesNoType.No); - break; - case "Permission": - this.ParsePermissionElement(child, directoryId, "CreateFolder"); - break; - case "PermissionEx": - this.ParsePermissionExElement(child, directoryId, "CreateFolder"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "DirectoryId", directoryId }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CreateFolderSymbol(sourceLineNumbers) - { - DirectoryRef = directoryId, - ComponentRef = componentId, - }); - } - - return directoryId; - } - - /// - /// Parses a copy file element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of file to copy (null if moving the file). - private void ParseCopyFileElement(XElement node, string componentId, string fileId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var delete = false; - string destinationDirectory = null; - string destinationSubdirectory = null; - string destinationName = null; - string destinationShortName = null; - string destinationProperty = null; - string sourceDirectory = null; - string sourceSubdirectory = null; - string sourceFolder = null; - string sourceName = null; - string sourceProperty = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Delete": - delete = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "DestinationDirectory": - destinationDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, destinationDirectory); - break; - case "DestinationSubdirectory": - destinationSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "DestinationName": - destinationName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib); - break; - case "DestinationProperty": - destinationProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "DestinationShortName": - destinationShortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib); - break; - case "FileId": - if (null != fileId) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); - } - fileId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, fileId); - break; - case "SourceDirectory": - sourceDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, sourceDirectory); - break; - case "SourceSubdirectory": - sourceSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "SourceName": - sourceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SourceProperty": - sourceProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null != sourceFolder && null != sourceDirectory) // SourceFolder and SourceDirectory cannot coexist - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceDirectory")); - } - - if (null != sourceFolder && null != sourceProperty) // SourceFolder and SourceProperty cannot coexist - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceProperty")); - } - - if (null != sourceDirectory && null != sourceProperty) // SourceDirectory and SourceProperty cannot coexist - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "SourceDirectory")); - } - - sourceDirectory = this.HandleSubdirectory(sourceLineNumbers, node, sourceDirectory, sourceSubdirectory, "SourceDirectory", "SourceSubdirectory"); - - if (null != destinationDirectory && null != destinationProperty) // DestinationDirectory and DestinationProperty cannot coexist - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationProperty", "DestinationDirectory")); - } - - destinationDirectory = this.HandleSubdirectory(sourceLineNumbers, node, destinationDirectory, destinationSubdirectory, "DestinationDirectory", "DestinationSubdirectory"); - - if (null == id) - { - id = this.Core.CreateIdentifier("cf", sourceFolder, sourceDirectory, sourceProperty, destinationDirectory, destinationProperty, destinationName); - } - - this.Core.ParseForExtensionElements(node); - - if (null == fileId) - { - // DestinationDirectory or DestinationProperty must be specified - if (null == destinationDirectory && null == destinationProperty) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationDirectory", "DestinationProperty", "FileId")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MoveFileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - SourceName = sourceName, - DestinationName = destinationName, - DestinationShortName = destinationShortName, - SourceFolder = sourceDirectory ?? sourceProperty, - DestFolder = destinationDirectory ?? destinationProperty, - Delete = delete, - }); - } - } - else // copy the file - { - if (null != sourceDirectory) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceDirectory", "FileId")); - } - - if (null != sourceFolder) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "FileId")); - } - - if (null != sourceName) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceName", "FileId")); - } - - if (null != sourceProperty) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "FileId")); - } - - if (delete) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Delete", "FileId")); - } - - if (null == destinationName && null == destinationDirectory && null == destinationProperty) - { - this.Core.Write(WarningMessages.CopyFileFileIdUseless(sourceLineNumbers)); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new DuplicateFileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - FileRef = fileId, - DestinationName = destinationName, - DestinationShortName = destinationShortName, - DestinationFolder = destinationDirectory ?? destinationProperty, - }); - } - } - } - - /// - /// Parses a CustomAction element. - /// - /// Element to parse. - private void ParseCustomActionElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var inlineScript = false; - var suppressModularization = YesNoType.NotSet; - string source = null; - string target = null; - var explicitWin64 = false; - - string scriptFile = null; - string subdirectory = null; - - CustomActionSourceType? sourceType = null; - CustomActionTargetType? targetType = null; - var executionType = CustomActionExecutionType.Immediate; - var hidden = false; - var impersonate = true; - var patchUninstall = false; - var tsAware = false; - var win64 = false; - var async = false; - var ignoreResult = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "BinaryRef": - if (null != source) - { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - sourceType = CustomActionSourceType.Binary; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary - break; - case "Bitness": - var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (bitnessValue) - { - case "always32": - explicitWin64 = true; - win64 = false; - break; - case "always64": - explicitWin64 = true; - win64 = true; - break; - case "default": - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); - break; - } - break; - case "Directory": - if (null != source) - { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileRef", "Property", "Script")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - sourceType = CustomActionSourceType.Directory; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, source); - break; - case "DllEntry": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - targetType = CustomActionTargetType.Dll; - break; - case "Error": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - sourceType = CustomActionSourceType.File; - targetType = CustomActionTargetType.TextData; - - // The target can be either a formatted error string or a literal - // error number. Try to convert to error number to determine whether - // to add a reference. No need to look at the value. - if (Int32.TryParse(target, out var ignored)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Error, target); - } - break; - case "ExeCommand": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid - targetType = CustomActionTargetType.Exe; - break; - case "Execute": - var execute = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (execute) - { - case "commit": - executionType = CustomActionExecutionType.Commit; - break; - case "deferred": - executionType = CustomActionExecutionType.Deferred; - break; - case "firstSequence": - executionType = CustomActionExecutionType.FirstSequence; - break; - case "immediate": - executionType = CustomActionExecutionType.Immediate; - break; - case "oncePerProcess": - executionType = CustomActionExecutionType.OncePerProcess; - break; - case "rollback": - executionType = CustomActionExecutionType.Rollback; - break; - case "secondSequence": - executionType = CustomActionExecutionType.ClientRepeat; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, execute, "commit", "deferred", "firstSequence", "immediate", "oncePerProcess", "rollback", "secondSequence")); - break; - } - break; - case "FileRef": - if (null != source) - { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - sourceType = CustomActionSourceType.File; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File - break; - case "HideTarget": - hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Impersonate": - impersonate = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "JScriptCall": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid - targetType = CustomActionTargetType.JScript; - break; - case "PatchUninstall": - patchUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Property": - if (null != source) - { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); - } - source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - sourceType = CustomActionSourceType.Property; - break; - case "Return": - var returnValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (returnValue) - { - case "asyncNoWait": - async = true; - ignoreResult = true; - break; - case "asyncWait": - async = true; - break; - case "check": - break; - case "ignore": - ignoreResult = true; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, returnValue, "asyncNoWait", "asyncWait", "check", "ignore")); - break; - } - break; - case "Script": - if (null != source) - { - this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); - } - - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - - // set the source and target to empty string for error messages when the user sets multiple sources or targets - source = String.Empty; - target = String.Empty; - - inlineScript = true; - - var script = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (script) - { - case "jscript": - sourceType = CustomActionSourceType.Directory; - targetType = CustomActionTargetType.JScript; - break; - case "vbscript": - sourceType = CustomActionSourceType.Directory; - targetType = CustomActionTargetType.VBScript; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, script, "jscript", "vbscript")); - break; - } - break; - case "ScriptSourceFile": - scriptFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "SuppressModularization": - suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "TerminalServerAware": - tsAware = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Value": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid - targetType = CustomActionTargetType.TextData; - break; - case "VBScriptCall": - if (null != target) - { - this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid - targetType = CustomActionTargetType.VBScript; - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - if (!explicitWin64 && this.Context.IsCurrentPlatform64Bit && (CustomActionTargetType.VBScript == targetType || CustomActionTargetType.JScript == targetType)) - { - win64 = true; - } - - if (!String.IsNullOrEmpty(subdirectory)) - { - if (sourceType == CustomActionSourceType.Directory) - { - source = this.HandleSubdirectory(sourceLineNumbers, node, source, subdirectory, "Directory", "Subdirectory"); - } - else - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Subdirectory", "Directory")); - } - } - - // if we have an in-lined Script CustomAction ensure no source or target attributes were provided - if (inlineScript) - { - if (String.IsNullOrEmpty(scriptFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); - } - } - else if (CustomActionTargetType.VBScript == targetType) // non-inline vbscript - { - if (null == source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "BinaryRef", "FileRef", "Property")); - } - else if (CustomActionSourceType.Directory == sourceType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "Directory")); - } - } - else if (CustomActionTargetType.JScript == targetType) // non-inline jscript - { - if (null == source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "BinaryRef", "FileRef", "Property")); - } - else if (CustomActionSourceType.Directory == sourceType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "Directory")); - } - } - else if (CustomActionTargetType.Exe == targetType) // exe-command - { - if (null == source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryRef", "Directory", "FileRef", "Property")); - } - } - else if (CustomActionTargetType.TextData == targetType && CustomActionSourceType.Directory != sourceType && CustomActionSourceType.Property != sourceType && CustomActionSourceType.File != sourceType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property", "Error")); - } - - if (!inlineScript && !String.IsNullOrEmpty(scriptFile)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); - } - - if (win64 && CustomActionTargetType.VBScript != targetType && CustomActionTargetType.JScript != targetType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Win64", "Script", "VBScriptCall", "JScriptCall")); - } - - if (async && ignoreResult && CustomActionTargetType.Exe != targetType) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Return", "asyncNoWait", "ExeCommand")); - } - - // TS-aware CAs are valid only when deferred. - if (tsAware & - CustomActionExecutionType.Deferred != executionType && - CustomActionExecutionType.Rollback != executionType && - CustomActionExecutionType.Commit != executionType) - { - this.Core.Write(ErrorMessages.IllegalTerminalServerCustomActionAttributes(sourceLineNumbers)); - } - - // MSI doesn't support in-script property setting, so disallow it - if (CustomActionSourceType.Property == sourceType && - CustomActionTargetType.TextData == targetType && - (CustomActionExecutionType.Deferred == executionType || - CustomActionExecutionType.Rollback == executionType || - CustomActionExecutionType.Commit == executionType)) - { - this.Core.Write(ErrorMessages.IllegalPropertyCustomActionAttributes(sourceLineNumbers)); - } - - if (!targetType.HasValue /*0 == targetBits*/) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, id) - { - ExecutionType = executionType, - Source = source, - SourceType = sourceType.Value, - Target = target, - TargetType = targetType.Value, - Async = async, - IgnoreResult = ignoreResult, - Impersonate = impersonate, - PatchUninstall = patchUninstall, - TSAware = tsAware, - Win64 = win64, - Hidden = hidden, - ScriptFile = new IntermediateFieldPathValue { Path = scriptFile } - }); - - if (YesNoType.Yes == suppressModularization) - { - this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) - { - SuppressIdentifier = id.Id - }); - } - } - } - - /// - /// Parses a simple reference element. - /// - /// Element to parse. - /// Symbol which contains the target of the simple reference. - /// Id of the referenced element. - private string ParseSimpleRefElement(XElement node, IntermediateSymbolDefinition symbolDefinition) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, id); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return id; - } - - /// - /// Parses a PatchFamilyRef element. - /// - /// Element to parse. - /// The parent type. - /// The ID of the parent. - /// Id of the referenced element. - private void ParsePatchFamilyRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var primaryKeys = new string[2]; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - primaryKeys[0] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "ProductCode": - primaryKeys[1] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == primaryKeys[0]) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.MsiPatchSequence, primaryKeys); - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, primaryKeys[0], true); - } - } - - /// - /// Parses an ensure table element. - /// - /// Element to parse. - private void ParseEnsureTableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (31 < id.Length) - { - this.Core.Write(ErrorMessages.TableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id)); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.EnsureTable(sourceLineNumbers, id); - } - - /// - /// Parses a custom table element. - /// - /// Element to parse. - /// not cleaned - private void ParseCustomTableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string tableId = null; - var unreal = false; - var columns = new List(); - var foundColumns = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Unreal": - unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == tableId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (31 < tableId.Length) - { - this.Core.Write(ErrorMessages.CustomTableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", tableId)); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "Column": - foundColumns = true; - - var column = this.ParseColumnElement(child, childSourceLineNumbers, tableId); - if (column != null) - { - columns.Add(column); - } - break; - case "Row": - this.ParseRowElement(child, childSourceLineNumbers, tableId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (columns.Count > 0) - { - if (!columns.Where(c => c.PrimaryKey).Any()) - { - this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers)); - } - - if (!this.Core.EncounteredError) - { - var columnNames = String.Join(new string(WixCustomTableSymbol.ColumnNamesSeparator, 1), columns.Select(c => c.Name)); - - this.Core.AddSymbol(new WixCustomTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, tableId)) - { - ColumnNames = columnNames, - Unreal = unreal, - }); - } - else if (!foundColumns) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Column")); - } - } - } - - /// - /// Parses a CustomTableRef element. - /// - /// Element to parse. - private void ParseCustomTableRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string tableId = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == tableId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "Row": - this.ParseRowElement(child, childSourceLineNumbers, tableId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses a Column element. - /// - /// Element to parse. - /// Element's SourceLineNumbers. - /// Table Id. - private WixCustomTableColumnSymbol ParseColumnElement(XElement child, SourceLineNumber childSourceLineNumbers, string tableId) - { - string columnName = null; - IntermediateFieldType? columnType = null; - var description = String.Empty; - int? keyColumn = null; - var keyTable = String.Empty; - var localizable = false; - long? maxValue = null; - long? minValue = null; - WixCustomTableColumnCategoryType? category = null; - var modularization = WixCustomTableColumnModularizeType.None; - var nullable = false; - var primaryKey = false; - var setValues = String.Empty; - var columnUnreal = false; - var width = 0; - - foreach (var childAttrib in child.Attributes()) - { - switch (childAttrib.Name.LocalName) - { - case "Id": - columnName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, childAttrib); - break; - case "Category": - var categoryValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (categoryValue) - { - case "text": - category = WixCustomTableColumnCategoryType.Text; - break; - case "upperCase": - category = WixCustomTableColumnCategoryType.UpperCase; - break; - case "lowerCase": - category = WixCustomTableColumnCategoryType.LowerCase; - break; - case "integer": - category = WixCustomTableColumnCategoryType.Integer; - break; - case "doubleInteger": - category = WixCustomTableColumnCategoryType.DoubleInteger; - break; - case "timeDate": - category = WixCustomTableColumnCategoryType.TimeDate; - break; - case "identifier": - category = WixCustomTableColumnCategoryType.Identifier; - break; - case "property": - category = WixCustomTableColumnCategoryType.Property; - break; - case "filename": - category = WixCustomTableColumnCategoryType.Filename; - break; - case "wildCardFilename": - category = WixCustomTableColumnCategoryType.WildCardFilename; - break; - case "path": - category = WixCustomTableColumnCategoryType.Path; - break; - case "paths": - category = WixCustomTableColumnCategoryType.Paths; - break; - case "anyPath": - category = WixCustomTableColumnCategoryType.AnyPath; - break; - case "defaultDir": - category = WixCustomTableColumnCategoryType.DefaultDir; - break; - case "regPath": - category = WixCustomTableColumnCategoryType.RegPath; - break; - case "formatted": - category = WixCustomTableColumnCategoryType.Formatted; - break; - case "formattedSddl": - category = WixCustomTableColumnCategoryType.FormattedSddl; - break; - case "template": - category = WixCustomTableColumnCategoryType.Template; - break; - case "condition": - category = WixCustomTableColumnCategoryType.Condition; - break; - case "guid": - category = WixCustomTableColumnCategoryType.Guid; - break; - case "version": - category = WixCustomTableColumnCategoryType.Version; - break; - case "language": - category = WixCustomTableColumnCategoryType.Language; - break; - case "binary": - category = WixCustomTableColumnCategoryType.Binary; - break; - case "customSource": - category = WixCustomTableColumnCategoryType.CustomSource; - break; - case "cabinet": - category = WixCustomTableColumnCategoryType.Cabinet; - break; - case "shortcut": - category = WixCustomTableColumnCategoryType.Shortcut; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Category", categoryValue, - "text", "upperCase", "lowerCase", "integer", "doubleInteger", "timeDate", "identifier", "property", "filename", - "wildCardFilename", "path", "paths", "anyPath", "defaultDir", "regPath", "formatted", "formattedSddl", "template", - "condition", "guid", "version", "language", "binary", "customSource", "cabinet", "shortcut")); - columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. - break; - } - break; - case "Description": - description = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - break; - case "KeyColumn": - keyColumn = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 1, 32); - break; - case "KeyTable": - keyTable = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - break; - case "Localizable": - localizable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); - break; - case "MaxValue": - maxValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); - break; - case "MinValue": - minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); - break; - case "Modularize": - var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (modularizeValue) - { - case "column": - modularization = WixCustomTableColumnModularizeType.Column; - break; - case "companionFile": - modularization = WixCustomTableColumnModularizeType.CompanionFile; - break; - case "condition": - modularization = WixCustomTableColumnModularizeType.Condition; - break; - case "controlEventArgument": - modularization = WixCustomTableColumnModularizeType.ControlEventArgument; - break; - case "controlText": - modularization = WixCustomTableColumnModularizeType.ControlText; - break; - case "icon": - modularization = WixCustomTableColumnModularizeType.Icon; - break; - case "none": - modularization = WixCustomTableColumnModularizeType.None; - break; - case "property": - modularization = WixCustomTableColumnModularizeType.Property; - break; - case "semicolonDelimited": - modularization = WixCustomTableColumnModularizeType.SemicolonDelimited; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited")); - columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. - break; - } - break; - case "Nullable": - nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); - break; - case "PrimaryKey": - primaryKey = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); - break; - case "Set": - setValues = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (typeValue) - { - case "binary": - columnType = IntermediateFieldType.Path; - break; - case "int": - columnType = IntermediateFieldType.Number; - break; - case "string": - columnType = IntermediateFieldType.String; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string")); - columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. - break; - } - break; - case "Width": - width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue); - break; - case "Unreal": - columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); - break; - default: - this.Core.UnexpectedAttribute(child, childAttrib); - break; - } - } - - if (null == columnName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); - } - - if (!columnType.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type")); - } - else if (columnType == IntermediateFieldType.Number) - { - if (2 != width && 4 != width) - { - this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width)); - } - } - else if (columnType == IntermediateFieldType.Path) - { - if (!category.HasValue) - { - category = WixCustomTableColumnCategoryType.Binary; - } - else if (category != WixCustomTableColumnCategoryType.Binary) - { - this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); - } - } - - this.Core.ParseForExtensionElements(child); - - if (this.Core.EncounteredError) - { - return null; - } - - var attributes = primaryKey ? WixCustomTableColumnSymbolAttributes.PrimaryKey : WixCustomTableColumnSymbolAttributes.None; - attributes |= localizable ? WixCustomTableColumnSymbolAttributes.Localizable : WixCustomTableColumnSymbolAttributes.None; - attributes |= nullable ? WixCustomTableColumnSymbolAttributes.Nullable : WixCustomTableColumnSymbolAttributes.None; - attributes |= columnUnreal ? WixCustomTableColumnSymbolAttributes.Unreal : WixCustomTableColumnSymbolAttributes.None; - - var column = this.Core.AddSymbol(new WixCustomTableColumnSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, columnName)) - { - TableRef = tableId, - Name = columnName, - Type = columnType.Value, - Attributes = attributes, - Width = width, - Category = category, - Description = description, - KeyColumn = keyColumn, - KeyTable = keyTable, - MaxValue = maxValue, - MinValue = minValue, - Modularize = modularization, - Set = setValues, - }); - return column; - } - - /// - /// Parses a Row element. - /// - /// Element to parse. - /// Element's SourceLineNumbers. - /// Table Id. - private void ParseRowElement(XElement node, SourceLineNumber sourceLineNumbers, string tableId) - { - var rowId = Guid.NewGuid().ToString("N").ToUpperInvariant(); - - foreach (var attrib in node.Attributes()) - { - this.Core.ParseExtensionAttribute(node, attrib); - } - - foreach (var child in node.Elements()) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "Data": - string columnName = null; - string data = null; - foreach (var attrib in child.Attributes()) - { - switch (attrib.Name.LocalName) - { - case "Column": - columnName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - case "Value": - data = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - default: - this.Core.ParseExtensionAttribute(child, attrib); - break; - } - } - - this.Core.InnerTextDisallowed(node); - - if (null == columnName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Column")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixCustomTableCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, rowId, columnName)) - { - RowId = rowId, - ColumnRef = columnName, - TableRef = tableId, - Data = data - }); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - - if (!this.Core.EncounteredError) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId); - } - } - - /// - /// Parses a directory element. - /// - /// Element to parse. - /// Optional identifier of parent directory. - /// Disk id inherited from parent directory. - /// Path to source file as of yet. - private void ParseDirectoryElement(XElement node, string parentId, int diskId, string fileSource) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string componentGuidGenerationSeed = null; - var fileSourceAttribSet = false; - XAttribute nameAttribute = null; - var name = "."; // default to parent directory. - string shortName = null; - string sourceName = null; - string shortSourceName = null; - string symbols = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ComponentGuidGenerationSeed": - componentGuidGenerationSeed = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "FileSource": - fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - fileSourceAttribSet = true; - break; - case "Name": - if ("." == attrib.Value) - { - name = attrib.Value; - } - else - { - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - } - nameAttribute = attrib; - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "ShortSourceName": - shortSourceName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "SourceName": - if ("." == attrib.Value) - { - sourceName = attrib.Value; - } - else - { - sourceName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (nameAttribute == null) - { - if (!String.IsNullOrEmpty(shortName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); - } - } - else if (!String.IsNullOrEmpty(name)) - { - if (String.IsNullOrEmpty(shortName)) - { - } - else if (name == ".") - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name", name)); - } - else if (name.Equals(shortName, StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "Name", "ShortName", name)); - } - } - - if (String.IsNullOrEmpty(sourceName)) - { - if (!String.IsNullOrEmpty(shortSourceName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName")); - } - } - else - { - if (String.IsNullOrEmpty(shortSourceName)) - { - } - else if (sourceName == ".") - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName", sourceName)); - } - else if (sourceName.Equals(shortSourceName, StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "SourceName", "ShortSourceName", sourceName)); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); - } - else if (WindowsInstallerStandard.IsStandardDirectory(id.Id)) - { - if (String.IsNullOrEmpty(sourceName)) - { - this.Core.Write(CompilerWarnings.DefiningStandardDirectoryDeprecated(sourceLineNumbers, id.Id)); - } - - if (id.Id == "TARGETDIR" && name != "SourceDir" && shortName == null && shortSourceName == null && sourceName == null) - { - this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name)); - } - } - - // Update the file source path appropriately. - if (fileSourceAttribSet) - { - if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); - } - } - else // add the appropriate part of this directory element to the file source. - { - string append = String.IsNullOrEmpty(sourceName) ? name : sourceName; - - if (!String.IsNullOrEmpty(append)) - { - fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id.Id, fileSource); - break; - case "Directory": - this.ParseDirectoryElement(child, id.Id, diskId, fileSource); - break; - case "Merge": - this.ParseMergeElement(child, id.Id, diskId); - break; - case "SymbolPath": - if (null != symbols) - { - symbols += ";" + this.ParseSymbolPathElement(child); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) - { - ParentDirectoryRef = parentId, - Name = name, - ShortName = shortName, - SourceName = sourceName, - SourceShortName = shortSourceName, - ComponentGuidGenerationSeed = componentGuidGenerationSeed - }); - - if (null != symbols) - { - this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, id) - { - SymbolType = SymbolPathType.Directory, - SymbolId = id.Id, - SymbolPaths = symbols, - }); - } - } - } - - /// - /// Parses a directory reference element. - /// - /// Element to parse. - private void ParseDirectoryRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var diskId = CompilerConstants.IntegerNotSet; - var fileSource = String.Empty; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "FileSource": - fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (WindowsInstallerStandard.IsStandardDirectory(id)) - { - this.Core.Write(CompilerWarnings.DirectoryRefStandardDirectoryDeprecated(sourceLineNumbers, id)); - } - - if (!String.IsNullOrEmpty(fileSource) && !fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id, fileSource); - break; - case "Directory": - this.ParseDirectoryElement(child, id, diskId, fileSource); - break; - case "Merge": - this.ParseMergeElement(child, id, diskId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses a directory search element. - /// - /// Element to parse. - /// Signature of parent search element. - /// Signature of search element. - private string ParseDirectorySearchElement(XElement node, string parentSignature) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var depth = CompilerConstants.IntegerNotSet; - string path = null; - var assignToProperty = false; - string signature = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Depth": - depth = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Path": - path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "AssignToProperty": - assignToProperty = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("dir", path, depth.ToString()); - } - - signature = id.Id; - - var oneChild = false; - var hasFileSearch = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchElement(child, id.Id); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, id.Id); - break; - case "FileSearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - hasFileSearch = true; - signature = this.ParseFileSearchElement(child, id.Id, assignToProperty, depth); - break; - case "FileSearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - - // If AssignToProperty is set, only a FileSearch - // or no child element can be nested. - if (assignToProperty) - { - if (!hasFileSearch) - { - this.Core.Write(ErrorMessages.IllegalParentAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AssignToProperty", child.Name.LocalName)); - } - else if (!oneChild) - { - // This a normal directory search. - assignToProperty = false; - } - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var access = id.Access; - var rowId = id.Id; - - // If AssignToProperty is set, the DrLocator row created by - // ParseFileSearchElement creates the directory entry to return - // and the row created here is for the file search. - if (assignToProperty) - { - access = AccessModifier.Section; - rowId = signature; - - // The property should be set to the directory search Id. - signature = id.Id; - } - - var symbol = this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(access, rowId, parentSignature, path)) - { - SignatureRef = rowId, - Parent = parentSignature, - Path = path, - }); - - if (CompilerConstants.IntegerNotSet != depth) - { - symbol.Depth = depth; - } - } - - return signature; - } - - /// - /// Parses a directory search reference element. - /// - /// Element to parse. - /// Signature of parent search element. - /// Signature of search element. - private string ParseDirectorySearchRefElement(XElement node, string parentSignature) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - Identifier parent = null; - string path = null; - string signature = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Parent": - parent = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Path": - path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null != parent) - { - if (!String.IsNullOrEmpty(parentSignature)) - { - this.Core.Write(ErrorMessages.CanNotHaveTwoParents(sourceLineNumbers, id.Id, parent.Id, parentSignature)); - } - else - { - parentSignature = parent.Id; - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("dsr", parentSignature, path); - } - - signature = id.Id; - - var oneChild = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchElement(child, id.Id); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, id.Id); - break; - case "FileSearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); - break; - case "FileSearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.DrLocator, id.Id, parentSignature, path); - - return signature; - } - - /// - /// Parses a feature element. - /// - /// Element to parse. - /// The type of parent. - /// Optional identifer for parent feature. - /// Display value for last feature used to get the features to display in the same order as specified - /// in the source code. - private void ParseFeatureElement(XElement node, ComplexReferenceParentType parentType, string parentId, ref int lastDisplay) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string configurableDirectory = null; - string description = null; - var displayValue = "collapse"; - var level = 1; - string title = null; - - var installDefault = FeatureInstallDefault.Local; - var typicalDefault = FeatureTypicalDefault.Install; - var disallowAbsent = false; - var disallowAdvertise = false; - var display = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "AllowAbsent": - disallowAbsent = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); - break; - case "AllowAdvertise": - disallowAdvertise = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); - break; - case "ConfigurableDirectory": - configurableDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, configurableDirectory); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Display": - displayValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "InstallDefault": - var installDefaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (installDefaultValue) - { - case "followParent": - if (ComplexReferenceParentType.Product == parentType) - { - this.Core.Write(ErrorMessages.RootFeatureCannotFollowParent(sourceLineNumbers)); - } - //bits = bits | MsiInterop.MsidbFeatureAttributesFollowParent; - installDefault = FeatureInstallDefault.FollowParent; - break; - case "local": // this is the default - installDefault = FeatureInstallDefault.Local; - break; - case "source": - //bits = bits | MsiInterop.MsidbFeatureAttributesFavorSource; - installDefault = FeatureInstallDefault.Source; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installDefaultValue, "followParent", "local", "source")); - break; - } - break; - case "Level": - level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Title": - title = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if ("PUT-FEATURE-TITLE-HERE" == title) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, title)); - } - break; - case "TypicalDefault": - var typicalValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typicalValue) - { - case "advertise": - //bits |= MsiInterop.MsidbFeatureAttributesFavorAdvertise; - typicalDefault = FeatureTypicalDefault.Advertise; - break; - case "install": // this is the default - typicalDefault = FeatureTypicalDefault.Install; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typicalValue, "advertise", "install")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (38 < id.Id.Length) - { - this.Core.Write(ErrorMessages.FeatureNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - - if (null != configurableDirectory && configurableDirectory.ToUpper(CultureInfo.InvariantCulture) != configurableDirectory) - { - this.Core.Write(ErrorMessages.FeatureConfigurableDirectoryNotUppercase(sourceLineNumbers, node.Name.LocalName, "ConfigurableDirectory", configurableDirectory)); - } - - if (FeatureTypicalDefault.Advertise == typicalDefault && disallowAdvertise) - { - this.Core.Write(ErrorMessages.FeatureCannotFavorAndDisallowAdvertise(sourceLineNumbers, node.Name.LocalName, "TypicalDefault", "advertise", "AllowAdvertise", "no")); - } - - var childDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ComponentGroupRef": - this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id, null); - break; - case "ComponentRef": - this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id.Id, null); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id.Id, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id.Id, ref childDisplay); - break; - case "FeatureGroupRef": - this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id.Id); - break; - case "Level": - this.ParseLevelElement(child, id.Id); - break; - case "MergeRef": - this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - switch (displayValue) - { - case "collapse": - lastDisplay = (lastDisplay | 1) + 1; - display = lastDisplay; - break; - case "expand": - lastDisplay = (lastDisplay + 1) | 1; - display = lastDisplay; - break; - case "hidden": - display = 0; - break; - default: - if (!Int32.TryParse(displayValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out display)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Display", displayValue, "collapse", "expand", "hidden")); - } - else - { - // Save the display value (if its not hidden) for subsequent rows - if (0 != display) - { - lastDisplay = display; - } - } - break; - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, id) - { - ParentFeatureRef = null, // this field is set in the linker - Title = title, - Description = description, - Display = display, - Level = level, - DirectoryRef = configurableDirectory, - DisallowAbsent = disallowAbsent, - DisallowAdvertise = disallowAdvertise, - InstallDefault = installDefault, - TypicalDefault = typicalDefault, - }); - - if (ComplexReferenceParentType.Unknown != parentType) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id.Id, false); - } - } - } - - /// - /// Parses a feature reference element. - /// - /// Element to parse. - /// The type of parent. - /// Optional identifier for parent feature. - private void ParseFeatureRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var ignoreParent = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, id); - break; - case "IgnoreParent": - ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - var lastDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ComponentGroupRef": - this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id, null); - break; - case "ComponentRef": - this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id, null); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id, ref lastDisplay); - break; - case "FeatureGroup": - this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Feature, id); - break; - case "FeatureGroupRef": - this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id); - break; - case "MergeRef": - this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - if (ComplexReferenceParentType.Unknown != parentType && YesNoType.Yes != ignoreParent) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id, false); - } - } - } - - /// - /// Parses a feature group element. - /// - /// Element to parse. - /// - /// - private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - var lastDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ComponentGroupRef": - this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); - break; - case "ComponentRef": - this.ParseComponentRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, ref lastDisplay); - break; - case "FeatureGroupRef": - this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); - break; - case "MergeRef": - this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixFeatureGroupSymbol(sourceLineNumbers, id)); - - //Add this FeatureGroup and its parent in WixGroup. - this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.FeatureGroup, id.Id); - } - } - - /// - /// Parses a feature group reference element. - /// - /// Element to parse. - /// The type of parent. - /// Identifier of parent element. - private void ParseFeatureGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - Debug.Assert(ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Product == parentType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var ignoreParent = YesNoType.NotSet; - var primary = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixFeatureGroup, id); - break; - case "IgnoreParent": - ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Primary": - primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - if (YesNoType.Yes != ignoreParent) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.FeatureGroup, id, (YesNoType.Yes == primary)); - } - } - } - - /// - /// Parses an environment element. - /// - /// Element to parse. - /// Identifier of parent component. - private void ParseEnvironmentElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string name = null; - EnvironmentActionType? action = null; - EnvironmentPartType? part = null; - var permanent = false; - var separator = ";"; // default to ';' - var system = false; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "create": - action = EnvironmentActionType.Create; - break; - case "set": - action = EnvironmentActionType.Set; - break; - case "remove": - action = EnvironmentActionType.Remove; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "create", "set", "remove")); - break; - } - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Part": - var partValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (partValue) - { - case "all": - part = EnvironmentPartType.All; - break; - case "first": - part = EnvironmentPartType.First; - break; - case "last": - part = EnvironmentPartType.Last; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Part", partValue, "all", "first", "last")); - break; - } - break; - case "Permanent": - permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Separator": - separator = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "System": - system = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("env", ((int?)action)?.ToString(), name, ((int?)part)?.ToString(), system.ToString()); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (part.HasValue && action == EnvironmentActionType.Create) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); - } - - //if (Wix.Environment.PartType.NotSet != partType) - //{ - // if ("+" == action) - // { - // this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); - // } - - // switch (partType) - // { - // case Wix.Environment.PartType.all: - // break; - // case Wix.Environment.PartType.first: - // text = String.Concat(text, separator, "[~]"); - // break; - // case Wix.Environment.PartType.last: - // text = String.Concat("[~]", separator, text); - // break; - // } - //} - - //if (permanent) - //{ - // uninstall = null; - //} - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new EnvironmentSymbol(sourceLineNumbers, id) - { - Name = name, - Value = value, - Separator = separator, - Action = action, - Part = part, - Permanent = permanent, - System = system, - ComponentRef = componentId - }); - } - } - - /// - /// Parses an error element. - /// - /// Element to parse. - private void ParseErrorElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var id = CompilerConstants.IntegerNotSet; - string message = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Message": - message = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (CompilerConstants.IntegerNotSet == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = CompilerConstants.IllegalInteger; - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ErrorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id)) - { - Message = message - }); - } - } - - /// - /// Parses an extension element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Flag if this extension is advertised. - /// ProgId for extension. - private void ParseExtensionElement(XElement node, string componentId, YesNoType advertise, string progId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string extension = null; - string mime = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - extension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Advertise": - var extensionAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - if ((YesNoType.No == advertise && YesNoType.Yes == extensionAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == extensionAdvertise)) - { - this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, extensionAdvertise.ToString(), advertise.ToString())); - } - advertise = extensionAdvertise; - break; - case "ContentType": - mime = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - var context = new Dictionary() { { "ProgId", progId }, { "ComponentId", componentId } }; - this.Core.ParseExtensionAttribute(node, attrib, context); - } - } - - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Verb": - this.ParseVerbElement(child, extension, progId, componentId, advertise); - break; - case "MIME": - var newMime = this.ParseMIMEElement(child, extension, componentId, advertise); - if (null != newMime && null == mime) - { - mime = newMime; - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (YesNoType.Yes == advertise) - { - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ExtensionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, extension, componentId)) - { - Extension = extension, - ComponentRef = componentId, - ProgIdRef = progId, - MimeRef = mime, - FeatureRef = Guid.Empty.ToString("B"), - }); - - this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Verb); - } - } - else if (YesNoType.No == advertise) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), String.Empty, progId, componentId); // Extension - if (null != mime) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), "Content Type", mime, componentId); // Extension's MIME ContentType - } - } - } - - - /// - /// Parses a file element. - /// - /// File element to parse. - /// Parent's component id. - /// Ancestor's directory id. - /// Disk id inherited from parent component. - /// Default source path of parent directory. - /// This will be set with the possible keyPath for the parent component. - /// true if the component is 64-bit. - /// - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseFileElement(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, bool win64Component, string componentGuid) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var assemblyType = AssemblyType.NotAnAssembly; - string assemblyApplication = null; - string assemblyManifest = null; - string bindPath = null; - - //int bits = MsiInterop.MsidbFileAttributesVital; - var readOnly = false; - var checksum = false; - bool? compressed = null; - var hidden = false; - var system = false; - var vital = true; // assume all files are vital. - - string companionFile = null; - string defaultLanguage = null; - var defaultSize = 0; - string defaultVersion = null; - string fontTitle = null; - var keyPath = YesNoType.NotSet; - string name = null; - var patchGroup = CompilerConstants.IntegerNotSet; - var patchIgnore = false; - var patchIncludeWholeFile = false; - var patchAllowIgnoreOnError = false; - - string ignoreLengths = null; - string ignoreOffsets = null; - string protectLengths = null; - string protectOffsets = null; - string symbols = null; - - string procArch = null; - int? selfRegCost = null; - string shortName = null; - var source = sourcePath; // assume we'll use the parents as the source for this file - var sourceSet = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Assembly": - var assemblyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (assemblyValue) - { - case ".net": - assemblyType = AssemblyType.DotNetAssembly; - break; - case "no": - assemblyType = AssemblyType.NotAnAssembly; - break; - case "win32": - assemblyType = AssemblyType.Win32Assembly; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "Assembly", assemblyValue, "no", "win32", ".net")); - break; - } - break; - case "AssemblyApplication": - assemblyApplication = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyApplication); - break; - case "AssemblyManifest": - assemblyManifest = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyManifest); - break; - case "BindPath": - bindPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Checksum": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - checksum = true; - //bits |= MsiInterop.MsidbFileAttributesChecksum; - } - break; - case "CompanionFile": - companionFile = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, companionFile); - break; - case "Compressed": - var compressedValue = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - if (YesNoDefaultType.Yes == compressedValue) - { - compressed = true; - //bits |= MsiInterop.MsidbFileAttributesCompressed; - } - else if (YesNoDefaultType.No == compressedValue) - { - compressed = false; - //bits |= MsiInterop.MsidbFileAttributesNoncompressed; - } - break; - case "DefaultLanguage": - defaultLanguage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DefaultSize": - defaultSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "DefaultVersion": - defaultVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "FontTitle": - fontTitle = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Hidden": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - hidden = true; - //bits |= MsiInterop.MsidbFileAttributesHidden; - } - break; - case "KeyPath": - keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "PatchGroup": - patchGroup = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); - break; - case "PatchIgnore": - patchIgnore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "PatchWholeFile": - patchIncludeWholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "PatchAllowIgnoreOnError": - patchAllowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ProcessorArchitecture": - var procArchValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (procArchValue) - { - case "msil": - procArch = "MSIL"; - break; - case "x86": - procArch = "x86"; - break; - case "x64": - procArch = "amd64"; - break; - case "arm64": - procArch = "arm64"; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "ProcessorArchitecture", procArchValue, "msil", "x86", "x64")); - break; - } - break; - case "ReadOnly": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - readOnly = true; - //bits |= MsiInterop.MsidbFileAttributesReadOnly; - } - break; - case "SelfRegCost": - selfRegCost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "Source": - source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - sourceSet = true; - break; - case "System": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - system = true; - //bits |= MsiInterop.MsidbFileAttributesSystem; - } - break; - case "TrueType": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - fontTitle = String.Empty; - } - break; - case "Vital": - var isVital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - if (YesNoType.Yes == isVital) - { - vital = true; - //bits |= MsiInterop.MsidbFileAttributesVital; - } - else if (YesNoType.No == isVital) - { - vital = false; - //bits &= ~MsiInterop.MsidbFileAttributesVital; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null != companionFile) - { - // the companion file cannot be the key path of a component - if (YesNoType.Yes == keyPath) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "CompanionFile", "KeyPath", "yes")); - } - } - - if (sourceSet && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && null == name) - { - name = Path.GetFileName(source); - if (!this.Core.IsValidLongFilename(name, false)) - { - this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); - } - } - - if (name == null) - { - if (shortName == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else - { - name = shortName; - shortName = null; - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("fil", directoryId, name); - } - - if (null != defaultVersion && null != companionFile) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DefaultVersion", "CompanionFile", companionFile)); - } - - if (AssemblyType.NotAnAssembly == assemblyType) - { - if (null != assemblyManifest) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyManifest")); - } - - if (null != assemblyApplication) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyApplication")); - } - } - else - { - if (AssemblyType.Win32Assembly == assemblyType && null == assemblyManifest) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AssemblyManifest", "Assembly", "win32")); - } - - // allow "*" guid components to omit explicit KeyPath as they can have only one file and therefore this file is the keypath - if (YesNoType.Yes != keyPath && "*" != componentGuid) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", (AssemblyType.DotNetAssembly == assemblyType ? ".net" : "win32"), "KeyPath", "yes")); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "AppId": - this.ParseAppIdElement(child, componentId, YesNoType.NotSet, id.Id, null, null); - break; - case "AssemblyName": - this.ParseAssemblyName(child, componentId); - break; - case "Class": - this.ParseClassElement(child, componentId, YesNoType.NotSet, id.Id, null, null, null); - break; - case "CopyFile": - this.ParseCopyFileElement(child, componentId, id.Id); - break; - case "IgnoreRange": - this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); - break; - case "ODBCDriver": - this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCDriver); - break; - case "ODBCTranslator": - this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCTranslator); - break; - case "Permission": - this.ParsePermissionElement(child, id.Id, "File"); - break; - case "PermissionEx": - this.ParsePermissionExElement(child, id.Id, "File"); - break; - case "ProtectRange": - this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); - break; - case "Shortcut": - this.ParseShortcutElement(child, componentId, node.Name.LocalName, id.Id, keyPath); - break; - case "SymbolPath": - if (null != symbols) - { - symbols += ";" + this.ParseSymbolPathElement(child); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - case "TypeLib": - this.ParseTypeLibElement(child, componentId, id.Id, win64Component); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "FileId", id?.Id }, { "ComponentId", componentId }, { "DirectoryId", directoryId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (!this.Core.EncounteredError) - { - var patchAttributes = PatchAttributeType.None; - if (patchIgnore) - { - patchAttributes |= PatchAttributeType.Ignore; - } - if (patchIncludeWholeFile) - { - patchAttributes |= PatchAttributeType.IncludeWholeFile; - } - if (patchAllowIgnoreOnError) - { - patchAttributes |= PatchAttributeType.AllowIgnoreOnError; - } - - if (String.IsNullOrEmpty(source)) - { - source = name; - } - else if (source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) // if source relies on parent directories, append the file name - { - source = Path.Combine(source, name); - } - - var attributes = FileSymbolAttributes.None; - attributes |= readOnly ? FileSymbolAttributes.ReadOnly : 0; - attributes |= hidden ? FileSymbolAttributes.Hidden : 0; - attributes |= system ? FileSymbolAttributes.System : 0; - attributes |= vital ? FileSymbolAttributes.Vital : 0; - attributes |= checksum ? FileSymbolAttributes.Checksum : 0; - attributes |= compressed.HasValue && compressed == true ? FileSymbolAttributes.Compressed : 0; - attributes |= compressed.HasValue && compressed == false ? FileSymbolAttributes.Uncompressed : 0; - - this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Name = name, - ShortName = shortName, - FileSize = defaultSize, - Version = companionFile ?? defaultVersion, - Language = defaultLanguage, - Attributes = attributes, - - DirectoryRef = directoryId, - DiskId = (CompilerConstants.IntegerNotSet == diskId) ? null : (int?)diskId, - Source = new IntermediateFieldPathValue { Path = source }, - - FontTitle = fontTitle, - SelfRegCost = selfRegCost, - BindPath = bindPath, - - PatchGroup = (CompilerConstants.IntegerNotSet == patchGroup) ? null : (int?)patchGroup, - PatchAttributes = patchAttributes, - - // Delta patching information - RetainLengths = protectLengths, - IgnoreOffsets = ignoreOffsets, - IgnoreLengths = ignoreLengths, - RetainOffsets = protectOffsets, - SymbolPaths = symbols, - }); - - if (AssemblyType.NotAnAssembly != assemblyType) - { - this.Core.AddSymbol(new AssemblySymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - FeatureRef = Guid.Empty.ToString("B"), - ManifestFileRef = assemblyManifest, - ApplicationFileRef = assemblyApplication, - Type = assemblyType, - ProcessorArchitecture = procArch, - }); - } - } - - if (CompilerConstants.IntegerNotSet != diskId) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); - } - - // If this component does not have a companion file this file is a possible keypath. - possibleKeyPath = null; - if (null == companionFile) - { - possibleKeyPath = id.Id; - } - - return keyPath; - } - - /// - /// Parses a file search element. - /// - /// Element to parse. - /// Signature of parent search element. - /// Whether this search element is used to search for the parent directory. - /// The depth specified by the parent search element. - /// Signature of search element. - private string ParseFileSearchElement(XElement node, string parentSignature, bool parentDirectorySearch, int parentDepth) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string languages = null; - var minDate = CompilerConstants.IntegerNotSet; - var maxDate = CompilerConstants.IntegerNotSet; - var maxSize = CompilerConstants.IntegerNotSet; - var minSize = CompilerConstants.IntegerNotSet; - string maxVersion = null; - string minVersion = null; - string name = null; - string shortName = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "MinVersion": - minVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MaxVersion": - maxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MinSize": - minSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "MaxSize": - maxSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "MinDate": - minDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); - break; - case "MaxDate": - maxDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); - break; - case "Languages": - languages = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // Using both ShortName and Name will not always work due to a Windows Installer bug. - if (null != shortName && null != name) - { - this.Core.Write(WarningMessages.FileSearchFileNameIssue(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); - } - else if (null == shortName && null == name) // at least one name must be specified. - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (this.Core.IsValidShortFilename(name, false)) - { - if (null == shortName) - { - shortName = name; - name = null; - } - else - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName")); - } - } - - if (null == id) - { - if (String.IsNullOrEmpty(parentSignature)) - { - id = this.Core.CreateIdentifier("fs", name ?? shortName); - } - else // reuse parent signature in the Signature table - { - id = new Identifier(AccessModifier.Section, parentSignature); - } - } - - var isSameId = String.Equals(id.Id, parentSignature, StringComparison.Ordinal); - if (parentDirectorySearch) - { - // If searching for the parent directory, the Id attribute - // value must be specified and unique. - if (isSameId) - { - this.Core.Write(ErrorMessages.UniqueFileSearchIdRequired(sourceLineNumbers, parentSignature, node.Name.LocalName)); - } - } - else if (parentDepth > 1) - { - // Otherwise, if the depth > 1 the Id must be absent or the same - // as the parent DirectorySearch if AssignToProperty is not set. - if (!isSameId) - { - this.Core.Write(ErrorMessages.IllegalSearchIdForParentDepth(sourceLineNumbers, id.Id, parentSignature)); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new SignatureSymbol(sourceLineNumbers, id) - { - FileName = name ?? shortName, - MinVersion = minVersion, - MaxVersion = maxVersion, - Languages = languages - }); - - if (CompilerConstants.IntegerNotSet != minSize) - { - symbol.MinSize = minSize; - } - - if (CompilerConstants.IntegerNotSet != maxSize) - { - symbol.MaxSize = maxSize; - } - - if (CompilerConstants.IntegerNotSet != minDate) - { - symbol.MinDate = minDate; - } - - if (CompilerConstants.IntegerNotSet != maxDate) - { - symbol.MaxDate = maxDate; - } - - // Create a DrLocator row to associate the file with a directory - // when a different identifier is specified for the FileSearch. - if (!isSameId) - { - if (parentDirectorySearch) - { - // Creates the DrLocator row for the directory search while - // the parent DirectorySearch creates the file locator row. - this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, parentSignature, id.Id, String.Empty)) - { - SignatureRef = parentSignature, - Parent = id.Id - }); - } - else - { - this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id.Id, parentSignature, String.Empty)) - { - SignatureRef = id.Id, - Parent = parentSignature - }); - } - } - } - - return id.Id; // the id of the FileSearch element is its signature - } - - - /// - /// Parses a fragment element. - /// - /// Element to parse. - private void ParseFragmentElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - this.activeName = null; - this.activeLanguage = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // NOTE: Id is not required for Fragments, this is a departure from the normal run of the mill processing. - - this.Core.CreateActiveSection(id?.Id, SectionType.Fragment, this.Context.CompilationId); - - var featureDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "_locDefinition": - break; - case "AdminExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); - break; - case "AdminUISequence": - this.ParseSequenceElement(child, SequenceTable.AdminUISequence); - break; - case "AdvertiseExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); - break; - case "InstallExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); - break; - case "InstallUISequence": - this.ParseSequenceElement(child, SequenceTable.InstallUISequence); - break; - case "AppId": - this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); - break; - case "Binary": - this.ParseBinaryElement(child); - break; - case "BootstrapperApplication": - this.ParseBootstrapperApplicationElement(child); - break; - case "BootstrapperApplicationRef": - this.ParseBootstrapperApplicationRefElement(child); - break; - case "BundleCustomData": - this.ParseBundleCustomDataElement(child); - break; - case "BundleCustomDataRef": - this.ParseBundleCustomDataRefElement(child); - break; - case "BundleExtension": - this.ParseBundleExtensionElement(child); - break; - case "BundleExtensionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); - break; - case "ComplianceCheck": - this.ParseComplianceCheckElement(child); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "ComponentGroup": - this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); - break; - case "Container": - this.ParseContainerElement(child); - break; - case "CustomAction": - this.ParseCustomActionElement(child); - break; - case "CustomActionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); - break; - case "CustomTable": - this.ParseCustomTableElement(child); - break; - case "CustomTableRef": - this.ParseCustomTableRefElement(child); - break; - case "Directory": - this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); - break; - case "DirectoryRef": - this.ParseDirectoryRefElement(child); - break; - case "EmbeddedChainer": - this.ParseEmbeddedChainerElement(child); - break; - case "EmbeddedChainerRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); - break; - case "EnsureTable": - this.ParseEnsureTableElement(child); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.Unknown, null, ref featureDisplay); - break; - case "FeatureGroup": - this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.Unknown, null); - break; - case "Icon": - this.ParseIconElement(child); - break; - case "Media": - this.ParseMediaElement(child, null); - break; - case "MediaTemplate": - this.ParseMediaTemplateElement(child, null); - break; - case "Launch": - this.ParseLaunchElement(child); - break; - case "PackageGroup": - this.ParsePackageGroupElement(child); - break; - case "PackageCertificates": - case "PatchCertificates": - this.ParseCertificatesElement(child); - break; - case "PatchFamily": - this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id.Id); - break; - case "PatchFamilyGroup": - this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id.Id); - break; - case "PatchFamilyGroupRef": - this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id.Id); - break; - case "PayloadGroup": - this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "PropertyRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Property); - break; - case "RelatedBundle": - this.ParseRelatedBundleElement(child); - break; - case "Requires": - this.ParseRequiresElement(child, null); - break; - case "SetDirectory": - this.ParseSetDirectoryElement(child); - break; - case "SetProperty": - this.ParseSetPropertyElement(child); - break; - case "SetVariable": - this.ParseSetVariableElement(child); - break; - case "SetVariableRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); - break; - case "SFPCatalog": - string parentName = null; - this.ParseSFPCatalogElement(child, ref parentName); - break; - case "StandardDirectory": - this.ParseStandardDirectoryElement(child); - break; - case "UI": - this.ParseUIElement(child); - break; - case "UIRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); - break; - case "Upgrade": - this.ParseUpgradeElement(child); - break; - case "Variable": - this.ParseVariableElement(child); - break; - case "WixVariable": - this.ParseWixVariableElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError && null != id) - { - this.Core.AddSymbol(new WixFragmentSymbol(sourceLineNumbers, id)); - } - } - - /// - /// Parses a launch condition element. - /// - /// Element to parse. - private void ParseLaunchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string condition = null; - string message = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Message": - message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(condition)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); - } - - if (String.IsNullOrEmpty(message)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Message")); - } - - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) - { - Condition = condition, - Description = message - }); - } - } - - /// - /// Parses a IniFile element. - /// - /// Element to parse. - /// Identifier of the parent component. - private void ParseIniFileElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - IniFileActionType? action = null; - string directory = null; - string key = null; - string name = null; - string section = null; - string shortName = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "addLine": - action = IniFileActionType.AddLine; - break; - case "addTag": - action = IniFileActionType.AddTag; - break; - case "removeLine": - action = IniFileActionType.RemoveLine; - break; - case "removeTag": - action = IniFileActionType.RemoveTag; - break; - case "": // error case handled by GetAttributeValue() - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", actionValue, "addLine", "addTag", "createLine", "removeLine", "removeTag")); - break; - } - break; - case "Directory": - directory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "Section": - section = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (!action.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); - } - else if (IniFileActionType.AddLine == action || IniFileActionType.AddTag == action || IniFileActionType.CreateLine == action) - { - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == section) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("ini", directory, name ?? shortName, section, key, name); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new IniFileSymbol(sourceLineNumbers, id) - { - FileName = name, - ShortFileName = shortName, - DirProperty = directory, - Section = section, - Key = key, - Value = value, - Action = action.Value, - ComponentRef = componentId - }); - } - } - - /// - /// Parses an IniFile search element. - /// - /// Element to parse. - /// Signature for search element. - private string ParseIniFileSearchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var field = CompilerConstants.IntegerNotSet; - string key = null; - string name = null; - string section = null; - string shortName = null; - string signature = null; - var type = 1; // default is file - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Field": - field = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "Section": - section = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "directory": - type = 0; - break; - case "file": - type = 1; - break; - case "raw": - type = 2; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "registry")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == section) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("ini", name, section, key, field.ToString(), type.ToString()); - } - - signature = id.Id; - - var oneChild = false; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "DirectorySearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - - // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column - signature = this.ParseDirectorySearchElement(child, id.Id); - break; - case "DirectorySearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseDirectorySearchRefElement(child, id.Id); - break; - case "FileSearch": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); - id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures - break; - case "FileSearchRef": - if (oneChild) - { - this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); - } - oneChild = true; - var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures - id = new Identifier(AccessModifier.Section, newId); - signature = null; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new IniLocatorSymbol(sourceLineNumbers, id) - { - FileName = name, - ShortFileName = shortName, - Section = section, - Key = key, - Type = type - }); - - if (CompilerConstants.IntegerNotSet != field) - { - symbol.Field = field; - } - } - - return signature; - } - - /// - /// Parses an isolated component element. - /// - /// Element to parse. - /// Identifier of parent component. - private void ParseIsolateComponentElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string shared = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Shared": - shared = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, shared); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == shared) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Shared")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new IsolatedComponentSymbol(sourceLineNumbers) - { - SharedComponentRef = shared, - ApplicationComponentRef = componentId - }); - } - } - - /// - /// Parses a PatchCertificates or PackageCertificates element. - /// - /// The element to parse. - private void ParseCertificatesElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - // no attributes are supported for this element - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - this.Core.UnexpectedAttribute(node, attrib); - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "DigitalCertificate": - var name = this.ParseDigitalCertificateElement(child); - - if (!this.Core.EncounteredError) - { - if ("PatchCertificates" == node.Name.LocalName) - { - this.Core.AddSymbol(new MsiPatchCertificateSymbol(sourceLineNumbers) - { - PatchCertificate = name, - DigitalCertificateRef = name, - }); - } - else - { - this.Core.AddSymbol(new MsiPackageCertificateSymbol(sourceLineNumbers) - { - PackageCertificate = name, - DigitalCertificateRef = name, - }); - } - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses an digital certificate element. - /// - /// Element to parse. - /// The identifier of the certificate. - private string ParseDigitalCertificateElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (40 < id.Id.Length) - { - this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 40)); - - // No need to check for modularization problems since DigitalSignature and thus DigitalCertificate - // currently have no usage in merge modules. - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiDigitalCertificateSymbol(sourceLineNumbers, id) - { - CertData = sourceFile - }); - } - - return id.Id; - } - - /// - /// Parses an digital signature element. - /// - /// Element to parse. - /// Disk id inherited from parent media. - private void ParseDigitalSignatureElement(XElement node, string diskId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string certificateId = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // sanity check for debug to ensure the stream name will not be a problem - if (null != sourceFile) - { - Debug.Assert(62 >= "MsiDigitalSignature.Media.".Length + diskId.Length); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "DigitalCertificate": - certificateId = this.ParseDigitalCertificateElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null == certificateId) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "DigitalCertificate")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiDigitalSignatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "Media", diskId)) - { - Table = "Media", - SignObject = diskId, - DigitalCertificateRef = certificateId, - Hash = sourceFile - }); - } - } - - /// - /// Parses a MajorUpgrade element. - /// - /// The element to parse. - /// The current context. - private void ParseMajorUpgradeElement(XElement node, IDictionary contextValues) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var migrateFeatures = true; - var ignoreRemoveFailure = false; - var allowDowngrades = false; - var allowSameVersionUpgrades = false; - var blockUpgrades = false; - string downgradeErrorMessage = null; - string disallowUpgradeErrorMessage = null; - string removeFeatures = null; - string schedule = null; - - var upgradeCode = contextValues["UpgradeCode"]; - if (String.IsNullOrEmpty(upgradeCode)) - { - this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "UpgradeCode", node.Name.LocalName)); - } - - var productVersion = contextValues["ProductVersion"]; - if (String.IsNullOrEmpty(productVersion)) - { - this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "Version", node.Name.LocalName)); - } - - var productLanguage = contextValues["ProductLanguage"]; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AllowDowngrades": - allowDowngrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "AllowSameVersionUpgrades": - allowSameVersionUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Disallow": - blockUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "DowngradeErrorMessage": - downgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisallowUpgradeErrorMessage": - disallowUpgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MigrateFeatures": - migrateFeatures = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "IgnoreLanguage": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - productLanguage = null; - } - break; - case "IgnoreRemoveFailure": - ignoreRemoveFailure = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "RemoveFeatures": - removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Schedule": - schedule = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!allowDowngrades && String.IsNullOrEmpty(downgradeErrorMessage)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes", true)); - } - - if (allowDowngrades && !String.IsNullOrEmpty(downgradeErrorMessage)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes")); - } - - if (allowDowngrades && allowSameVersionUpgrades) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AllowSameVersionUpgrades", "AllowDowngrades", "yes")); - } - - if (blockUpgrades && String.IsNullOrEmpty(disallowUpgradeErrorMessage)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes", true)); - } - - if (!blockUpgrades && !String.IsNullOrEmpty(disallowUpgradeErrorMessage)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes")); - } - - if (!this.Core.EncounteredError) - { - // create the row that performs the upgrade (or downgrade) - var symbol = this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeCode, - Remove = removeFeatures, - MigrateFeatures = migrateFeatures, - IgnoreRemoveFailures = ignoreRemoveFailure, - ActionProperty = WixUpgradeConstants.UpgradeDetectedProperty - }); - - if (allowDowngrades) - { - symbol.VersionMin = "0"; - symbol.Language = productLanguage; - symbol.VersionMinInclusive = true; - } - else - { - symbol.VersionMax = productVersion; - symbol.Language = productLanguage; - symbol.VersionMaxInclusive = allowSameVersionUpgrades; - } - - // Add launch condition that blocks upgrades - if (blockUpgrades) - { - this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) - { - Condition = WixUpgradeConstants.UpgradePreventedCondition, - Description = downgradeErrorMessage - }); - } - - // now create the Upgrade row and launch conditions to prevent downgrades (unless explicitly permitted) - if (!allowDowngrades) - { - this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeCode, - VersionMin = productVersion, - Language = productLanguage, - OnlyDetect = true, - IgnoreRemoveFailures = ignoreRemoveFailure, - ActionProperty = WixUpgradeConstants.DowngradeDetectedProperty - }); - - this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) - { - Condition = WixUpgradeConstants.DowngradePreventedCondition, - Description = downgradeErrorMessage - }); - } - - // finally, schedule RemoveExistingProducts - string after = null; - switch (schedule) - { - case null: - case "afterInstallValidate": - after = "InstallValidate"; - break; - case "afterInstallInitialize": - after = "InstallInitialize"; - break; - case "afterInstallExecute": - after = "InstallExecute"; - break; - case "afterInstallExecuteAgain": - after = "InstallExecuteAgain"; - break; - case "afterInstallFinalize": - after = "InstallFinalize"; - break; - } - - this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, SequenceTable.InstallExecuteSequence, "RemoveExistingProducts", afterAction: after); - } - } - - /// - /// Parses a media element. - /// - /// Element to parse. - /// Set to the PatchId if parsing Patch/Media element otherwise null. - private void ParseMediaElement(XElement node, string patchId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var id = CompilerConstants.IntegerNotSet; - string cabinet = null; - CompressionLevel? compressionLevel = null; - string diskPrompt = null; - string layout = null; - var patch = null != patchId; - string volumeLabel = null; - string source = null; - string symbols = null; - - var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "Cabinet": - cabinet = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "CompressionLevel": - compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); - break; - case "DiskPrompt": - diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined - break; - case "EmbedCab": - embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Layout": - layout = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "VolumeLabel": - volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Source": - source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (CompilerConstants.IntegerNotSet == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = CompilerConstants.IllegalInteger; - } - - if (YesNoType.IllegalValue != embedCab) - { - if (YesNoType.Yes == embedCab) - { - if (null == cabinet) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "EmbedCab", "yes")); - } - else - { - if (62 < cabinet.Length) - { - this.Core.Write(ErrorMessages.MediaEmbeddedCabinetNameTooLong(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet, cabinet.Length)); - } - - cabinet = String.Concat("#", cabinet); - } - } - else // external cabinet file - { - // external cabinet files must use 8.3 filenames - if (!String.IsNullOrEmpty(cabinet) && !this.Core.IsValidLongFilename(cabinet) && !Common.ContainsValidBinderVariable(cabinet)) - { - this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet)); - } - } - } - - if (compressionLevel.HasValue && String.IsNullOrEmpty(cabinet)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "CompressionLevel")); - } - - if (patch) - { - // Default Source to a form of the Patch Id if none is specified. - if (null == source) - { - source = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); - } - } - - foreach (var child in node.Elements()) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "DigitalSignature": - if (YesNoType.Yes == embedCab) - { - this.Core.Write(ErrorMessages.SignedEmbeddedCabinet(childSourceLineNumbers)); - } - else if (null == cabinet) - { - this.Core.Write(ErrorMessages.ExpectedSignedCabinetName(childSourceLineNumbers)); - } - else - { - this.ParseDigitalSignatureElement(child, id.ToString(CultureInfo.InvariantCulture.NumberFormat)); - } - break; - case "PatchBaseline": - if (patch) - { - this.ParsePatchBaselineElement(child, id); - } - else - { - this.Core.UnexpectedElement(node, child); - } - break; - case "SymbolPath": - if (null != symbols) - { - symbols += "" + this.ParseSymbolPathElement(child); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - // add the row to the section - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id)) - { - DiskId = id, - DiskPrompt = diskPrompt, - Cabinet = cabinet, - VolumeLabel = volumeLabel, - Source = source, // the Source column is only set when creating a patch - CompressionLevel = compressionLevel, - Layout = layout - }); - - if (null != symbols) - { - this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Media, id)) - { - SymbolType = SymbolPathType.Media, - SymbolId = id.ToString(CultureInfo.InvariantCulture), - SymbolPaths = symbols - }); - } - } - } - - /// - /// Parses a media template element. - /// - /// Element to parse. - /// Set to the PatchId if parsing Patch/Media element otherwise null. - private void ParseMediaTemplateElement(XElement node, string patchId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var cabinetTemplate = "cab{0}.cab"; - string diskPrompt = null; - var patch = null != patchId; - string volumeLabel = null; - int? maximumUncompressedMediaSize = null; - int? maximumCabinetSizeForLargeFileSplitting = null; - CompressionLevel? compressionLevel = null; // this defaults to 'medium' in the MSI and Burn backends - - var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "CabinetTemplate": - var authoredCabinetTemplateValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - if (!String.IsNullOrEmpty(authoredCabinetTemplateValue)) - { - cabinetTemplate = authoredCabinetTemplateValue; - } - - // Create an example cabinet name using the maximum number of cabinets supported, 999. - var exampleCabinetName = String.Format(cabinetTemplate, "###"); - if (!this.Core.IsValidLocIdentifier(exampleCabinetName)) - { - // The example name should not match the authored template since that would nullify the - // reason for having multiple cabinets. External cabinet files must also be valid file names. - if (exampleCabinetName.Equals(authoredCabinetTemplateValue, StringComparison.OrdinalIgnoreCase) || !this.Core.IsValidLongFilename(exampleCabinetName, false)) - { - this.Core.Write(ErrorMessages.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate)); - } - else if (!this.Core.IsValidLongFilename(exampleCabinetName) && !Common.ContainsValidBinderVariable(exampleCabinetName)) // ignore short names with wix variables because it rarely works out. - { - this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate)); - } - } - break; - case "CompressionLevel": - compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); - break; - case "DiskPrompt": - diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined - this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "EmbedCab": - embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "VolumeLabel": - volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "MaximumUncompressedMediaSize": - maximumUncompressedMediaSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); - break; - case "MaximumCabinetSizeForLargeFileSplitting": - maximumCabinetSizeForLargeFileSplitting = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Compiler.MinValueOfMaxCabSizeForLargeFileSplitting, Compiler.MaxValueOfMaxCabSizeForLargeFileSplitting); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (YesNoType.Yes == embedCab) - { - cabinetTemplate = String.Concat("#", cabinetTemplate); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, 1)) - { - DiskId = 1 - }); - - this.Core.AddSymbol(new WixMediaTemplateSymbol(sourceLineNumbers) - { - CabinetTemplate = cabinetTemplate, - VolumeLabel = volumeLabel, - DiskPrompt = diskPrompt, - MaximumUncompressedMediaSize = maximumUncompressedMediaSize, - MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting, - CompressionLevel = compressionLevel - }); - - //else - //{ - // mediaTemplateRow.MaximumUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize; - //} - - //else - //{ - // mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting = 0; // Default value of 0 corresponds to max size of 2048 MB (i.e. 2 GB) - //} - } - } - - /// - /// Parses a merge element. - /// - /// Element to parse. - /// Identifier for parent directory. - /// Disk id inherited from parent directory. - private void ParseMergeElement(XElement node, string directoryId, int diskId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var configData = String.Empty; - FileSymbolAttributes attributes = 0; - string language = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); - break; - case "FileCompression": - var compress = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - attributes |= compress == YesNoType.Yes ? FileSymbolAttributes.Compressed : 0; - attributes |= compress == YesNoType.No ? FileSymbolAttributes.Uncompressed : 0; - break; - case "Language": - language = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == language) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ConfigurationData": - if (0 == configData.Length) - { - configData = this.ParseConfigurationDataElement(child); - } - else - { - configData = String.Concat(configData, ",", this.ParseConfigurationDataElement(child)); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new WixMergeSymbol(sourceLineNumbers, id) - { - DirectoryRef = directoryId, - SourceFile = sourceFile, - DiskId = diskId, - ConfigurationData = configData, - FileAttributes = attributes, - FeatureRef = Guid.Empty.ToString("B") - }); - - symbol.Set((int)WixMergeSymbolFields.Language, language); - } - } - - /// - /// Parses a standard directory element. - /// - /// Element to parse. - private void ParseStandardDirectoryElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(id)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (!WindowsInstallerStandard.IsStandardDirectory(id)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Id", id, String.Join(", \"", WindowsInstallerStandard.StandardDirectories().Select(d => d.Id.Id)))); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId: CompilerConstants.IntegerNotSet, id, srcPath: String.Empty); - break; - case "Directory": - this.ParseDirectoryElement(child, id, diskId: CompilerConstants.IntegerNotSet, fileSource: String.Empty); - break; - case "Merge": - this.ParseMergeElement(child, id, diskId: CompilerConstants.IntegerNotSet); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses a configuration data element. - /// - /// Element to parse. - /// String in format "name=value" with '%', ',' and '=' hex encoded. - private string ParseConfigurationDataElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string name = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else // need to hex encode these characters - { - name = name.Replace("%", "%25"); - name = name.Replace("=", "%3D"); - name = name.Replace(",", "%2C"); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - else // need to hex encode these characters - { - value = value.Replace("%", "%25"); - value = value.Replace("=", "%3D"); - value = value.Replace(",", "%2C"); - } - - this.Core.ParseForExtensionElements(node); - - return String.Concat(name, "=", value); - } - - /// - /// Parses a Level element. - /// - /// Element to parse. - /// Id of the parent Feature element. - private void ParseLevelElement(XElement node, string featureId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string condition = null; - int? level = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (!level.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); - } - - if (String.IsNullOrEmpty(condition)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - if (CompilerConstants.IntegerNotSet == level) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); - level = CompilerConstants.IllegalInteger; - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ConditionSymbol(sourceLineNumbers) - { - FeatureRef = featureId, - Level = level.Value, - Condition = condition - }); - } - } - } - - /// - /// Parses a merge reference element. - /// - /// Element to parse. - /// Parents complex reference type. - /// Identifier for parent feature or feature group. - private void ParseMergeRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var primary = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixMerge, id); - break; - case "Primary": - primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Module, id, (YesNoType.Yes == primary)); - } - - /// - /// Parses a mime element. - /// - /// Element to parse. - /// Identifier for parent extension. - /// Identifier for parent component. - /// Flag if the parent element is advertised. - /// Content type if this is the default for the MIME type. - private string ParseMIMEElement(XElement node, string extension, string componentId, YesNoType parentAdvertised) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string classId = null; - string contentType = null; - var advertise = parentAdvertised; - var returnContentType = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Advertise": - advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Class": - classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "ContentType": - contentType = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Default": - returnContentType = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == contentType) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ContentType")); - } - - // if the advertise state has not been set, default to non-advertised - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - this.Core.ParseForExtensionElements(node); - - if (YesNoType.Yes == advertise) - { - if (YesNoType.Yes != parentAdvertised) - { - this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), parentAdvertised.ToString())); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MIMESymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, contentType)) - { - ContentType = contentType, - ExtensionRef = extension, - CLSID = classId - }); - } - } - else if (YesNoType.No == advertise) - { - if (YesNoType.Yes == returnContentType && YesNoType.Yes == parentAdvertised) - { - this.Core.Write(ErrorMessages.CannotDefaultMismatchedAdvertiseStates(sourceLineNumbers)); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "Extension", String.Concat(".", extension), componentId); - if (null != classId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "CLSID", classId, componentId); - } - } - - return YesNoType.Yes == returnContentType ? contentType : null; - } - - /// - /// Parses a patch property element. - /// - /// The element to parse. - /// True if parsing an patch element. - private void ParsePatchPropertyElement(XElement node, bool patch) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string name = null; - string company = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Company": - company = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (patch) - { - // /Patch/PatchProperty goes directly into MsiPatchMetadata table - this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, company, name)) - { - Company = company, - Property = name, - Value = value - }); - } - else - { - if (null != company) - { - this.Core.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); - } - this.AddPrivateProperty(sourceLineNumbers, name, value); - } - } - - /// - /// Adds a row to the properties table. - /// - /// Source line numbers. - /// Name of the property. - /// Value of the property. - private void AddPrivateProperty(SourceLineNumber sourceLineNumbers, string name, string value) - { - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new PropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) - { - Value = value - }); - } - } - - /// - /// Parses a TargetProductCode element. - /// - /// The element to parse. - /// The id from the node. - private string ParseTargetProductCodeElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (id.Length > 0 && "*" != id) - { - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return id; - } - - /// - /// Parses a ReplacePatch element. - /// - /// The element to parse. - /// The id from the node. - private string ParseReplacePatchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return id; - } - - /// - /// Parses a symbol path element. - /// - /// The element to parse. - /// The path from the node. - private string ParseSymbolPathElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string path = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Path": - path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == path) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Path")); - } - - this.Core.ParseForExtensionElements(node); - - return path; - } - - /// - /// Parses the All element under a PatchFamily. - /// - /// The element to parse. - private void ParseAllElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - // find unexpected attributes - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - this.Core.UnexpectedAttribute(node, attrib); - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - // Always warn when using the All element. - this.Core.Write(WarningMessages.AllChangesIncludedInPatch(sourceLineNumbers)); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers) - { - Table = "*", - PrimaryKeys = "*", - }); - } - } - - /// - /// Parses all reference elements under a PatchFamily. - /// - /// The element to parse. - /// Table that reference was made to. - private void ParsePatchChildRefElement(XElement node, string tableName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers) - { - Table = tableName, - PrimaryKeys = id - }); - } - } - - /// - /// Parses a PatchBaseline element. - /// - /// The element to parse. - /// Media index from parent element. - private void ParsePatchBaselineElement(XElement node, int? diskId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var parsedValidate = false; - var validationFlags = TransformFlags.PatchTransformDefault; - string baselineFile = null; - string updateFile = null; - string transformFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "BaselineFile": - baselineFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UpdateFile": - updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "TransformFile": - transformFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (27 < id.Id.Length) - { - this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, 27)); - } - - if (!String.IsNullOrEmpty(baselineFile) || !String.IsNullOrEmpty(updateFile)) - { - if (String.IsNullOrEmpty(baselineFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "UpdateFile")); - } - - if (String.IsNullOrEmpty(updateFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile", "BaselineFile")); - } - - if (!String.IsNullOrEmpty(transformFile)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TransformFile", !String.IsNullOrEmpty(baselineFile) ? "BaselineFile" : "UpdateFile")); - } - } - else if (String.IsNullOrEmpty(transformFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "TransformFile", true)); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Validate": - if (parsedValidate) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); - } - else - { - this.ParseValidateElement(child, ref validationFlags); - parsedValidate = true; - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPatchBaselineSymbol(sourceLineNumbers, id) - { - DiskId = diskId ?? 1, - ValidationFlags = validationFlags, - BaselineFile = new IntermediateFieldPathValue { Path = baselineFile }, - UpdateFile = new IntermediateFieldPathValue { Path = updateFile }, - TransformFile = new IntermediateFieldPathValue { Path = transformFile }, - }); - } - } - - /// - /// Parses a Validate element. - /// - /// The element to parse. - /// TransformValidation flags to use when creating the authoring patch transform. - private void ParseValidateElement(XElement node, ref TransformFlags validationFlags) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ProductId": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ValidateProduct; - } - else - { - validationFlags &= ~TransformFlags.ValidateProduct; - } - break; - case "ProductLanguage": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ValidateLanguage; - } - else - { - validationFlags &= ~TransformFlags.ValidateLanguage; - } - break; - case "ProductVersion": - var check = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - validationFlags &= ~TransformFlags.ProductVersionMask; - switch (check) - { - case "Major": - case "major": - validationFlags |= TransformFlags.ValidateMajorVersion; - break; - case "Minor": - case "minor": - validationFlags |= TransformFlags.ValidateMinorVersion; - break; - case "Update": - case "update": - validationFlags |= TransformFlags.ValidateUpdateVersion; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Version", check, "Major", "Minor", "Update")); - break; - } - break; - case "ProductVersionOperator": - var op = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - validationFlags &= ~TransformFlags.ProductVersionOperatorMask; - switch (op) - { - case "Lesser": - case "lesser": - validationFlags |= TransformFlags.ValidateNewLessBaseVersion; - break; - case "LesserOrEqual": - case "lesserOrEqual": - validationFlags |= TransformFlags.ValidateNewLessEqualBaseVersion; - break; - case "Equal": - case "equal": - validationFlags |= TransformFlags.ValidateNewEqualBaseVersion; - break; - case "GreaterOrEqual": - case "greaterOrEqual": - validationFlags |= TransformFlags.ValidateNewGreaterEqualBaseVersion; - break; - case "Greater": - case "greater": - validationFlags |= TransformFlags.ValidateNewGreaterBaseVersion; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Operator", op, "Lesser", "LesserOrEqual", "Equal", "GreaterOrEqual", "Greater")); - break; - } - break; - case "UpgradeCode": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ValidateUpgradeCode; - } - else - { - validationFlags &= ~TransformFlags.ValidateUpgradeCode; - } - break; - case "IgnoreAddExistingRow": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorAddExistingRow; - } - else - { - validationFlags &= ~TransformFlags.ErrorAddExistingRow; - } - break; - case "IgnoreAddExistingTable": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorAddExistingTable; - } - else - { - validationFlags &= ~TransformFlags.ErrorAddExistingTable; - } - break; - case "IgnoreDeleteMissingRow": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorDeleteMissingRow; - } - else - { - validationFlags &= ~TransformFlags.ErrorDeleteMissingRow; - } - break; - case "IgnoreDeleteMissingTable": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorDeleteMissingTable; - } - else - { - validationFlags &= ~TransformFlags.ErrorDeleteMissingTable; - } - break; - case "IgnoreUpdateMissingRow": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorUpdateMissingRow; - } - else - { - validationFlags &= ~TransformFlags.ErrorUpdateMissingRow; - } - break; - case "IgnoreChangingCodePage": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - validationFlags |= TransformFlags.ErrorChangeCodePage; - } - else - { - validationFlags &= ~TransformFlags.ErrorChangeCodePage; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - } - - private string HandleSubdirectory(SourceLineNumber sourceLineNumbers, XElement element, string directoryId, string subdirectory, string directoryAttributeName, string subdirectoryAttributename) - { - if (!String.IsNullOrEmpty(subdirectory)) - { - if (String.IsNullOrEmpty(directoryId)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, element.Name.LocalName, subdirectoryAttributename, directoryAttributeName)); - } - else - { - directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); - } - } - - return directoryId; - } - } -} diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs deleted file mode 100644 index 727084eb..00000000 --- a/src/WixToolset.Core/CompilerCore.cs +++ /dev/null @@ -1,1166 +0,0 @@ -// 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 -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Reflection; - using System.Text; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal enum ValueListKind - { - /// - /// A list of values with nothing before the final value. - /// - None, - - /// - /// A list of values with 'and' before the final value. - /// - And, - - /// - /// A list of values with 'or' before the final value. - /// - Or - } - - /// - /// Core class for the compiler. - /// - internal class CompilerCore - { - internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; - internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; - - // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113) - private static readonly List BuiltinBundleVariables = new List( - new string[] { - "AdminToolsFolder", - "AppDataFolder", - "CommonAppDataFolder", - "CommonFiles64Folder", - "CommonFilesFolder", - "CompatibilityMode", - "Date", - "DesktopFolder", - "FavoritesFolder", - "FontsFolder", - "InstallerName", - "InstallerVersion", - "LocalAppDataFolder", - "LogonUser", - "MyPicturesFolder", - "NTProductType", - "NTSuiteBackOffice", - "NTSuiteDataCenter", - "NTSuiteEnterprise", - "NTSuitePersonal", - "NTSuiteSmallBusiness", - "NTSuiteSmallBusinessRestricted", - "NTSuiteWebServer", - "PersonalFolder", - "Privileged", - "ProgramFiles64Folder", - "ProgramFiles6432Folder", - "ProgramFilesFolder", - "ProgramMenuFolder", - "RebootPending", - "SendToFolder", - "ServicePackLevel", - "StartMenuFolder", - "StartupFolder", - "System64Folder", - "SystemFolder", - "TempFolder", - "TemplateFolder", - "TerminalServer", - "UserLanguageID", - "UserUILanguageID", - "VersionMsi", - "VersionNT", - "VersionNT64", - "WindowsFolder", - "WindowsVolume", - "WixBundleAction", - "WixBundleForcedRestartPackage", - "WixBundleElevated", - "WixBundleInstalled", - "WixBundleProviderKey", - "WixBundleTag", - "WixBundleVersion", - }); - - private static readonly List DisallowedMsiProperties = new List( - new string[] { - "ACTION", - "ADDLOCAL", - "ADDSOURCE", - "ADDDEFAULT", - "ADVERTISE", - "ALLUSERS", - "REBOOT", - "REINSTALL", - "REINSTALLMODE", - "REMOVE" - }); - - private readonly Dictionary extensions; - private readonly IParseHelper parseHelper; - private readonly Intermediate intermediate; - private readonly IMessaging messaging; - private Dictionary activeSectionCachedInlinedDirectoryIds; - private HashSet activeSectionSimpleReferences; - - /// - /// Constructor for all compiler core. - /// - /// The Intermediate object representing compiled source document. - /// - /// - /// The WiX extensions collection. - internal CompilerCore(Intermediate intermediate, IMessaging messaging, IParseHelper parseHelper, Dictionary extensions) - { - this.extensions = extensions; - this.parseHelper = parseHelper; - this.intermediate = intermediate; - this.messaging = messaging; - } - - /// - /// Gets the section the compiler is currently emitting symbols into. - /// - /// The section the compiler is currently emitting symbols into. - public IntermediateSection ActiveSection { get; private set; } - - /// - /// Gets whether the compiler core encountered an error while processing. - /// - /// Flag if core encountered an error during processing. - public bool EncounteredError => this.messaging.EncounteredError; - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - public bool ShowPedanticMessages { get; set; } - - /// - /// Add a symbol to the active section. - /// - /// Symbol to add. - public T AddSymbol(T symbol) - where T : IntermediateSymbol - { - return this.ActiveSection.AddSymbol(symbol); - } - - /// - /// Convert a bit array into an int value. - /// - /// The bit array to convert. - /// The converted int value. - public int CreateIntegerFromBitArray(BitArray bits) - { - if (32 != bits.Length) - { - throw new ArgumentException(String.Format("Can only convert a bit array with 32-bits to integer. Actual number of bits in array: {0}", bits.Length), "bits"); - } - - int[] intArray = new int[1]; - bits.CopyTo(intArray, 0); - - return intArray[0]; - } - - /// - /// Sets a bit in a bit array based on the index at which an attribute name was found in a string array. - /// - /// Array of attributes that map to bits. - /// Name of attribute to check. - /// Value of attribute to check. - /// The bit array in which the bit will be set if found. - /// The offset into the bit array. - /// true if the bit was set; false otherwise. - public bool TrySetBitFromName(string[] attributeNames, string attributeName, YesNoType attributeValue, BitArray bits, int offset) - { - for (int i = 0; i < attributeNames.Length; i++) - { - if (attributeName.Equals(attributeNames[i], StringComparison.Ordinal)) - { - bits.Set(i + offset, YesNoType.Yes == attributeValue); - return true; - } - } - - return false; - } - - internal void InnerTextDisallowed(XElement element) - { - this.parseHelper.InnerTextDisallowed(element); - } - - /// - /// Verifies that a filename is ambiguous. - /// - /// Filename to verify. - /// true if the filename is ambiguous; false otherwise. - public static bool IsAmbiguousFilename(string filename) - { - if (String.IsNullOrEmpty(filename)) - { - return false; - } - - var tilde = filename.IndexOf('~'); - return (tilde > 0 && tilde < filename.Length) && Char.IsNumber(filename[tilde + 1]); - } - - /// - /// Verifies that a value is a legal identifier. - /// - /// The value to verify. - /// true if the value is an identifier; false otherwise. - public bool IsValidIdentifier(string value) - { - return this.parseHelper.IsValidIdentifier(value); - } - - /// - /// Verifies if an identifier is a valid loc identifier. - /// - /// Identifier to verify. - /// True if the identifier is a valid loc identifier. - public bool IsValidLocIdentifier(string identifier) - { - return this.parseHelper.IsValidLocIdentifier(identifier); - } - - /// - /// Verifies if a filename is a valid long filename. - /// - /// Filename to verify. - /// true if wildcards are allowed in the filename. - /// true if relative paths are allowed in the filename. - /// True if the filename is a valid long filename - public bool IsValidLongFilename(string filename, bool allowWildcards = false, bool allowRelative = false) - { - return this.parseHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); - } - - /// - /// Verifies if a filename is a valid short filename. - /// - /// Filename to verify. - /// true if wildcards are allowed in the filename. - /// True if the filename is a valid short filename - public bool IsValidShortFilename(string filename, bool allowWildcards) - { - return this.parseHelper.IsValidShortFilename(filename, allowWildcards); - } - - /// - /// Replaces the illegal filename characters to create a legal name. - /// - /// Filename to make valid. - /// Replacement string for invalid characters in filename. - /// Valid filename. - public static string MakeValidLongFileName(string filename, char replace) - { - if (String.IsNullOrEmpty(filename)) - { - return filename; - } - - StringBuilder sb = null; - - var found = filename.IndexOfAny(Common.IllegalLongFilenameCharacters); - while (found != -1) - { - if (sb == null) - { - sb = new StringBuilder(filename); - } - - sb[found] = replace; - - found = (found + 1 < filename.Length) ? filename.IndexOfAny(Common.IllegalLongFilenameCharacters, found + 1) : -1; - } - - return sb?.ToString() ?? filename; - } - - /// - /// Verifies the given string is a valid product version. - /// - /// The product version to verify. - /// True if version is a valid product version - public static bool IsValidProductVersion(string version) - { - if (!Common.IsValidBinderVariable(version)) - { - Version ver = new Version(version); - - if (255 < ver.Major || 255 < ver.Minor || 65535 < ver.Build) - { - return false; - } - } - - return true; - } - - /// - /// Verifies the given string is a valid module or bundle version. - /// - /// The version to verify. - /// True if version is a valid module or bundle version. - public static bool IsValidModuleOrBundleVersion(string version) - { - return Common.IsValidFourPartVersion(version); - } - - /// - /// Creates group and ordering information. - /// - /// Source line numbers. - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of this item. - /// Identifier for this item. - /// Type of previous item, if known. - /// Identifier of previous item, if known - public void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers, - ComplexReferenceParentType parentType, string parentId, - ComplexReferenceChildType type, string id, - ComplexReferenceChildType previousType, string previousId) - { - if (this.EncounteredError) - { - return; - } - - if (parentType != ComplexReferenceParentType.Unknown && parentId != null) - { - this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id); - } - - if (previousType != ComplexReferenceChildType.Unknown && previousId != null) - { - // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"? - // TODO: Also, we could potentially include an 'Attributes' field to track things like - // 'before' vs. 'after', and explicit vs. inferred dependencies. - this.AddSymbol(new WixOrderingSymbol(sourceLineNumbers) - { - ItemType = type, - ItemIdRef = id, - DependsOnType = previousType, - DependsOnIdRef = previousId, - }); - } - } - - /// - /// Creates a version 3 name-based UUID. - /// - /// The namespace UUID. - /// The value. - /// The generated GUID for the given namespace and value. - public string CreateGuid(Guid namespaceGuid, string value) - { - return this.parseHelper.CreateGuid(namespaceGuid, value); - } - - /// - /// Creates directories using the inline directory syntax. - /// - /// Source line information. - /// Optional identifier of parent directory. - /// Optional inline syntax to override attribute's value. - /// Identifier of the leaf directory created. - public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, string parentId, string inlineSyntax = null) - { - return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute: null, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds); - } - - /// - /// Creates a Registry row in the active section. - /// - /// Source and line number of the current row. - /// The registry entry root. - /// The registry entry key. - /// The registry entry name. - /// The registry entry value. - /// The component which will control installation/uninstallation of the registry entry. - public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId) - { - return this.parseHelper.CreateRegistrySymbol(this.ActiveSection, sourceLineNumbers, root, key, name, value, componentId, true); - } - - /// - /// Create a WixSimpleReferenceSymbol in the active section. - /// - /// Source line information for the row. - /// The symbol name of the simple reference. - /// The primary key of the simple reference. - public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) - { - if (!this.EncounteredError) - { - var id = String.Concat(symbolName, ":", primaryKey); - - // If this simple reference hasn't been added to the active section already, add it. - if (this.activeSectionSimpleReferences.Add(id)) - { - this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKey); - } - } - } - - /// - /// Create a WixSimpleReferenceSymbol in the active section. - /// - /// Source line information for the row. - /// The symbol name of the simple reference. - /// The primary keys of the simple reference. - public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) - { - if (!this.EncounteredError) - { - var joinedKeys = String.Join("/", primaryKeys); - var id = String.Concat(symbolName, ":", joinedKeys); - - // If this simple reference hasn't been added to the active section already, add it. - if (this.activeSectionSimpleReferences.Add(id)) - { - this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKeys); - } - } - } - - /// - /// Create a WixSimpleReferenceSymbol in the active section. - /// - /// Source line information for the row. - /// The symbol definition of the simple reference. - /// The primary key of the simple reference. - public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) - { - this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKey); - } - - /// - /// Create a WixSimpleReferenceSymbol in the active section. - /// - /// Source line information for the row. - /// The symbol definition of the simple reference. - /// The primary keys of the simple reference. - public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) - { - this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKeys); - } - - /// - /// A row in the WixGroup table is added for this child node and its parent node. - /// - /// Source line information for the row. - /// Type of child's complex reference parent. - /// Id of the parenet node. - /// Complex reference type of child - /// Id of the Child Node. - public void CreateWixGroupRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) - { - if (!this.EncounteredError) - { - this.parseHelper.CreateWixGroupSymbol(this.ActiveSection, sourceLineNumbers, parentType, parentId, childType, childId); - } - } - - /// - /// Add the appropriate symbols to make sure that the given table shows up - /// in the resulting output. - /// - /// Source line numbers. - /// Name of the table to ensure existance of. - public void EnsureTable(SourceLineNumber sourceLineNumbers, string tableName) - { - if (!this.EncounteredError) - { - this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableName); - } - } - - /// - /// Add the appropriate symbols to make sure that the given table shows up - /// in the resulting output. - /// - /// Source line numbers. - /// Definition of the table to ensure existance of. - public void EnsureTable(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) - { - if (!this.EncounteredError) - { - this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableDefinition); - } - } - - /// - /// Get an attribute value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// A rule for the contents of the value. If the contents do not follow the rule, an error is thrown. - /// The attribute's value. - public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) - { - return this.parseHelper.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); - } - - /// - /// Get a valid code page by web name or number from a string attribute. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// A valid code page integer value. - public int GetAttributeCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - if (null == attribute) - { - throw new ArgumentNullException(nameof(attribute)); - } - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - try - { - return Common.GetValidCodePage(value); - } - catch (NotSupportedException) - { - this.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); - } - - return CompilerConstants.IllegalInteger; - } - - /// - /// Get a valid code page by web name or number from a string attribute. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// Whether to allow Unicode (UCS) or UTF code pages. - /// A valid code page integer value or variable expression. - public string GetAttributeLocalizableCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool onlyAnsi = false) - { - if (null == attribute) - { - throw new ArgumentNullException(nameof(attribute)); - } - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - // Allow for localization of code page names and values. - if (this.IsValidLocIdentifier(value)) - { - return value; - } - - try - { - var codePage = Common.GetValidCodePage(value, false, onlyAnsi, sourceLineNumbers); - return codePage.ToString(CultureInfo.InvariantCulture); - } - catch (NotSupportedException) - { - // Not a valid windows code page. - this.messaging.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); - } - catch (WixException e) - { - this.messaging.Write(e.Error); - } - - return null; - } - - /// - /// Get an integer attribute value and displays an error for an illegal integer value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The minimum legal value. - /// The maximum legal value. - /// The attribute's integer value or a special value if an error occurred during conversion. - public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) - { - return this.parseHelper.GetAttributeIntegerValue(sourceLineNumbers, attribute, minimum, maximum); - } - - /// - /// Get a long integral attribute value and displays an error for an illegal long value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The minimum legal value. - /// The maximum legal value. - /// The attribute's long value or a special value if an error occurred during conversion. - public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) - { - return this.parseHelper.GetAttributeLongValue(sourceLineNumbers, attribute, minimum, maximum); - } - - /// - /// Get a date time attribute value and display errors for illegal values. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// Int representation of the date time. - public int GetAttributeDateTimeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - if (null == attribute) - { - throw new ArgumentNullException("attribute"); - } - - string value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (0 < value.Length) - { - try - { - DateTime date = DateTime.Parse(value, CultureInfo.InvariantCulture.DateTimeFormat); - - return ((((date.Year - 1980) * 512) + (date.Month * 32 + date.Day)) * 65536) + - (date.Hour * 2048) + (date.Minute * 32) + (date.Second / 2); - } - catch (ArgumentOutOfRangeException) - { - this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - catch (FormatException) - { - this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - catch (OverflowException) - { - this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return CompilerConstants.IllegalInteger; - } - - /// - /// Get an integer attribute value or localize variable and displays an error for - /// an illegal value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The minimum legal value. - /// The maximum legal value. - /// The attribute's integer value or localize variable as a string or a special value if an error occurred during conversion. - public string GetAttributeLocalizableIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) - { - if (null == attribute) - { - throw new ArgumentNullException("attribute"); - } - - Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (0 < value.Length) - { - if (this.IsValidLocIdentifier(value) || Common.IsValidBinderVariable(value)) - { - return value; - } - else - { - try - { - var integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat); - - if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) - { - this.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); - } - else if (minimum > integer || maximum < integer) - { - this.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); - integer = CompilerConstants.IllegalInteger; - } - - return value; - } - catch (FormatException) - { - this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - catch (OverflowException) - { - this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - } - - return null; - } - - /// - /// Get a guid attribute value and displays an error for an illegal guid value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// Determines whether the guid can be automatically generated. - /// If true, no error is raised on empty value. If false, an error is raised. - /// The attribute's guid value or a special value if an error occurred. - public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) - { - return this.parseHelper.GetAttributeGuidValue(sourceLineNumbers, attribute, generatable, canBeEmpty); - } - - /// - /// Get an identifier attribute value and displays an error for an illegal identifier value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's identifier value or a special value if an error occurred. - public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return this.parseHelper.GetAttributeIdentifier(sourceLineNumbers, attribute); - } - - /// - /// Get an identifier attribute value and displays an error for an illegal identifier value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's identifier value or a special value if an error occurred. - public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return this.parseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attribute); - } - - /// - /// Gets a yes/no value and displays an error for an illegal yes/no value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's YesNoType value. - public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return this.parseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute); - } - - /// - /// Gets a yes/no/default value and displays an error for an illegal yes/no value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's YesNoDefaultType value. - public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return this.parseHelper.GetAttributeYesNoDefaultValue(sourceLineNumbers, attribute); - } - - /// - /// Gets a short filename value and displays an error for an illegal short filename value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// true if wildcards are allowed in the filename. - /// The attribute's short filename value. - public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false) - { - if (null == attribute) - { - throw new ArgumentNullException("attribute"); - } - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (0 < value.Length) - { - if (!this.parseHelper.IsValidShortFilename(value, allowWildcards) && !Common.ContainsValidBinderVariable(value)) - { - this.Write(ErrorMessages.IllegalShortFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - else if (CompilerCore.IsAmbiguousFilename(value)) - { - this.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return value; - } - - /// - /// Gets a long filename value and displays an error for an illegal long filename value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// true if wildcards are allowed in the filename. - /// true if relative paths are allowed in the filename. - /// The attribute's long filename value. - public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false, bool allowRelative = false) - { - return this.parseHelper.GetAttributeLongFilename(sourceLineNumbers, attribute, allowWildcards, allowRelative); - } - - /// - /// Gets a version value or possibly a binder variable and displays an error for an illegal version value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's version value. - public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return this.parseHelper.GetAttributeVersionValue(sourceLineNumbers, attribute); - } - - /// - /// Gets a RegistryRoot as a MsiInterop.MsidbRegistryRoot value and displays an error for an illegal value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// Whether HKMU is returned as -1 (true), or treated as an error (false). - /// The attribute's RegisitryRootType value. - public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) - { - return this.parseHelper.GetAttributeRegistryRootValue(sourceLineNumbers, attribute, allowHkmu); - } - - /// - /// Gets a Bundle variable value and displays an error for an illegal value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's value. - public string GetAttributeBundleVariableValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - string value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (!String.IsNullOrEmpty(value)) - { - if (CompilerCore.BuiltinBundleVariables.Contains(value)) - { - string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.BuiltinBundleVariables); - this.Write(ErrorMessages.IllegalAttributeValueWithIllegalList(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, illegalValues)); - } - } - - return value; - } - - /// - /// Gets an MsiProperty name value and displays an error for an illegal value. - /// - /// Source line information about the owner element. - /// The attribute containing the value to get. - /// The attribute's value. - public string GetAttributeMsiPropertyNameValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - string value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (0 < value.Length) - { - if (CompilerCore.DisallowedMsiProperties.Contains(value)) - { - string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.DisallowedMsiProperties); - this.Write(ErrorMessages.DisallowedMsiProperty(sourceLineNumbers, value, illegalValues)); - } - } - - return value; - } - - /// - /// Checks if the string contains a property (i.e. "foo[Property]bar") - /// - /// String to evaluate for properties. - /// True if a property is found in the string. - public bool ContainsProperty(string possibleProperty) - { - return this.parseHelper.ContainsProperty(possibleProperty); - } - - /// - /// Generate an identifier by hashing data from the row. - /// - /// Three letter or less prefix for generated row identifier. - /// Information to hash. - /// The generated identifier. - public Identifier CreateIdentifier(string prefix, params string[] args) - { - return this.parseHelper.CreateIdentifier(prefix, args); - } - - /// - /// Create an identifier based on passed file name - /// - /// File name to generate identifer from - /// - public Identifier CreateIdentifierFromFilename(string filename) - { - return this.parseHelper.CreateIdentifierFromFilename(filename); - } - - /// - /// Attempts to use an extension to parse the attribute. - /// - /// Element containing attribute to be parsed. - /// Attribute to be parsed. - /// Extra information about the context in which this element is being parsed. - public void ParseExtensionAttribute(XElement element, XAttribute attribute, IDictionary context = null) - { - this.parseHelper.ParseExtensionAttribute(this.extensions.Values, this.intermediate, this.ActiveSection, element, attribute, context); - } - - /// - /// Attempts to use an extension to parse the element. - /// - /// Element containing element to be parsed. - /// Element to be parsed. - /// Extra information about the context in which this element is being parsed. - public void ParseExtensionElement(XElement parentElement, XElement element, IDictionary context = null) - { - this.parseHelper.ParseExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); - } - - /// - /// Process all children of the element looking for extensions and erroring on the unexpected. - /// - /// Element to parse children. - public void ParseForExtensionElements(XElement element) - { - this.parseHelper.ParseForExtensionElements(this.extensions.Values, this.intermediate, this.ActiveSection, element); - } - - /// - /// Attempts to use an extension to parse the element, with support for setting component keypath. - /// - /// Element containing element to be parsed. - /// Element to be parsed. - /// Extra information about the context in which this element is being parsed. - public IComponentKeyPath ParsePossibleKeyPathExtensionElement(XElement parentElement, XElement element, IDictionary context) - { - return this.parseHelper.ParsePossibleKeyPathExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); - } - - /// - /// Displays an unexpected attribute error if the attribute is not the namespace attribute. - /// - /// Element containing unexpected attribute. - /// The unexpected attribute. - public void UnexpectedAttribute(XElement element, XAttribute attribute) - { - this.parseHelper.UnexpectedAttribute(element, attribute); - } - - /// - /// Display an unexepected element error. - /// - /// The parent element. - /// The unexpected child element. - public void UnexpectedElement(XElement parentElement, XElement childElement) - { - this.parseHelper.UnexpectedElement(parentElement, childElement); - } - - /// - /// Sends a message. - /// - /// Message to write. - public void Write(Message message) - { - this.messaging.Write(message); - } - - /// - /// Verifies that the calling assembly version is equal to or newer than the given . - /// - /// Source line information about the owner element. - /// The version required of the calling assembly. - internal void VerifyRequiredVersion(SourceLineNumber sourceLineNumbers, string requiredVersion) - { - // an null or empty string means any version will work - if (!String.IsNullOrEmpty(requiredVersion)) - { - Assembly caller = Assembly.GetCallingAssembly(); - AssemblyName name = caller.GetName(); - FileVersionInfo fv = FileVersionInfo.GetVersionInfo(caller.Location); - - Version versionRequired = new Version(requiredVersion); - Version versionCurrent = new Version(fv.FileVersion); - - if (versionRequired > versionCurrent) - { - if (this.GetType().Assembly.Equals(caller)) - { - this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired)); - } - else - { - this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired, name.Name)); - } - } - } - } - - /// - /// Creates a new section and makes it the active section in the core. - /// - /// Unique identifier for the section. - /// Type of section to create. - /// Unique identifier for the compilation. - /// New section. - internal IntermediateSection CreateActiveSection(string id, SectionType type, string compilationId) - { - this.ActiveSection = this.CreateSection(id, type, compilationId); - - this.activeSectionCachedInlinedDirectoryIds = new Dictionary(); - this.activeSectionSimpleReferences = new HashSet(); - - return this.ActiveSection; - } - - /// - /// Creates a new section. - /// - /// Unique identifier for the section. - /// Type of section to create. - /// Unique identifier for the compilation. - /// New section. - internal IntermediateSection CreateSection(string id, SectionType type, string compilationId) - { - var section = new IntermediateSection(id, type, compilationId); - - this.intermediate.AddSection(section); - - return section; - } - - /// - /// Creates WixComplexReference and WixGroup rows in the active section. - /// - /// Source line information. - /// The parent type. - /// The parent id. - /// The parent language. - /// The child type. - /// The child id. - /// Whether the child is primary. - public void CreateComplexReference(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) - { - this.parseHelper.CreateComplexReference(this.ActiveSection, sourceLineNumbers, parentType, parentId, parentLanguage, childType, childId, isPrimary); - } - - /// - /// Creates a directory row from a name. - /// - /// Source line information. - /// Optional identifier for the new row. - /// Optional identifier for the parent row. - /// Long name of the directory. - /// Optional short name of the directory. - /// Optional source name for the directory. - /// Optional short source name for the directory. - /// Identifier for the newly created row. - internal Identifier CreateDirectorySymbol(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) - { - return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, shortName, sourceName, shortSourceName); - } - - public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) - { - this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); - } - - internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false) - { - return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); - } - - private static string CreateValueList(ValueListKind kind, IEnumerable values) - { - // Ideally, we could denote the list kind (and the list itself) directly in the - // message XML, and detect and expand in the MessageHandler.GenerateMessageString() - // method. Doing so would make vararg-style messages much easier, but impacts - // every single message we format. For now, callers just have to know when a - // message takes a list of values in a single string argument, the caller will - // have to do the expansion themselves. (And, unfortunately, hard-code the knowledge - // that the list is an 'and' or 'or' list.) - - // For a localizable solution, we need to be able to get the list format string - // from resources. We aren't currently localized right now, so the values are - // just hard-coded. - const string valueFormat = "'{0}'"; - const string valueSeparator = ", "; - string terminalTerm = String.Empty; - - switch (kind) - { - case ValueListKind.None: - terminalTerm = ""; - break; - case ValueListKind.And: - terminalTerm = "and "; - break; - case ValueListKind.Or: - terminalTerm = "or "; - break; - } - - StringBuilder list = new StringBuilder(); - - // This weird construction helps us determine when we're adding the last value - // to the list. Instead of adding them as we encounter them, we cache the current - // value and append the *previous* one. - string previousValue = null; - bool haveValues = false; - foreach (string value in values) - { - if (null != previousValue) - { - if (haveValues) - { - list.Append(valueSeparator); - } - list.AppendFormat(valueFormat, previousValue); - haveValues = true; - } - - previousValue = value; - } - - // If we have no previous value, that means that the list contained no values, and - // something has gone very wrong. - Debug.Assert(null != previousValue); - if (null != previousValue) - { - if (haveValues) - { - list.Append(valueSeparator); - list.Append(terminalTerm); - } - list.AppendFormat(valueFormat, previousValue); - haveValues = true; - } - - return list.ToString(); - } - } -} diff --git a/src/WixToolset.Core/CompilerErrors.cs b/src/WixToolset.Core/CompilerErrors.cs deleted file mode 100644 index 10646dfd..00000000 --- a/src/WixToolset.Core/CompilerErrors.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 -{ - using WixToolset.Data; - - internal static class CompilerErrors - { - public static Message IllegalCharactersInProvider(SourceLineNumber sourceLineNumbers, string attributeName, char illegalChar, string illegalChars) - { - return Message(sourceLineNumbers, Ids.IllegalCharactersInProvider, "The provider key authored into the {0} attribute contains an illegal character, '{1}'. Please author the provider key without any of the following characters: {2}", attributeName, illegalChar, illegalChars); - } - - public static Message ReservedValue(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string attributeValue) - { - return Message(sourceLineNumbers, Ids.ReservedValue, "The {0}/@{1} attribute value '{2}' is reserved and cannot be used here. Please choose a different value.", elementName, attributeName, attributeValue); - } - - public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name) - { - return Message(sourceLineNumbers, Ids.IllegalName, "The Tag/@Name attribute value, '{1}', contains invalid filename identifiers. The Tag/@Name may have defaulted from the {0}/@Name attrbute. If so, use the Tag/@Name attribute to provide a valid filename. Any character except for the follow may be used: \\ ? | > < : / * \".", parentElement, name); - } - - public static Message ExampleRegid(SourceLineNumber sourceLineNumbers, string regid) - { - return Message(sourceLineNumbers, Ids.ExampleRegid, "Regid '{0}' is a placeholder that must be replaced with an appropriate value for your installation. Use the simplified URI for your organization or project.", regid); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); - } - - public enum Ids - { - IllegalCharactersInProvider = 5400, - ReservedValue = 5401, - - IllegalName = 6601, - ExampleRegid = 6602, - } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. - } -} diff --git a/src/WixToolset.Core/CompilerWarnings.cs b/src/WixToolset.Core/CompilerWarnings.cs deleted file mode 100644 index 5c11b878..00000000 --- a/src/WixToolset.Core/CompilerWarnings.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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 -{ - using WixToolset.Data; - - internal static class CompilerWarnings - { - public static Message DirectoryRefStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) - { - return Message(sourceLineNumbers, Ids.DirectoryRefStandardDirectoryDeprecated, "Using DirectoryRef to reference the standard directory '{0}' is deprecated. Use the StandardDirectory element instead.", directoryId); - } - - public static Message DefiningStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) - { - return Message(sourceLineNumbers, Ids.DefiningStandardDirectoryDeprecated, "It is no longer necessary to define the standard directory '{0}'. Use the StandardDirectory element instead.", directoryId); - } - - public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers) - { - return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified in an MSI package. The ProductVersion will be used by default."); - } - - public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers, string id) - { - return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified for MSI package {0}. The ProductVersion will be used by default.", id); - } - - public static Message PropertyRemoved(string name) - { - return Message(null, Ids.PropertyRemoved, "The property {0} was authored in the package with a value and will be removed. The property should not be authored.", name); - } - - public static Message ProvidesKeyNotFound(SourceLineNumber sourceLineNumbers, string id) - { - return Message(sourceLineNumbers, Ids.ProvidesKeyNotFound, "The provider key with identifier {0} was not found in the WixDependencyProvider table. Related registry rows will not be removed from authoring.", id); - } - - public static Message RequiresKeyNotFound(SourceLineNumber sourceLineNumbers, string id) - { - return Message(sourceLineNumbers, Ids.RequiresKeyNotFound, "The dependency key with identifier {0} was not found in the WixDependency table. Related registry rows will not be removed from authoring.", id); - } - - public static Message Win64Component(SourceLineNumber sourceLineNumbers, string componentId) - { - return Message(sourceLineNumbers, Ids.Win64Component, "The Provides element should not be authored in the 64-bit component with identifier {0}. The dependency feature may not work if installing this package on 64-bit Windows operating systems prior to Windows 7 and Windows Server 2008 R2. Set the Component/@Bitness attribute to \"always32\" to ensure the dependency feature works correctly on legacy operating systems.", componentId); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); - } - - public enum Ids - { - ProvidesKeyNotFound = 5431, - RequiresKeyNotFound = 5432, - PropertyRemoved = 5433, - DiscouragedVersionAttribute = 5434, - Win64Component = 5435, - DirectoryRefStandardDirectoryDeprecated = 5436, - DefiningStandardDirectoryDeprecated = 5437, - } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. - } -} diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs deleted file mode 100644 index 6d2e75f7..00000000 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ /dev/null @@ -1,3266 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - private static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnUXContainerName); - private static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnDefaultAttachedContainerName); - private static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Section, BurnConstants.BundleLayoutOnlyPayloadsName); - - /// - /// Parses an ApprovedExeForElevation element. - /// - /// Element to parse - private void ParseApprovedExeForElevation(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string valueName = null; - var win64 = this.Context.IsCurrentPlatform64Bit; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Bitness": - var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (bitnessValue) - { - case "always32": - win64 = false; - break; - case "always64": - win64 = true; - break; - case "default": - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); - break; - } - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - valueName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - var attributes = WixApprovedExeForElevationAttributes.None; - - if (win64) - { - attributes |= WixApprovedExeForElevationAttributes.Win64; - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixApprovedExeForElevationSymbol(sourceLineNumbers, id) - { - Key = key, - ValueName = valueName, - Attributes = attributes, - }); - } - } - - /// - /// Parses a Bundle element. - /// - /// Element to parse - private void ParseBundleElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string copyright = null; - string aboutUrl = null; - var compressed = YesNoDefaultType.Default; - WixBundleAttributes attributes = 0; - string helpTelephone = null; - string helpUrl = null; - string manufacturer = null; - string name = null; - string tag = null; - string updateUrl = null; - string upgradeCode = null; - string version = null; - string condition = null; - string parentName = null; - - string fileSystemSafeBundleName = null; - string logVariablePrefixAndExtension = null; - string iconSourceFile = null; - string splashScreenSourceFile = null; - - // Process only standard attributes until the active section is initialized. - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AboutUrl": - aboutUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Compressed": - compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Copyright": - copyright = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisableModify": - var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (value) - { - case "button": - attributes |= WixBundleAttributes.SingleChangeUninstallButton; - break; - case "yes": - attributes |= WixBundleAttributes.DisableModify; - break; - case "no": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no")); - break; - } - break; - case "DisableRemove": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixBundleAttributes.DisableRemove; - } - break; - case "HelpTelephone": - helpTelephone = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "HelpUrl": - helpUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Manufacturer": - manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "IconSourceFile": - iconSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ParentName": - parentName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ProviderKey": - // This can't be processed until we create the section. - break; - case "SplashScreenSourceFile": - splashScreenSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Tag": - tag = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UpdateUrl": - updateUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UpgradeCode": - upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Version": - version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - if (String.IsNullOrEmpty(version)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) - { - this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Bundle", version)); - } - - if (String.IsNullOrEmpty(upgradeCode)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpgradeCode")); - } - - if (String.IsNullOrEmpty(copyright)) - { - if (String.IsNullOrEmpty(manufacturer)) - { - copyright = "Copyright (c). All rights reserved."; - } - else - { - copyright = String.Format("Copyright (c) {0}. All rights reserved.", manufacturer); - } - } - - if (String.IsNullOrEmpty(name)) - { - logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup:log"); - } - else - { - // Ensure only allowable path characters are in "name" (and change spaces to underscores). - fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), '_'); - logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":log"); - } - - this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; - this.Core.CreateActiveSection(this.activeName, SectionType.Bundle, this.Context.CompilationId); - - // Now that the active section is initialized, process only extension attributes and the special ProviderKey attribute. - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ProviderKey": - this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib); - break; - // Unknown attributes were reported earlier. - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - var baSeen = false; - var chainSeen = false; - var logSeen = false; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ApprovedExeForElevation": - this.ParseApprovedExeForElevation(child); - break; - case "BootstrapperApplication": - if (baSeen) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "BootstrapperApplication")); - } - this.ParseBootstrapperApplicationElement(child); - baSeen = true; - break; - case "BootstrapperApplicationRef": - this.ParseBootstrapperApplicationRefElement(child); - break; - case "BundleCustomData": - this.ParseBundleCustomDataElement(child); - break; - case "BundleCustomDataRef": - this.ParseBundleCustomDataRefElement(child); - break; - case "BundleExtension": - this.ParseBundleExtensionElement(child); - break; - case "BundleExtensionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); - break; - case "OptionalUpdateRegistration": - this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name); - break; - case "Chain": - if (chainSeen) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Chain")); - } - this.ParseChainElement(child); - chainSeen = true; - break; - case "Container": - this.ParseContainerElement(child); - break; - case "ContainerRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleContainer); - break; - case "Log": - if (logSeen) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Log")); - } - logVariablePrefixAndExtension = this.ParseLogElement(child, fileSystemSafeBundleName); - logSeen = true; - break; - case "PayloadGroup": - this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads); - break; - case "PayloadGroupRef": - this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads, ComplexReferenceChildType.Unknown, null); - break; - case "RelatedBundle": - this.ParseRelatedBundleElement(child); - break; - case "Requires": - this.ParseRequiresElement(child, null); - break; - case "SetVariable": - this.ParseSetVariableElement(child); - break; - case "SetVariableRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); - break; - case "SoftwareTag": - this.ParseBundleTagElement(child); - break; - case "Update": - this.ParseUpdateElement(child); - break; - case "Variable": - this.ParseVariableElement(child); - break; - case "WixVariable": - this.ParseWixVariableElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!chainSeen) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Chain")); - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new WixBundleSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeCode, - Version = version, - Copyright = copyright, - Name = name, - Manufacturer = manufacturer, - Attributes = attributes, - AboutUrl = aboutUrl, - HelpUrl = helpUrl, - HelpTelephone = helpTelephone, - UpdateUrl = updateUrl, - Compressed = YesNoDefaultType.Yes == compressed ? true : YesNoDefaultType.No == compressed ? (bool?)false : null, - IconSourceFile = iconSourceFile, - SplashScreenSourceFile = splashScreenSourceFile, - Condition = condition, - Tag = tag, - Platform = this.CurrentPlatform, - ParentName = parentName, - }); - - if (!String.IsNullOrEmpty(logVariablePrefixAndExtension)) - { - var split = logVariablePrefixAndExtension.Split(':'); - symbol.LogPathVariable = split[0]; - symbol.LogPrefix = split[1]; - symbol.LogExtension = split[2]; - } - - if (null != upgradeCode) - { - this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) - { - BundleId = upgradeCode, - Action = RelatedBundleActionType.Upgrade, - }); - } - - this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnDefaultAttachedContainerId) - { - Name = "bundle-attached.cab", - Type = ContainerType.Attached, - }); - - // Ensure that the bundle stores the well-known persisted values. - this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_NAME)) - { - Hidden = false, - Persisted = true, - }); - - this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE)) - { - Hidden = false, - Persisted = true, - }); - - this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER)) - { - Hidden = false, - Persisted = true, - }); - - this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_LAST_USED_SOURCE)) - { - Hidden = false, - Persisted = true, - }); - } - } - - /// - /// Parse a Container element. - /// - /// Element to parse - /// - private string ParseLogElement(XElement node, string fileSystemSafeBundleName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var disableLog = YesNoType.NotSet; - var variable = "WixBundleLog"; - var logPrefix = fileSystemSafeBundleName ?? "Setup"; - var logExtension = ".log"; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Disable": - disableLog = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "PathVariable": - variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Prefix": - logPrefix = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Extension": - logExtension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (!logExtension.StartsWith(".", StringComparison.Ordinal)) - { - logExtension = String.Concat(".", logExtension); - } - - this.Core.ParseForExtensionElements(node); - - return YesNoType.Yes == disableLog ? null : String.Join(":", variable, logPrefix, logExtension); - } - - /// - /// Parse a Container element. - /// - /// Element to parse - private void ParseContainerElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string downloadUrl = null; - string name = null; - var type = ContainerType.Detached; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - if (id?.Id == BurnConstants.BurnUXContainerName || id?.Id == BurnConstants.BurnDefaultAttachedContainerName) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - break; - case "DownloadUrl": - downloadUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Type": - var typeString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeString) - { - case "attached": - type = ContainerType.Attached; - break; - case "detached": - type = ContainerType.Detached; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - if (!String.IsNullOrEmpty(name)) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (!Common.IsIdentifier(id.Id)) - { - this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - } - else if (null == name) - { - name = id.Id; - } - - if (!String.IsNullOrEmpty(downloadUrl) && ContainerType.Detached != type) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "Type", "attached")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "PackageGroupRef": - this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.Container, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, id) - { - Name = name, - Type = type, - DownloadUrl = downloadUrl - }); - } - } - - /// - /// Parse the BoostrapperApplication element. - /// - /// Element to parse - private void ParseBootstrapperApplicationElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - Identifier previousId = null; - var previousType = ComplexReferenceChildType.Unknown; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "BootstrapperApplicationDll": - previousId = this.ParseBootstrapperApplicationDllElement(child, id, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "Payload": - previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "PayloadGroupRef": - previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.PayloadGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (id != null) - { - this.Core.AddSymbol(new WixBootstrapperApplicationSymbol(sourceLineNumbers, id)); - } - } - - /// - /// Parse the BoostrapperApplication element. - /// - /// Element to parse - /// - /// - /// - private Identifier ParseBootstrapperApplicationDllElement(XElement node, Identifier defaultId, ComplexReferenceChildType previousType, Identifier previousId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) - { - Id = defaultId, - }; - var dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - compilerPayload.ParseId(attrib); - break; - case "Name": - compilerPayload.ParseName(attrib); - break; - case "SourceFile": - compilerPayload.ParseSourceFile(attrib); - break; - case "DpiAwareness": - var dpiAwarenessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (dpiAwarenessValue) - { - case "gdiScaled": - dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.GdiScaled; - break; - case "perMonitor": - dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitor; - break; - case "perMonitorV2": - dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; - break; - case "system": - dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.System; - break; - case "unaware": - dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.Unaware; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "DpiAwareness", dpiAwarenessValue, "gdiScaled", "perMonitor", "perMonitorV2", "system", "unaware")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - extensionAttributes.Add(attrib); - } - } - - compilerPayload.FinishCompilingPayload(); - - // Now that the Id is known, we can parse the extension attributes. - var context = new Dictionary - { - ["Id"] = compilerPayload.Id.Id, - }; - - foreach (var extensionAttribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, extensionAttribute, context); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); - this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnUXContainerId) - { - Name = "bundle-ux.cab", - Type = ContainerType.Attached - }); - - this.Core.AddSymbol(new WixBootstrapperApplicationDllSymbol(sourceLineNumbers, compilerPayload.Id) - { - DpiAwareness = dpiAwareness, - }); - } - - return compilerPayload.Id; - } - - /// - /// Parse the BoostrapperApplicationRef element. - /// - /// Element to parse - private void ParseBootstrapperApplicationRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - Identifier previousId = null; - var previousType = ComplexReferenceChildType.Unknown; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Payload": - previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "PayloadGroupRef": - previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.PayloadGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (String.IsNullOrEmpty(id)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBootstrapperApplication, id); - } - } - - - - /// - /// Parses a BundleCustomData element. - /// - /// Element to parse. - private void ParseBundleCustomDataElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string customDataId = null; - WixBundleCustomDataType? customDataType = null; - string extensionId = null; - var attributeDefinitions = new List(); - var foundAttributeDefinitions = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "bootstrapperApplication": - customDataType = WixBundleCustomDataType.BootstrapperApplication; - break; - case "bundleExtension": - customDataType = WixBundleCustomDataType.BundleExtension; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "bootstrapperApplication", "bundleExtension")); - customDataType = WixBundleCustomDataType.Unknown; // set a value to prevent expected attribute error below. - break; - } - break; - case "ExtensionId": - extensionId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleExtension, extensionId); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == customDataId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - var hasExtensionId = null != extensionId; - if (!customDataType.HasValue) - { - customDataType = hasExtensionId ? WixBundleCustomDataType.BundleExtension : WixBundleCustomDataType.BootstrapperApplication; - } - - if (!customDataType.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); - } - else if (hasExtensionId) - { - if (customDataType.Value == WixBundleCustomDataType.BootstrapperApplication) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensonId", "Type", "bootstrapperApplication")); - } - } - else if (customDataType.Value == WixBundleCustomDataType.BundleExtension) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensionId", "Type", "bundleExtension")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "BundleAttributeDefinition": - foundAttributeDefinitions = true; - - var attributeDefinition = this.ParseBundleAttributeDefinitionElement(child, childSourceLineNumbers, customDataId); - if (attributeDefinition != null) - { - attributeDefinitions.Add(attributeDefinition); - } - break; - case "BundleElement": - this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (attributeDefinitions.Count > 0) - { - if (!this.Core.EncounteredError) - { - var attributeNames = String.Join(new string(WixBundleCustomDataSymbol.AttributeNamesSeparator, 1), attributeDefinitions.Select(c => c.Name)); - - this.Core.AddSymbol(new WixBundleCustomDataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, customDataId)) - { - AttributeNames = attributeNames, - Type = customDataType.Value, - BundleExtensionRef = extensionId, - }); - } - } - else if (!foundAttributeDefinitions) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "BundleAttributeDefinition")); - } - } - - /// - /// Parses a BundleCustomDataRef element. - /// - /// Element to parse. - private void ParseBundleCustomDataRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string customDataId = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == customDataId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "BundleElement": - this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses a BundleAttributeDefinition element. - /// - /// Element to parse. - /// Element's SourceLineNumbers. - /// BundleCustomData Id. - private WixBundleCustomDataAttributeSymbol ParseBundleAttributeDefinitionElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) - { - string attributeName = null; - - foreach (var attrib in node.Attributes()) - { - switch (attrib.Name.LocalName) - { - case "Id": - attributeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - - if (null == attributeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (this.Core.EncounteredError) - { - return null; - } - - var customDataAttribute = this.Core.AddSymbol(new WixBundleCustomDataAttributeSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, attributeName)) - { - CustomDataRef = customDataId, - Name = attributeName, - }); - return customDataAttribute; - } - - /// - /// Parses a BundleElement element. - /// - /// Element to parse. - /// Element's SourceLineNumbers. - /// BundleCustomData Id. - private void ParseBundleElementElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) - { - var elementId = Guid.NewGuid().ToString("N").ToUpperInvariant(); - - foreach (var attrib in node.Attributes()) - { - this.Core.ParseExtensionAttribute(node, attrib); - } - - foreach (var child in node.Elements()) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "BundleAttribute": - string attributeName = null; - string value = null; - foreach (var attrib in child.Attributes()) - { - switch (attrib.Name.LocalName) - { - case "Id": - attributeName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - default: - this.Core.ParseExtensionAttribute(child, attrib); - break; - } - } - - if (null == attributeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleCustomDataCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, elementId, attributeName)) - { - ElementId = elementId, - AttributeRef = attributeName, - CustomDataRef = customDataId, - Value = value, - }); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - - if (!this.Core.EncounteredError) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); - } - } - - /// - /// Parse the BundleExtension element. - /// - /// Element to parse - private void ParseBundleExtensionElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); - Identifier previousId = null; - var previousType = ComplexReferenceChildType.Unknown; - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - compilerPayload.ParseId(attrib); - break; - case "Name": - compilerPayload.ParseName(attrib); - break; - case "SourceFile": - compilerPayload.ParseSourceFile(attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - extensionAttributes.Add(attrib); - } - } - - compilerPayload.FinishCompilingPayload(); - - // Now that the Id is known, we can parse the extension attributes. - var context = new Dictionary - { - ["Id"] = compilerPayload.Id.Id, - }; - - foreach (var extensionAttribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, extensionAttribute, context); - } - - compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); - previousId = compilerPayload.Id; - previousType = ComplexReferenceChildType.Payload; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Payload": - previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "PayloadGroupRef": - previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); - previousType = ComplexReferenceChildType.PayloadGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - // Add the BundleExtension. - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleExtensionSymbol(sourceLineNumbers, compilerPayload.Id) - { - PayloadRef = compilerPayload.Id.Id, - }); - } - } - - /// - /// Parse the OptionalUpdateRegistration element. - /// - /// The element to parse. - /// The manufacturer. - /// The product family. - /// The bundle name. - private void ParseOptionalUpdateRegistrationElement(XElement node, string defaultManufacturer, string defaultProductFamily, string defaultName) - { - const string defaultClassification = "Update"; - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string manufacturer = null; - string department = null; - string productFamily = null; - string name = null; - var classification = defaultClassification; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Manufacturer": - manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Department": - department = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ProductFamily": - productFamily = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Classification": - classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(manufacturer)) - { - if (!String.IsNullOrEmpty(defaultManufacturer)) - { - manufacturer = defaultManufacturer; - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Manufacturer", node.Parent.Name.LocalName)); - } - } - - if (String.IsNullOrEmpty(productFamily)) - { - if (!String.IsNullOrEmpty(defaultProductFamily)) - { - productFamily = defaultProductFamily; - } - } - - if (String.IsNullOrEmpty(name)) - { - if (!String.IsNullOrEmpty(defaultName)) - { - name = defaultName; - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Name", node.Parent.Name.LocalName)); - } - } - - if (String.IsNullOrEmpty(classification)) - { - this.Core.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, node.Name.LocalName, "Classification", defaultClassification)); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixUpdateRegistrationSymbol(sourceLineNumbers) - { - Manufacturer = manufacturer, - Department = department, - ProductFamily = productFamily, - Name = name, - Classification = classification - }); - } - } - - /// - /// Parse Payload element. - /// - /// Element to parse - /// ComplexReferenceParentType of parent element. (BA or PayloadGroup) - /// Identifier of parent element. - /// - /// - private Identifier ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) - { - Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); - Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - var allowed = true; - switch (attrib.Name.LocalName) - { - case "Id": - compilerPayload.ParseId(attrib); - break; - case "Compressed": - compilerPayload.ParseCompressed(attrib); - break; - case "Name": - compilerPayload.ParseName(attrib); - break; - case "SourceFile": - compilerPayload.ParseSourceFile(attrib); - break; - case "DownloadUrl": - compilerPayload.ParseDownloadUrl(attrib); - break; - default: - allowed = false; - break; - } - - if (!allowed) - { - this.Core.UnexpectedAttribute(node, attrib); - } - } - else - { - extensionAttributes.Add(attrib); - } - } - - compilerPayload.FinishCompilingPayload(); - - // Now that the PayloadId is known, we can parse the extension attributes. - var context = new Dictionary - { - ["Id"] = compilerPayload.Id.Id, - }; - - foreach (var extensionAttribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, extensionAttribute, context); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child, context); - } - } - - compilerPayload.CreatePayloadSymbol(parentType, parentId?.Id, previousType, previousId?.Id); - - return compilerPayload.Id; - } - - /// - /// Parse PayloadGroup element. - /// - /// Element to parse - /// Optional ComplexReferenceParentType of parent element. (typically another PayloadGroup) - /// Identifier of parent element. - private void ParsePayloadGroupElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId) - { - Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - var previousType = ComplexReferenceChildType.Unknown; - Identifier previousId = null; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - WixBundlePackageType? packageType = null; - switch (child.Name.LocalName) - { - case "ExePackagePayload": - packageType = WixBundlePackageType.Exe; - break; - case "MsiPackagePayload": - packageType = WixBundlePackageType.Msi; - break; - case "MspPackagePayload": - packageType = WixBundlePackageType.Msp; - break; - case "MsuPackagePayload": - packageType = WixBundlePackageType.Msu; - break; - case "Payload": - previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); - previousType = ComplexReferenceChildType.Payload; - break; - case "PayloadGroupRef": - previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); - previousType = ComplexReferenceChildType.PayloadGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - - if (packageType.HasValue) - { - var compilerPayload = this.ParsePackagePayloadElement(null, child, packageType.Value, null); - var payloadSymbol = compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.PayloadGroup, id?.Id, previousType, previousId?.Id); - if (payloadSymbol != null) - { - previousId = payloadSymbol.Id; - previousType = ComplexReferenceChildType.Payload; - - this.CreatePackagePayloadSymbol(payloadSymbol.SourceLineNumbers, packageType.Value, payloadSymbol.Id, ComplexReferenceParentType.PayloadGroup, id); - } - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundlePayloadGroupSymbol(sourceLineNumbers, id)); - - this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null); - } - } - - /// - /// Parses a payload group reference element. - /// - /// Element to parse. - /// ComplexReferenceParentType of parent element (BA or PayloadGroup). - /// Identifier of parent element. - /// - /// - private Identifier ParsePayloadGroupRefElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) - { - Debug.Assert(ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); - Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePayloadGroup, id.Id); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id?.Id, previousType, previousId?.Id); - - return id; - } - - /// - /// Parse ExitCode element. - /// - /// Element to parse - /// Id of parent element - private void ParseExitCodeElement(XElement node, string packageId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var value = CompilerConstants.IntegerNotSet; - var behavior = ExitCodeBehaviorType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Value": - value = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); - break; - case "Behavior": - var behaviorString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (behaviorString) - { - case "error": - behavior = ExitCodeBehaviorType.Error; - break; - case "forceReboot": - behavior = ExitCodeBehaviorType.ForceReboot; - break; - case "scheduleReboot": - behavior = ExitCodeBehaviorType.ScheduleReboot; - break; - case "success": - behavior = ExitCodeBehaviorType.Success; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Behavior", behaviorString, "success, error, scheduleReboot, forceReboot")); - behavior = ExitCodeBehaviorType.Success; // set value to avoid ExpectedAttribute below. - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (ExitCodeBehaviorType.NotSet == behavior) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Behavior")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundlePackageExitCodeSymbol(sourceLineNumbers) - { - ChainPackageId = packageId, - Code = value, - Behavior = behavior - }); - } - } - - /// - /// Parse Chain element. - /// - /// Element to parse - private void ParseChainElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var attributes = WixChainAttributes.None; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "DisableRollback": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixChainAttributes.DisableRollback; - } - break; - case "DisableSystemRestore": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixChainAttributes.DisableSystemRestore; - } - break; - case "ParallelCache": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixChainAttributes.ParallelCache; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - string previousId = null; - var previousType = ComplexReferenceChildType.Unknown; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "MsiPackage": - previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "MspPackage": - previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "MsuPackage": - previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "ExePackage": - previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "RollbackBoundary": - previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "PackageGroupRef": - previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); - previousType = ComplexReferenceChildType.PackageGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (null == previousId) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "MsiPackage", "ExePackage", "PackageGroupRef")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixChainSymbol(sourceLineNumbers) - { - Attributes = attributes - }); - } - } - - /// - /// Parse MsiPackage element - /// - /// Element to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - private string ParseMsiPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - return this.ParseChainPackage(node, WixBundlePackageType.Msi, parentType, parentId, previousType, previousId); - } - - /// - /// Parse MspPackage element - /// - /// Element to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - private string ParseMspPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - return this.ParseChainPackage(node, WixBundlePackageType.Msp, parentType, parentId, previousType, previousId); - } - - /// - /// Parse MsuPackage element - /// - /// Element to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - private string ParseMsuPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - return this.ParseChainPackage(node, WixBundlePackageType.Msu, parentType, parentId, previousType, previousId); - } - - /// - /// Parse ExePackage element - /// - /// Element to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - private string ParseExePackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - return this.ParseChainPackage(node, WixBundlePackageType.Exe, parentType, parentId, previousType, previousId); - } - - /// - /// Parse RollbackBoundary element - /// - /// Element to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - private string ParseRollbackBoundaryElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); - Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var vital = YesNoType.Yes; - var transaction = YesNoType.No; - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - var allowed = true; - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - if (id?.Id == BurnConstants.BundleDefaultBoundaryId) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - break; - case "Vital": - vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Transaction": - transaction = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - allowed = false; - break; - } - - if (!allowed) - { - this.Core.UnexpectedAttribute(node, attrib); - } - } - else - { - // Save the extension attributes for later... - extensionAttributes.Add(attrib); - } - } - - if (null == id) - { - if (!String.IsNullOrEmpty(previousId)) - { - id = this.Core.CreateIdentifier("rba", previousId); - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (!Common.IsIdentifier(id.Id)) - { - this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - } - - // Now that the rollback identifier is known, we can parse the extension attributes... - var contextValues = new Dictionary - { - ["RollbackBoundaryId"] = id.Id - }; - foreach (var attribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, attribute, contextValues); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.CreateRollbackBoundary(sourceLineNumbers, id, vital, transaction, parentType, parentId, previousType, previousId); - } - - return id.Id; - } - - /// - /// Parses one of the ChainPackage elements - /// - /// Element to parse - /// Type of package to parse - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier for package element. - /// This method contains the shared logic for parsing all of the ChainPackage - /// types, as there is more in common between them than different. - private string ParseChainPackage(XElement node, WixBundlePackageType packageType, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); - Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) - { - IsRequired = false, - }; - string after = null; - string installCondition = null; - var cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space. - string cacheId = null; - string description = null; - string displayName = null; - var logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; - var rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; - var permanent = YesNoType.NotSet; - var visible = YesNoType.NotSet; - var vital = YesNoType.Yes; - string installArguments = null; - string repairArguments = null; - string uninstallArguments = null; - var perMachine = YesNoDefaultType.NotSet; - string detectCondition = null; - string protocol = null; - var installSize = CompilerConstants.IntegerNotSet; - string msuKB = null; - var enableFeatureSelection = YesNoType.NotSet; - var forcePerMachine = YesNoType.NotSet; - CompilerPayload childPackageCompilerPayload = null; - var slipstream = YesNoType.NotSet; - var hasPayloadInfo = false; - - var expectedNetFx4Args = new string[] { "/q", "/norestart" }; - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - var allowed = true; - switch (attrib.Name.LocalName) - { - case "Id": - compilerPayload.ParseId(attrib); - break; - case "Name": - compilerPayload.ParseName(attrib); - hasPayloadInfo = true; - break; - case "SourceFile": - compilerPayload.ParseSourceFile(attrib); - hasPayloadInfo = true; - break; - case "DownloadUrl": - compilerPayload.ParseDownloadUrl(attrib); - hasPayloadInfo = true; - break; - case "After": - after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "InstallCondition": - installCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Cache": - var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (value) - { - case "always": - cache = YesNoAlwaysType.Always; - break; - case "yes": - cache = YesNoAlwaysType.Yes; - break; - case "no": - cache = YesNoAlwaysType.No; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "always", "yes", "no")); - break; - } - break; - case "CacheId": - cacheId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "EnableFeatureSelection": - enableFeatureSelection = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msi); - break; - case "ForcePerMachine": - forcePerMachine = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msi); - break; - case "LogPathVariable": - logPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "RollbackLogPathVariable": - rollbackPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Permanent": - permanent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Visible": - visible = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msi); - break; - case "Vital": - vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "InstallArguments": - installArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Exe); - break; - case "RepairArguments": - repairArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - allowed = (packageType == WixBundlePackageType.Exe); - break; - case "UninstallArguments": - uninstallArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Exe); - break; - case "PerMachine": - perMachine = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msp); - break; - case "DetectCondition": - detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu); - break; - case "Protocol": - protocol = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Exe); - break; - case "InstallSize": - installSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "KB": - msuKB = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msu); - break; - case "Compressed": - compilerPayload.ParseCompressed(attrib); - hasPayloadInfo = true; - break; - case "Slipstream": - slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - allowed = (packageType == WixBundlePackageType.Msp); - break; - default: - allowed = false; - break; - } - - if (!allowed) - { - this.Core.UnexpectedAttribute(node, attrib); - } - } - else - { - // Save the extension attributes for later... - extensionAttributes.Add(attrib); - } - } - - // We need to handle the package payload up front because it affects Id generation. Id is needed by other child elements. - var packagePayloadElementName = packageType + "PackagePayload"; - foreach (var child in node.Elements(CompilerCore.WixNamespace + packagePayloadElementName)) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - - if (childPackageCompilerPayload != null) - { - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); - } - else if (hasPayloadInfo) - { - this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "SourceFile", "Name", "DownloadUrl", "Compressed")); - } - - childPackageCompilerPayload = this.ParsePackagePayloadElement(childSourceLineNumbers, child, packageType, compilerPayload.Id); - } - - if (compilerPayload.Id == null && childPackageCompilerPayload != null) - { - compilerPayload.Id = childPackageCompilerPayload.Id; - } - - compilerPayload.FinishCompilingPackage(); - var id = compilerPayload.Id; - - if (id.Id == BurnConstants.BundleDefaultBoundaryId) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - - if (null == logPathVariable) - { - logPathVariable = String.Concat("WixBundleLog_", id.Id); - } - - if (null == rollbackPathVariable) - { - rollbackPathVariable = String.Concat("WixBundleRollbackLog_", id.Id); - } - - if (!String.IsNullOrEmpty(protocol) && !protocol.Equals("burn", StringComparison.Ordinal) && !protocol.Equals("netfx4", StringComparison.Ordinal) && !protocol.Equals("none", StringComparison.Ordinal)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Protocol", protocol, "none, burn, netfx4")); - } - - if (!String.IsNullOrEmpty(protocol) && protocol.Equals("netfx4", StringComparison.Ordinal)) - { - foreach (var expectedArgument in expectedNetFx4Args) - { - if (null == installArguments || -1 == installArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallArguments", installArguments, expectedArgument, "Protocol", "netfx4")); - } - - if (!String.IsNullOrEmpty(repairArguments) && -1 == repairArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairArguments", repairArguments, expectedArgument, "Protocol", "netfx4")); - } - - if (!String.IsNullOrEmpty(uninstallArguments) && -1 == uninstallArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallArguments", uninstallArguments, expectedArgument, "Protocol", "netfx4")); - } - } - } - - // Only set default scope for EXEs and MSPs if not already set. - if ((WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msp == packageType) && YesNoDefaultType.NotSet == perMachine) - { - perMachine = YesNoDefaultType.Default; - } - - // Detect condition is recommended or required for Exe and Msu packages - // (depending on whether uninstall arguments were provided). - if ((packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu) && String.IsNullOrEmpty(detectCondition)) - { - if (String.IsNullOrEmpty(uninstallArguments)) - { - this.Core.Write(WarningMessages.DetectConditionRecommended(sourceLineNumbers, node.Name.LocalName)); - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributeWithValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DetectCondition", "UninstallArguments")); - } - } - - // Now that the package ID is known, we can parse the extension attributes... - var contextValues = new Dictionary() { { "PackageId", id.Id } }; - foreach (var attribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, attribute, contextValues); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var allowed = true; - switch (child.Name.LocalName) - { - case "SlipstreamMsp": - allowed = (packageType == WixBundlePackageType.Msi); - if (allowed) - { - this.ParseSlipstreamMspElement(child, id.Id); - } - break; - case "MsiProperty": - allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp); - if (allowed) - { - this.ParseMsiPropertyElement(child, id.Id); - } - break; - case "Payload": - this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); - break; - case "PayloadGroupRef": - this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); - break; - case "Provides": - this.ParseProvidesElement(child, packageType, id.Id, out _); - break; - case "ExitCode": - allowed = (packageType == WixBundlePackageType.Exe); - if (allowed) - { - this.ParseExitCodeElement(child, id.Id); - } - break; - case "CommandLine": - allowed = (packageType == WixBundlePackageType.Exe); - if (allowed) - { - this.ParseCommandLineElement(child, id.Id); - } - break; - case "ExePackagePayload": - case "MsiPackagePayload": - case "MspPackagePayload": - case "MsuPackagePayload": - allowed = packagePayloadElementName == child.Name.LocalName; - // Handled previously - break; - default: - allowed = false; - break; - } - - if (!allowed) - { - this.Core.UnexpectedElement(node, child); - } - } - else - { - var context = new Dictionary() { { "Id", id.Id } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (!this.Core.EncounteredError) - { - var packageCompilerPayload = childPackageCompilerPayload ?? (hasPayloadInfo ? compilerPayload : null); - if (packageCompilerPayload != null) - { - var payload = packageCompilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id); - - this.CreatePackagePayloadSymbol(sourceLineNumbers, packageType, payload.Id, ComplexReferenceParentType.Package, id); - } - - this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); - - WixBundlePackageAttributes attributes = 0; - attributes |= (YesNoType.Yes == permanent) ? WixBundlePackageAttributes.Permanent : 0; - attributes |= (YesNoType.Yes == visible) ? WixBundlePackageAttributes.Visible : 0; - - var chainPackageSymbol = this.Core.AddSymbol(new WixBundlePackageSymbol(sourceLineNumbers, id) - { - Type = packageType, - Attributes = attributes, - InstallCondition = installCondition, - CacheId = cacheId, - Description = description, - DisplayName = displayName, - LogPathVariable = logPathVariable, - RollbackLogPathVariable = rollbackPathVariable, - }); - - if (YesNoAlwaysType.NotSet != cache) - { - chainPackageSymbol.Cache = cache; - } - - if (YesNoType.NotSet != vital) - { - chainPackageSymbol.Vital = (vital == YesNoType.Yes); - } - - if (YesNoDefaultType.NotSet != perMachine) - { - chainPackageSymbol.PerMachine = perMachine; - } - - if (CompilerConstants.IntegerNotSet != installSize) - { - chainPackageSymbol.InstallSize = installSize; - } - - switch (packageType) - { - case WixBundlePackageType.Exe: - this.Core.AddSymbol(new WixBundleExePackageSymbol(sourceLineNumbers, id) - { - Attributes = WixBundleExePackageAttributes.None, - DetectCondition = detectCondition, - InstallCommand = installArguments, - RepairCommand = repairArguments, - UninstallCommand = uninstallArguments, - ExeProtocol = protocol - }); - break; - - case WixBundlePackageType.Msi: - WixBundleMsiPackageAttributes msiAttributes = 0; - msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0; - msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0; - - this.Core.AddSymbol(new WixBundleMsiPackageSymbol(sourceLineNumbers, id) - { - Attributes = msiAttributes - }); - break; - - case WixBundlePackageType.Msp: - WixBundleMspPackageAttributes mspAttributes = 0; - mspAttributes |= (YesNoType.Yes == slipstream) ? WixBundleMspPackageAttributes.Slipstream : 0; - - this.Core.AddSymbol(new WixBundleMspPackageSymbol(sourceLineNumbers, id) - { - Attributes = mspAttributes - }); - break; - - case WixBundlePackageType.Msu: - this.Core.AddSymbol(new WixBundleMsuPackageSymbol(sourceLineNumbers, id) - { - DetectCondition = detectCondition, - MsuKB = msuKB - }); - break; - } - - this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, after); - this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ContainerPackage, id.Id, ComplexReferenceChildType.Unknown, null); - } - - return id.Id; - } - - private void CreatePackagePayloadSymbol(SourceLineNumber sourceLineNumbers, WixBundlePackageType packageType, Identifier payloadId, ComplexReferenceParentType parentType, Identifier parentId) - { - switch (packageType) - { - case WixBundlePackageType.Exe: - this.Core.AddSymbol(new WixBundleExePackagePayloadSymbol(sourceLineNumbers, payloadId)); - break; - - case WixBundlePackageType.Msi: - this.Core.AddSymbol(new WixBundleMsiPackagePayloadSymbol(sourceLineNumbers, payloadId)); - break; - - case WixBundlePackageType.Msp: - this.Core.AddSymbol(new WixBundleMspPackagePayloadSymbol(sourceLineNumbers, payloadId)); - break; - - case WixBundlePackageType.Msu: - this.Core.AddSymbol(new WixBundleMsuPackagePayloadSymbol(sourceLineNumbers, payloadId)); - break; - } - - this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PackagePayload, payloadId?.Id, ComplexReferenceChildType.Unknown, null); - } - - private CompilerPayload ParsePackagePayloadElement(SourceLineNumber sourceLineNumbers, XElement node, WixBundlePackageType packageType, Identifier defaultId) - { - sourceLineNumbers = sourceLineNumbers ?? Preprocessor.GetSourceLineNumbers(node); - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) - { - Id = defaultId, - IsRemoteAllowed = packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu, - }; - - // This list lets us evaluate extension attributes *after* all core attributes - // have been parsed and dealt with, regardless of authoring order. - var extensionAttributes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - var allowed = true; - switch (attrib.Name.LocalName) - { - case "Id": - compilerPayload.ParseId(attrib); - break; - case "Compressed": - compilerPayload.ParseCompressed(attrib); - break; - case "Name": - compilerPayload.ParseName(attrib); - break; - case "SourceFile": - compilerPayload.ParseSourceFile(attrib); - break; - case "DownloadUrl": - compilerPayload.ParseDownloadUrl(attrib); - break; - case "Description": - allowed = compilerPayload.IsRemoteAllowed; - if (allowed) - { - compilerPayload.ParseDescription(attrib); - } - break; - case "Hash": - allowed = compilerPayload.IsRemoteAllowed; - if (allowed) - { - compilerPayload.ParseHash(attrib); - } - break; - case "ProductName": - allowed = compilerPayload.IsRemoteAllowed; - if (allowed) - { - compilerPayload.ParseProductName(attrib); - } - break; - case "Size": - allowed = compilerPayload.IsRemoteAllowed; - if (allowed) - { - compilerPayload.ParseSize(attrib); - } - break; - case "Version": - allowed = compilerPayload.IsRemoteAllowed; - if (allowed) - { - compilerPayload.ParseVersion(attrib); - } - break; - default: - allowed = false; - break; - } - - if (!allowed) - { - this.Core.UnexpectedAttribute(node, attrib); - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - compilerPayload.FinishCompilingPackagePayload(); - - // Now that the PayloadId is known, we can parse the extension attributes. - var context = new Dictionary - { - ["Id"] = compilerPayload.Id.Id, - }; - - foreach (var extensionAttribute in extensionAttributes) - { - this.Core.ParseExtensionAttribute(node, extensionAttribute, context); - } - - this.Core.ParseForExtensionElements(node); - - return compilerPayload; - } - - /// - /// Parse CommandLine element. - /// - /// Element to parse - /// Parent packageId - private void ParseCommandLineElement(XElement node, string packageId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string installArgument = null; - string uninstallArgument = null; - string repairArgument = null; - string condition = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "InstallArgument": - installArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "UninstallArgument": - uninstallArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "RepairArgument": - repairArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(condition)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundlePackageCommandLineSymbol(sourceLineNumbers) - { - WixBundlePackageRef = packageId, - InstallArgument = installArgument, - UninstallArgument = uninstallArgument, - RepairArgument = repairArgument, - Condition = condition - }); - } - } - - /// - /// Parse PackageGroup element. - /// - /// Element to parse - private void ParsePackageGroupElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - if (id?.Id == BurnConstants.BundleChainPackageGroupId) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - var previousType = ComplexReferenceChildType.Unknown; - string previousId = null; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "MsiPackage": - previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "MspPackage": - previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "MsuPackage": - previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "ExePackage": - previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "RollbackBoundary": - previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.Package; - break; - case "PackageGroupRef": - previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); - previousType = ComplexReferenceChildType.PackageGroup; - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundlePackageGroupSymbol(sourceLineNumbers, id)); - } - } - - /// - /// Parses a package group reference element. - /// - /// Element to parse. - /// ComplexReferenceParentType of parent element (Unknown or PackageGroup). - /// Identifier of parent element. - /// Identifier for package group element. - private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - return this.ParsePackageGroupRefElement(node, parentType, parentId, ComplexReferenceChildType.Unknown, null); - } - - /// - /// Parses a package group reference element. - /// - /// Element to parse. - /// ComplexReferenceParentType of parent element (Unknown or PackageGroup). - /// Identifier of parent element. - /// - /// - /// Identifier for package group element. - private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.PackageGroup == parentType || ComplexReferenceParentType.Container == parentType); - Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string after = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - if (id == BurnConstants.BundleChainPackageGroupId) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id)); - } - else - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackageGroup, id); - } - break; - case "After": - after = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null != after && ComplexReferenceParentType.Container == parentType) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "After", parentId)); - } - - this.Core.ParseForExtensionElements(node); - - if (ComplexReferenceParentType.Container == parentType) - { - this.Core.CreateWixGroupRow(sourceLineNumbers, ComplexReferenceParentType.Container, parentId, ComplexReferenceChildType.PackageGroup, id); - } - else - { - this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PackageGroup, id, previousType, previousId, after); - } - - return id; - } - - /// - /// Creates rollback boundary. - /// - /// Source line numbers. - /// Identifier for the rollback boundary. - /// Indicates whether the rollback boundary is vital or not. - /// Indicates whether the rollback boundary will use an MSI transaction. - /// Type of parent group. - /// Identifier of parent group. - /// Type of previous item, if any. - /// Identifier of previous item, if any. - private void CreateRollbackBoundary(SourceLineNumber sourceLineNumbers, Identifier id, YesNoType vital, YesNoType transaction, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) - { - this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); - - var rollbackBoundary = this.Core.AddSymbol(new WixBundleRollbackBoundarySymbol(sourceLineNumbers, id)); - - if (YesNoType.NotSet != vital) - { - rollbackBoundary.Vital = (vital == YesNoType.Yes); - } - - if (YesNoType.NotSet != transaction) - { - rollbackBoundary.Transaction = (transaction == YesNoType.Yes); - } - - this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, null); - } - - /// - /// Creates group and ordering information for packages - /// - /// Source line numbers. - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of this item. - /// Identifier for this item. - /// Type of previous item, if known. - /// Identifier of previous item, if known - /// Identifier of explicit 'After' attribute, if given. - private void CreateChainPackageMetaRows(SourceLineNumber sourceLineNumbers, - ComplexReferenceParentType parentType, string parentId, - ComplexReferenceChildType type, string id, - ComplexReferenceChildType previousType, string previousId, string afterId) - { - // If there's an explicit 'After' attribute, it overrides the inferred previous item. - if (null != afterId) - { - previousType = ComplexReferenceChildType.Package; - previousId = afterId; - } - - this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId); - } - - /// - /// Parse MsiProperty element - /// - /// Element to parse - /// Id of parent element - private void ParseMsiPropertyElement(XElement node, string packageId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string name = null; - string value = null; - string condition = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - name = this.Core.GetAttributeMsiPropertyNameValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new WixBundleMsiPropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, name)) - { - PackageRef = packageId, - Name = name, - Value = value - }); - - if (!String.IsNullOrEmpty(condition)) - { - symbol.Condition = condition; - } - } - } - - /// - /// Parse SlipstreamMsp element - /// - /// Element to parse - /// Id of parent element - private void ParseSlipstreamMspElement(XElement node, string packageId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackage, id); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleSlipstreamMspSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, id)) - { - TargetPackageRef = packageId, - MspPackageRef = id - }); - } - } - - /// - /// Parse RelatedBundle element - /// - /// Element to parse - private void ParseRelatedBundleElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var actionType = RelatedBundleActionType.Detect; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Action": - var action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (action) - { - case "Detect": - case "detect": - actionType = RelatedBundleActionType.Detect; - break; - case "Upgrade": - case "upgrade": - actionType = RelatedBundleActionType.Upgrade; - break; - case "Addon": - case "addon": - actionType = RelatedBundleActionType.Addon; - break; - case "Patch": - case "patch": - actionType = RelatedBundleActionType.Patch; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", action, "Detect", "Upgrade", "Addon", "Patch")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) - { - BundleId = id, - Action = actionType, - }); - } - } - - /// - /// Parse Update element - /// - /// Element to parse - private void ParseUpdateElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string location = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Location": - location = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == location) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Location")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleUpdateSymbol(sourceLineNumbers) - { - Location = location - }); - } - } - - /// - /// Parse SetVariable element - /// - /// Element to parse - private void ParseSetVariableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string variable = null; - string condition = null; - string after = null; - string value = null; - string typeValue = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Variable": - variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "After": - after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Type": - typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib, null); - } - } - - var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); - - this.Core.ParseForExtensionElements(node); - - if (id == null) - { - id = this.Core.CreateIdentifier("sbv", variable, condition, after, value, type.ToString()); - } - - this.Core.CreateWixSearchSymbol(sourceLineNumbers, node.Name.LocalName, id, variable, condition, after); - - if (!this.Messaging.EncounteredError) - { - this.Core.AddSymbol(new WixSetVariableSymbol(sourceLineNumbers, id) - { - Value = value, - Type = type, - }); - } - } - - /// - /// Parse Variable element - /// - /// Element to parse - private void ParseVariableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var hidden = false; - string name = null; - var persisted = false; - string value = null; - string typeValue = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Hidden": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - hidden = true; - } - break; - case "Name": - name = this.Core.GetAttributeBundleVariableValue(sourceLineNumbers, attrib); - break; - case "Persisted": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - persisted = true; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Type": - typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else if (name.StartsWith("Wix", StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(ErrorMessages.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix")); - } - - if (hidden && persisted) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "yes", "Persisted")); - } - - var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) - { - Value = value, - Type = type, - Hidden = hidden, - Persisted = persisted - }); - } - } - - private WixBundleVariableType ValidateVariableTypeWithValue(SourceLineNumber sourceLineNumbers, XElement node, string typeValue, string value) - { - WixBundleVariableType type; - switch (typeValue) - { - case "formatted": - type = WixBundleVariableType.Formatted; - break; - case "numeric": - type = WixBundleVariableType.Numeric; - break; - case "string": - type = WixBundleVariableType.String; - break; - case "version": - type = WixBundleVariableType.Version; - break; - case null: - type = WixBundleVariableType.Unknown; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "formatted", "numeric", "string", "version")); - return WixBundleVariableType.Unknown; - } - - if (type != WixBundleVariableType.Unknown) - { - if (value == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type")); - } - - return type; - } - else if (value == null) - { - return type; - } - - // Infer the type from the current value... - if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) - { - // Version constructor does not support simple "v#" syntax so check to see if the value is - // non-negative real quick. - if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _)) - { - return WixBundleVariableType.Version; - } - else if (Version.TryParse(value.Substring(1), out var _)) - { - return WixBundleVariableType.Version; - } - } - - // Not a version, check for numeric. - if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _)) - { - return WixBundleVariableType.Numeric; - } - - return WixBundleVariableType.String; - } - } -} diff --git a/src/WixToolset.Core/Compiler_Dependency.cs b/src/WixToolset.Core/Compiler_Dependency.cs deleted file mode 100644 index 7c863883..00000000 --- a/src/WixToolset.Core/Compiler_Dependency.cs +++ /dev/null @@ -1,384 +0,0 @@ -// 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 -{ - using System; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - // The root registry key for the dependency extension. We write to Software\Classes explicitly - // based on the current security context instead of HKCR. See - // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. - private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; - - private static readonly char[] InvalidDependencyCharacters = new char[] { ' ', '\"', ';', '\\' }; - - /// - /// Processes the ProviderKey bundle attribute. - /// - /// Source line number for the parent element. - /// Parent element of attribute. - /// The XML attribute for the ProviderKey attribute. - private void ParseBundleProviderKeyAttribute(SourceLineNumber sourceLineNumbers, XElement parentElement, XAttribute attribute) - { - var providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attribute); - int illegalChar; - - // Make sure the key does not contain any illegal characters or values. - if (String.IsNullOrEmpty(providerKey)) - { - this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, attribute.Name.LocalName)); - } - else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) - { - this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); - } - else if ("ALL" == providerKey) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, parentElement.Name.LocalName, "ProviderKey", providerKey)); - } - - if (!this.Messaging.EncounteredError) - { - // Generate the primary key for the row. - var id = this.Core.CreateIdentifier("dep", attribute.Name.LocalName, providerKey); - - // Create the provider symbol for the bundle. The Component_ field is required - // in the table definition but unused for bundles, so just set it to the valid ID. - this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) - { - ParentRef = id.Id, - ProviderKey = providerKey, - Attributes = WixDependencyProviderAttributes.ProvidesAttributesBundle, - }); - } - } - - /// - /// Processes the Provides element. - /// - /// The XML node for the Provides element. - /// The type of the package being chained into a bundle, or null if building an MSI package. - /// The identifier of the parent component or package. - /// Possible KeyPath identifier. - /// Yes if this is the keypath. - private YesNoType ParseProvidesElement(XElement node, WixBundlePackageType? packageType, string parentId, out string possibleKeyPath) - { - possibleKeyPath = null; - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string version = null; - string displayName = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Version": - version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // Make sure the key is valid. The key will default to the ProductCode for MSI packages - // and the package code for MSP packages in the binder if not specified. - if (!String.IsNullOrEmpty(key)) - { - int illegalChar; - - // Make sure the key does not contain any illegal characters or values. - if (0 <= (illegalChar = key.IndexOfAny(InvalidDependencyCharacters))) - { - this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "Key", key[illegalChar], String.Join(" ", InvalidDependencyCharacters))); - } - else if ("ALL" == key) - { - this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Key", key)); - } - } - else if (!packageType.HasValue) - { - // Make sure the ProductCode is authored and set the key. - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "ProductCode"); - key = "!(bind.property.ProductCode)"; - } - else if (WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msu == packageType) - { - // Must specify the provider key when authored for a package. - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - // The Version attribute should not be authored in or for an MSI package. - if (!String.IsNullOrEmpty(version)) - { - switch (packageType) - { - case null: - this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers)); - break; - case WixBundlePackageType.Msi: - this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers, parentId)); - break; - } - } - else if (WixBundlePackageType.Msp == packageType || WixBundlePackageType.Msu == packageType) - { - // Must specify the Version when authored for packages that do not contain a version. - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - - // Need the element ID for child element processing, so generate now if not authored. - if (null == id) - { - id = this.Core.CreateIdentifier("dep", node.Name.LocalName, parentId, key); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Requires": - this.ParseRequiresElement(child, id.Id); - break; - case "RequiresRef": - this.ParseRequiresRefElement(child, id.Id, requiresAction: !packageType.HasValue); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Messaging.EncounteredError) - { - var symbol = this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) - { - ParentRef = parentId, - ProviderKey = key, - }); - - if (!String.IsNullOrEmpty(version)) - { - symbol.Version = version; - } - - if (!String.IsNullOrEmpty(displayName)) - { - symbol.DisplayName = displayName; - } - - if (!packageType.HasValue) - { - // Generate registry rows for the provider using binder properties. - var keyProvides = String.Concat(DependencyRegistryRoot, key); - var root = RegistryRootType.MachineUser; - - var value = "[ProductCode]"; - this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, null, value, parentId); - - value = !String.IsNullOrEmpty(version) ? version : "[ProductVersion]"; - var versionRegistrySymbol = this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "Version", value, parentId); - - value = !String.IsNullOrEmpty(displayName) ? displayName : "[ProductName]"; - this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "DisplayName", value, parentId); - - // Use the Version registry value and use that as a potential key path. - possibleKeyPath = versionRegistrySymbol.Id; - } - } - - return YesNoType.NotSet; - } - - /// - /// Processes the Requires element. - /// - /// The XML node for the Requires element. - /// The parent provider identifier. - private void ParseRequiresElement(XElement node, string providerId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string providerKey = null; - string minVersion = null; - string maxVersion = null; - var attributes = WixDependencySymbolAttributes.None; - var illegalChar = -1; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ProviderKey": - providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Minimum": - minVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "Maximum": - maxVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "IncludeMinimum": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixDependencySymbolAttributes.RequiresAttributesMinVersionInclusive; - } - break; - case "IncludeMaximum": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= WixDependencySymbolAttributes.RequiresAttributesMaxVersionInclusive; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (null == id) - { - // Generate an ID only if this element is authored under a Provides element; otherwise, a RequiresRef - // element will be necessary and the Id attribute will be required. - if (!String.IsNullOrEmpty(providerId)) - { - id = this.Core.CreateIdentifier("dep", node.Name.LocalName, providerKey); - } - else - { - this.Messaging.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Id", "Provides")); - id = Identifier.Invalid; - } - } - - if (String.IsNullOrEmpty(providerKey)) - { - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProviderKey")); - } - // Make sure the key does not contain any illegal characters. - else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) - { - this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); - } - - if (!this.Messaging.EncounteredError) - { - this.Core.AddSymbol(new WixDependencySymbol(sourceLineNumbers, id) - { - ProviderKey = providerKey, - MinVersion = minVersion, - MaxVersion = maxVersion, - Attributes = attributes - }); - - // Create the relationship between this WixDependency symbol and the WixDependencyProvider symbol. - if (!String.IsNullOrEmpty(providerId)) - { - this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) - { - WixDependencyProviderRef = providerId, - WixDependencyRef = id.Id, - }); - } - } - } - - /// - /// Processes the RequiresRef element. - /// - /// The XML node for the RequiresRef element. - /// The parent provider identifier. - /// Whether the Requires custom action should be referenced. - private void ParseRequiresRefElement(XElement node, string providerId, bool requiresAction) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (String.IsNullOrEmpty(id)) - { - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (!this.Messaging.EncounteredError) - { - // Create a link dependency on the row that contains information we'll need during bind. - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixDependency, id); - - // Create the relationship between the WixDependency row and the parent WixDependencyProvider row. - this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) - { - WixDependencyProviderRef = providerId, - WixDependencyRef = id, - }); - } - } - } -} diff --git a/src/WixToolset.Core/Compiler_EmbeddedUI.cs b/src/WixToolset.Core/Compiler_EmbeddedUI.cs deleted file mode 100644 index ede03933..00000000 --- a/src/WixToolset.Core/Compiler_EmbeddedUI.cs +++ /dev/null @@ -1,417 +0,0 @@ -// 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 -{ - using System; - using System.IO; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses an EmbeddedChaniner element. - /// - /// Element to parse. - private void ParseEmbeddedChainerElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string commandLine = null; - string condition = null; - string source = null; - var type = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "BinarySource": - if (null != source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "FileSource", "PropertySource")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - type = 0x2; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary - break; - case "CommandLine": - commandLine = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "FileSource": - if (null != source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "PropertySource")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - type = 0x12; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File - break; - case "PropertySource": - if (null != source) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "FileSource")); - } - source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - type = 0x32; - // cannot add a reference to a Property because it may be created at runtime. - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("mec", source, type.ToString()); - } - - if (null == source) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "BinarySource", "FileSource", "PropertySource")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiEmbeddedChainerSymbol(sourceLineNumbers, id) - { - Condition = condition, - CommandLine = commandLine, - Source = source, - Type = type - }); - } - } - - /// - /// Parses an EmbeddedUI element. - /// - /// Element to parse. - private void ParseEmbeddedUIElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string name = null; - var supportsBasicUI = false; - var messageFilter = WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT | WindowsInstallerConstants.INSTALLLOGMODE_ERROR | WindowsInstallerConstants.INSTALLLOGMODE_WARNING | WindowsInstallerConstants.INSTALLLOGMODE_USER - | WindowsInstallerConstants.INSTALLLOGMODE_INFO | WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE | WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE - | WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA - | WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS | WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA | WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE - | WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE | WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG | WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE - | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "IgnoreFatalExit": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT; - } - break; - case "IgnoreError": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ERROR; - } - break; - case "IgnoreWarning": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_WARNING; - } - break; - case "IgnoreUser": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_USER; - } - break; - case "IgnoreInfo": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INFO; - } - break; - case "IgnoreFilesInUse": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE; - } - break; - case "IgnoreResolveSource": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE; - } - break; - case "IgnoreOutOfDiskSpace": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE; - } - break; - case "IgnoreActionStart": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART; - } - break; - case "IgnoreActionData": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA; - } - break; - case "IgnoreProgress": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS; - } - break; - case "IgnoreCommonData": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA; - } - break; - case "IgnoreInitialize": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE; - } - break; - case "IgnoreTerminate": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE; - } - break; - case "IgnoreShowDialog": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG; - } - break; - case "IgnoreRMFilesInUse": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE; - } - break; - case "IgnoreInstallStart": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART; - } - break; - case "IgnoreInstallEnd": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND; - } - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SupportBasicUI": - supportsBasicUI = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(sourceFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - else if (String.IsNullOrEmpty(name)) - { - name = Path.GetFileName(sourceFile); - if (!this.Core.IsValidLongFilename(name, false)) - { - this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); - } - } - - if (null == id) - { - if (!String.IsNullOrEmpty(name)) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (!Common.IsIdentifier(id.Id)) - { - this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - } - else if (String.IsNullOrEmpty(name)) - { - name = id.Id; - } - - if (!name.Contains(".")) - { - this.Core.Write(ErrorMessages.InvalidEmbeddedUIFileName(sourceLineNumbers, name)); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "EmbeddedUIResource": - this.ParseEmbeddedUIResourceElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id) - { - FileName = name, - EntryPoint = true, - SupportsBasicUI = supportsBasicUI, - MessageFilter = messageFilter, - Source = sourceFile - }); - } - } - - /// - /// Parses a embedded UI resource element. - /// - /// Element to parse. - private void ParseEmbeddedUIResourceElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string name = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(sourceFile)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - else if (String.IsNullOrEmpty(name)) - { - name = Path.GetFileName(sourceFile); - if (!this.Core.IsValidLongFilename(name, false)) - { - this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); - } - } - - if (null == id) - { - if (!String.IsNullOrEmpty(name)) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (!Common.IsIdentifier(id.Id)) - { - this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - } - else if (String.IsNullOrEmpty(name)) - { - name = id.Id; - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id) - { - FileName = name, - Source = sourceFile - }); - } - } - } -} diff --git a/src/WixToolset.Core/Compiler_Module.cs b/src/WixToolset.Core/Compiler_Module.cs deleted file mode 100644 index 3986c8da..00000000 --- a/src/WixToolset.Core/Compiler_Module.cs +++ /dev/null @@ -1,662 +0,0 @@ -// 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 -{ - using System; - using System.Globalization; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses a module element. - /// - /// Element to parse. - private void ParseModuleElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var codepage = 0; - string moduleId = null; - string version = null; - var setCodepage = false; - var setPackageName = false; - var setKeywords = false; - var ignoredForMergeModules = false; - - this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); - - this.activeName = null; - this.activeLanguage = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if ("PUT-MODULE-NAME-HERE" == this.activeName) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); - } - else - { - this.activeName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - } - break; - case "Codepage": - codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); - break; - case "Guid": - moduleId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "InstallerVersion": - msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "Language": - this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Version": - version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == this.activeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == moduleId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Guid")); - } - - if (null == this.activeLanguage) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); - } - - if (null == version) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) - { - this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Module", version)); - } - - try - { - this.compilingModule = true; // notice that we are actually building a Merge Module here - this.Core.CreateActiveSection(this.activeName, SectionType.Module, this.Context.CompilationId); - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "AdminExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); - break; - case "AdminUISequence": - this.ParseSequenceElement(child, SequenceTable.AdminUISequence); - break; - case "AdvertiseExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); - break; - case "InstallExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); - break; - case "InstallUISequence": - this.ParseSequenceElement(child, SequenceTable.InstallUISequence); - break; - case "AppId": - this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); - break; - case "Binary": - this.ParseBinaryElement(child); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, CompilerConstants.IntegerNotSet, null, null); - break; - case "ComponentGroupRef": - this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); - break; - case "ComponentRef": - this.ParseComponentRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); - break; - case "Configuration": - this.ParseConfigurationElement(child); - break; - case "CustomAction": - this.ParseCustomActionElement(child); - break; - case "CustomActionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); - break; - case "CustomTable": - this.ParseCustomTableElement(child); - break; - case "CustomTableRef": - this.ParseCustomTableRefElement(child); - break; - case "Dependency": - this.ParseDependencyElement(child); - break; - case "Directory": - this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); - break; - case "DirectoryRef": - this.ParseDirectoryRefElement(child); - break; - case "EmbeddedChainer": - this.ParseEmbeddedChainerElement(child); - break; - case "EmbeddedChainerRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); - break; - case "EnsureTable": - this.ParseEnsureTableElement(child); - break; - case "Exclusion": - this.ParseExclusionElement(child); - break; - case "Icon": - this.ParseIconElement(child); - break; - case "IgnoreTable": - this.ParseIgnoreTableElement(child); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "PropertyRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Property); - break; - case "Requires": - this.ParseRequiresElement(child, null); - break; - case "SetDirectory": - this.ParseSetDirectoryElement(child); - break; - case "SetProperty": - this.ParseSetPropertyElement(child); - break; - case "SFPCatalog": - string parentName = null; - this.ParseSFPCatalogElement(child, ref parentName); - break; - case "StandardDirectory": - this.ParseStandardDirectoryElement(child); - break; - case "Substitution": - this.ParseSubstitutionElement(child); - break; - case "SummaryInformation": - this.ParseSummaryInformationElement(child, ref setCodepage, ref setPackageName, ref setKeywords, ref ignoredForMergeModules); - break; - case "UI": - this.ParseUIElement(child); - break; - case "UIRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); - break; - case "WixVariable": - this.ParseWixVariableElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - if (!setPackageName) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = this.activeName - }); - } - - if (!setKeywords) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = "Installer" - }); - } - - var symbol = this.Core.AddSymbol(new WixModuleSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, this.activeName, this.activeLanguage)) - { - ModuleId = this.activeName, - Language = this.activeLanguage, - Version = version - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.PackageCode, - Value = moduleId - }); - - this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, this.activeLanguage); - } - } - finally - { - this.compilingModule = false; // notice that we are no longer building a Merge Module here - } - } - - /// - /// Parses a dependency element. - /// - /// Element to parse. - private void ParseDependencyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string requiredId = null; - var requiredLanguage = CompilerConstants.IntegerNotSet; - string requiredVersion = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "RequiredId": - requiredId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "RequiredLanguage": - requiredLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "RequiredVersion": - requiredVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == requiredId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredId")); - requiredId = String.Empty; - } - - if (CompilerConstants.IntegerNotSet == requiredLanguage) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredLanguage")); - requiredLanguage = CompilerConstants.IllegalInteger; - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ModuleDependencySymbol(sourceLineNumbers) - { - ModuleID = this.activeName, - RequiredID = requiredId, - RequiredLanguage = requiredLanguage, - RequiredVersion = requiredVersion - }); - - symbol.Set((int)ModuleDependencySymbolFields.ModuleLanguage, this.activeLanguage); - } - } - - /// - /// Parses an exclusion element. - /// - /// Element to parse. - private void ParseExclusionElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string excludedId = null; - var excludeExceptLanguage = CompilerConstants.IntegerNotSet; - var excludeLanguage = CompilerConstants.IntegerNotSet; - var excludedLanguageField = "0"; - string excludedMaxVersion = null; - string excludedMinVersion = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ExcludedId": - excludedId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "ExcludeExceptLanguage": - excludeExceptLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "ExcludeLanguage": - excludeLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "ExcludedMaxVersion": - excludedMaxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ExcludedMinVersion": - excludedMinVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == excludedId) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExcludedId")); - excludedId = String.Empty; - } - - if (CompilerConstants.IntegerNotSet != excludeExceptLanguage && CompilerConstants.IntegerNotSet != excludeLanguage) - { - this.Core.Write(ErrorMessages.IllegalModuleExclusionLanguageAttributes(sourceLineNumbers)); - } - else if (CompilerConstants.IntegerNotSet != excludeExceptLanguage) - { - excludedLanguageField = Convert.ToString(-excludeExceptLanguage, CultureInfo.InvariantCulture); - } - else if (CompilerConstants.IntegerNotSet != excludeLanguage) - { - excludedLanguageField = Convert.ToString(excludeLanguage, CultureInfo.InvariantCulture); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ModuleExclusionSymbol(sourceLineNumbers) - { - ModuleID = this.activeName, - ExcludedID = excludedId, - ExcludedMinVersion = excludedMinVersion, - ExcludedMaxVersion = excludedMaxVersion - }); - - symbol.Set((int)ModuleExclusionSymbolFields.ModuleLanguage, this.activeLanguage); - symbol.Set((int)ModuleExclusionSymbolFields.ExcludedLanguage, excludedLanguageField); - } - } - - /// - /// Parses a configuration element for a configurable merge module. - /// - /// Element to parse. - private void ParseConfigurationElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string contextData = null; - string defaultValue = null; - string description = null; - string displayName = null; - var format = CompilerConstants.IntegerNotSet; - string helpKeyword = null; - string helpLocation = null; - bool keyNoOrphan = false; - bool nonNullable = false; - Identifier name = null; - string type = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - name = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ContextData": - contextData = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DefaultValue": - defaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Format": - var formatStr = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (formatStr) - { - case "Text": - case "text": - format = 0; - break; - case "Key": - case "key": - format = 1; - break; - case "Integer": - case "integer": - format = 2; - break; - case "Bitfield": - case "bitfield": - format = 3; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Format", formatStr, "Text", "Key", "Integer", "Bitfield")); - break; - } - break; - case "HelpKeyword": - helpKeyword = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "HelpLocation": - helpLocation = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "KeyNoOrphan": - keyNoOrphan = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "NonNullable": - nonNullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Type": - type = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (CompilerConstants.IntegerNotSet == format) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Format")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ModuleConfigurationSymbol(sourceLineNumbers, name) - { - Format = format, - Type = type, - ContextData = contextData, - DefaultValue = defaultValue, - KeyNoOrphan = keyNoOrphan, - NonNullable = nonNullable, - DisplayName = displayName, - Description = description, - HelpLocation = helpLocation, - HelpKeyword = helpKeyword - }); - } - } - - /// - /// Parses a substitution element for a configurable merge module. - /// - /// Element to parse. - private void ParseSubstitutionElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string column = null; - string rowKeys = null; - string table = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Column": - column = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Row": - rowKeys = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Table": - table = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == column) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Column")); - column = String.Empty; - } - - if (null == table) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Table")); - table = String.Empty; - } - - if (null == rowKeys) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Row")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ModuleSubstitutionSymbol(sourceLineNumbers) - { - Table = table, - Row = rowKeys, - Column = column, - Value = value - }); - } - } - - /// - /// Parses an IgnoreTable element. - /// - /// Element to parse. - private void ParseIgnoreTableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ModuleIgnoreTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, id))); - } - } - } -} diff --git a/src/WixToolset.Core/Compiler_Package.cs b/src/WixToolset.Core/Compiler_Package.cs deleted file mode 100644 index 87ccceb7..00000000 --- a/src/WixToolset.Core/Compiler_Package.cs +++ /dev/null @@ -1,4996 +0,0 @@ -// 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 -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses a product element. - /// - /// Element to parse. - private void ParsePackageElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compressed = YesNoDefaultType.Default; - var sourceBits = 0; - string codepage = null; - var productCode = "*"; - string productLanguage = null; - var isPerMachine = true; - string upgradeCode = null; - string manufacturer = null; - string version = null; - string symbols = null; - var isCodepageSet = false; - var isPackageNameSet = false; - var isKeywordsSet = false; - var isPackageAuthorSet = false; - - this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); - - this.activeName = null; - this.activeLanguage = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Codepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); - break; - case "Compressed": - compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - break; - case "InstallerVersion": - msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "Language": - productLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Manufacturer": - manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); - if ("PUT-COMPANY-NAME-HERE" == manufacturer) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer)); - } - break; - case "Name": - this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); - if ("PUT-PRODUCT-NAME-HERE" == this.activeName) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); - } - break; - case "ProductCode": - productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); - break; - case "Scope": - var installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (installScope) - { - case "perMachine": - // handled below after we create the section. - break; - case "perUser": - isPerMachine = false; - sourceBits |= 8; - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); - break; - } - break; - case "ShortNames": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - sourceBits |= 1; - } - break; - case "UpgradeCode": - upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Version": // if the attribute is valid version, use the attribute value as is (so "1.0000.01.01" would *not* get translated to "1.0.1.1"). - var verifiedVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - if (!String.IsNullOrEmpty(verifiedVersion)) - { - version = attrib.Value; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == productCode) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == manufacturer) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); - } - - if (null == this.activeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == upgradeCode) - { - this.Core.Write(WarningMessages.MissingUpgradeCode(sourceLineNumbers)); - } - - if (null == version) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - else if (!CompilerCore.IsValidProductVersion(version)) - { - this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); - } - - if (compressed != YesNoDefaultType.No) - { - sourceBits |= 2; - } - - if (this.Core.EncounteredError) - { - return; - } - - try - { - this.compilingProduct = true; - this.Core.CreateActiveSection(productCode, SectionType.Product, this.Context.CompilationId); - - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); - if (null != upgradeCode) - { - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); - } - - if (isPerMachine) - { - this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); - } - - this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, productLanguage); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WordCount, - Value = sourceBits.ToString(CultureInfo.InvariantCulture) - }); - - var contextValues = new Dictionary - { - ["ProductLanguage"] = productLanguage, - ["ProductVersion"] = version, - ["UpgradeCode"] = upgradeCode - }; - - var featureDisplay = 0; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "_locDefinition": - break; - case "AdminExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); - break; - case "AdminUISequence": - this.ParseSequenceElement(child, SequenceTable.AdminUISequence); - break; - case "AdvertiseExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); - break; - case "InstallExecuteSequence": - this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); - break; - case "InstallUISequence": - this.ParseSequenceElement(child, SequenceTable.InstallUISequence); - break; - case "AppId": - this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); - break; - case "Binary": - this.ParseBinaryElement(child); - break; - case "ComplianceCheck": - this.ParseComplianceCheckElement(child); - break; - case "Component": - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); - break; - case "ComponentGroup": - this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); - break; - case "CustomAction": - this.ParseCustomActionElement(child); - break; - case "CustomActionRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); - break; - case "CustomTable": - this.ParseCustomTableElement(child); - break; - case "CustomTableRef": - this.ParseCustomTableRefElement(child); - break; - case "Directory": - this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); - break; - case "DirectoryRef": - this.ParseDirectoryRefElement(child); - break; - case "EmbeddedChainer": - this.ParseEmbeddedChainerElement(child); - break; - case "EmbeddedChainerRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); - break; - case "EnsureTable": - this.ParseEnsureTableElement(child); - break; - case "Feature": - this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay); - break; - case "FeatureRef": - this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode); - break; - case "FeatureGroupRef": - this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode); - break; - case "Icon": - this.ParseIconElement(child); - break; - case "InstanceTransforms": - this.ParseInstanceTransformsElement(child); - break; - case "Launch": - this.ParseLaunchElement(child); - break; - case "MajorUpgrade": - this.ParseMajorUpgradeElement(child, contextValues); - break; - case "Media": - this.ParseMediaElement(child, null); - break; - case "MediaTemplate": - this.ParseMediaTemplateElement(child, null); - break; - case "PackageCertificates": - case "PatchCertificates": - this.ParseCertificatesElement(child); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "PropertyRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Property); - break; - case "Requires": - this.ParseRequiresElement(child, null); - break; - case "SetDirectory": - this.ParseSetDirectoryElement(child); - break; - case "SetProperty": - this.ParseSetPropertyElement(child); - break; - case "SFPCatalog": - string parentName = null; - this.ParseSFPCatalogElement(child, ref parentName); - break; - case "SoftwareTag": - this.ParsePackageTagElement(child); - break; - case "StandardDirectory": - this.ParseStandardDirectoryElement(child); - break; - case "SummaryInformation": - this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); - break; - case "SymbolPath": - if (null != symbols) - { - symbols += ";" + this.ParseSymbolPathElement(child); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - case "UI": - this.ParseUIElement(child); - break; - case "UIRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); - break; - case "Upgrade": - this.ParseUpgradeElement(child); - break; - case "WixVariable": - this.ParseWixVariableElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPackageSymbol(sourceLineNumbers) - { - PackageId = productCode, - UpgradeCode = upgradeCode, - Name = this.activeName, - Language = productLanguage, - Version = version, - Manufacturer = manufacturer, - Attributes = isPerMachine ? WixPackageAttributes.PerMachine : WixPackageAttributes.None, - Codepage = codepage, - }); - - if (!isPackageNameSet) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = this.activeName - }); - } - - if (!isPackageAuthorSet) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Author, - Value = manufacturer - }); - } - - if (!isKeywordsSet) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = "Installer" - }); - } - - if (null != symbols) - { - this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers) - { - SymbolId = productCode, - SymbolType = SymbolPathType.Product, - SymbolPaths = symbols, - }); - } - } - } - finally - { - this.compilingProduct = false; - } - } - - private void GetDefaultPlatformAndInstallerVersion(out string platform, out int msiVersion) - { - // Let's default to a modern version of MSI. Users can override, - // of course, subject to platform-specific limitations. - msiVersion = 500; - - switch (this.CurrentPlatform) - { - case Platform.X86: - platform = "Intel"; - break; - case Platform.X64: - platform = "x64"; - break; - case Platform.ARM64: - platform = "Arm64"; - break; - default: - throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString()); - } - } - - private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform, string language) - { - if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) - { - msiVersion = 200; - this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers)); - } - - if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion) - { - msiVersion = 500; - this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers)); - } - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Comments, - Value = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Title, - Value = "Installation Database" - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.PlatformAndLanguage, - Value = $"{platform};{language}" - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WindowsInstallerVersion, - Value = msiVersion.ToString(CultureInfo.InvariantCulture) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Security, - Value = "2" - }); - } - - /// - /// Parses an odbc driver or translator element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Default identifer for driver/translator file. - /// Symbol type we're processing for. - private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, SymbolDefinitionType symbolDefinitionType) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var driver = fileId; - string name = null; - var setup = fileId; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "File": - driver = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, driver); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SetupFile": - setup = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, setup); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("odb", name, fileId, setup); - } - - // drivers have a few possible children - if (SymbolDefinitionType.ODBCDriver == symbolDefinitionType) - { - // process any data sources for the driver - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ODBCDataSource": - string ignoredKeyPath = null; - this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath); - break; - case "Property": - this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCAttribute); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - else - { - this.Core.ParseForExtensionElements(node); - } - - if (!this.Core.EncounteredError) - { - switch (symbolDefinitionType) - { - case SymbolDefinitionType.ODBCDriver: - this.Core.AddSymbol(new ODBCDriverSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Description = name, - FileRef = driver, - SetupFileRef = setup, - }); - break; - case SymbolDefinitionType.ODBCTranslator: - this.Core.AddSymbol(new ODBCTranslatorSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Description = name, - FileRef = driver, - SetupFileRef = setup, - }); - break; - default: - throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); - } - } - } - - /// - /// Parses a Property element underneath an ODBC driver or translator. - /// - /// Element to parse. - /// Identifier of parent driver or translator. - /// Name of the table to create property in. - private void ParseODBCProperty(XElement node, string parentId, SymbolDefinitionType symbolDefinitionType) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string propertyValue = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - propertyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var identifier = new Identifier(AccessModifier.Section, parentId, id); - switch (symbolDefinitionType) - { - case SymbolDefinitionType.ODBCAttribute: - this.Core.AddSymbol(new ODBCAttributeSymbol(sourceLineNumbers, identifier) - { - DriverRef = parentId, - Attribute = id, - Value = propertyValue, - }); - break; - case SymbolDefinitionType.ODBCSourceAttribute: - this.Core.AddSymbol(new ODBCSourceAttributeSymbol(sourceLineNumbers, identifier) - { - DataSourceRef = parentId, - Attribute = id, - Value = propertyValue, - }); - break; - default: - throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); - } - } - } - - /// - /// Parse an odbc data source element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Default name of driver. - /// Identifier of this element in case it is a keypath. - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var keyPath = YesNoType.NotSet; - string name = null; - var registration = CompilerConstants.IntegerNotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "DriverName": - driverName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "KeyPath": - keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Registration": - var registrationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (registrationValue) - { - case "machine": - registration = 0; - break; - case "user": - registration = 1; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (CompilerConstants.IntegerNotSet == registration) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration")); - registration = CompilerConstants.IllegalInteger; - } - - if (null == id) - { - id = this.Core.CreateIdentifier("odc", name, driverName, registration.ToString()); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Property": - this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCSourceAttribute); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ODBCDataSourceSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - Description = name, - DriverDescription = driverName, - Registration = registration - }); - } - - possibleKeyPath = id.Id; - return keyPath; - } - - /// - /// Parses a package element. - /// - /// Element to parse. - /// - /// - /// - /// - private void ParseSummaryInformationElement(XElement node, ref bool isCodepageSet, ref bool isPackageNameSet, ref bool isKeywordsSet, ref bool isPackageAuthorSet) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string codepage = null; - string packageName = null; - string keywords = null; - string packageAuthor = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Codepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); - break; - case "Description": - packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Keywords": - keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Manufacturer": - packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if ("PUT-COMPANY-NAME-HERE" == packageAuthor) - { - this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor)); - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - if (null != codepage) - { - isCodepageSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Codepage, - Value = codepage - }); - } - - if (null != packageName) - { - isPackageNameSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = packageName - }); - } - - if (null != packageAuthor) - { - isPackageAuthorSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Author, - Value = packageAuthor - }); - } - - if (null != keywords) - { - isKeywordsSet = true; - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = keywords - }); - } - } - } - - /// - /// Parses a patch information element. - /// - /// Element to parse. - private void ParsePatchInformationElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var codepage = "1252"; - string comments = null; - var keywords = "Installer,Patching,PCP,Database"; - var msiVersion = 1; // Should always be 1 for patches - string packageAuthor = null; - var packageName = this.activeName; - var security = YesNoDefaultType.Default; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AdminImage": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "Comments": - comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Compressed": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "Description": - packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Keywords": - keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Languages": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "Manufacturer": - packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Platforms": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "ReadOnly": - security = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - break; - case "ShortNames": - this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - break; - case "SummaryCodepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Codepage, - Value = codepage - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Title, - Value = "Patch" - }); - - if (null != packageName) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Subject, - Value = packageName - }); - } - - if (null != packageAuthor) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Author, - Value = packageAuthor - }); - } - - if (null != keywords) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Keywords, - Value = keywords - }); - } - - if (null != comments) - { - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Comments, - Value = comments - }); - } - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WindowsInstallerVersion, - Value = msiVersion.ToString(CultureInfo.InvariantCulture) - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.WordCount, - Value = "0" - }); - - this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) - { - PropertyId = SummaryInformationType.Security, - Value = YesNoDefaultType.No == security ? "0" : YesNoDefaultType.Yes == security ? "4" : "2" - }); - } - } - - /// - /// Parses a permission element. - /// - /// Element to parse. - /// Identifier of object to be secured. - /// Name of table that contains objectId. - private void ParsePermissionElement(XElement node, string objectId, string tableName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var bits = new BitArray(32); - string domain = null; - string[] specialPermissions = null; - string user = null; - - switch (tableName) - { - case "CreateFolder": - specialPermissions = LockPermissionConstants.FolderPermissions; - break; - case "File": - specialPermissions = LockPermissionConstants.FilePermissions; - break; - case "Registry": - specialPermissions = LockPermissionConstants.RegistryPermissions; - break; - default: - this.Core.UnexpectedElement(node.Parent, node); - return; // stop processing this element since no valid permissions are available - } - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Domain": - domain = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "User": - user = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "FileAllRights": - // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127) - bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[16] = bits[17] = bits[18] = bits[19] = bits[20] = true; - break; - case "SpecificRightsAll": - // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111) - bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[9] = bits[10] = bits[11] = bits[12] = bits[13] = bits[14] = bits[15] = true; - break; - default: - var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - if (!this.Core.TrySetBitFromName(LockPermissionConstants.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16)) - { - if (!this.Core.TrySetBitFromName(LockPermissionConstants.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28)) - { - if (!this.Core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0)) - { - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == user) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User")); - } - - var permission = this.Core.CreateIntegerFromBitArray(bits); - - if (Int32.MinValue == permission) // just GENERIC_READ, which is MSI_NULL - { - this.Core.Write(ErrorMessages.GenericReadNotAllowed(sourceLineNumbers)); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new LockPermissionsSymbol(sourceLineNumbers) - { - LockObject = objectId, - Table = tableName, - Domain = domain, - User = user, - Permission = permission - }); - } - } - - /// - /// Parses an extended permission element. - /// - /// Element to parse. - /// Identifier of object to be secured. - /// Name of table that contains objectId. - private void ParsePermissionExElement(XElement node, string objectId, string tableName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string condition = null; - Identifier id = null; - string sddl = null; - - switch (tableName) - { - case "CreateFolder": - case "File": - case "Registry": - case "ServiceInstall": - break; - default: - this.Core.UnexpectedElement(node.Parent, node); - return; // stop processing this element since nothing will be valid. - } - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Sddl": - sddl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == sddl) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl")); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("pme", objectId, tableName, sddl); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiLockPermissionsExSymbol(sourceLineNumbers, id) - { - LockObject = objectId, - Table = tableName, - SDDLText = sddl, - Condition = condition - }); - } - } - - /// - /// Parses a progid element - /// - /// Element to parse. - /// Identifier of parent component. - /// Flag if progid is advertised. - /// CLSID related to ProgId. - /// Default description of ProgId - /// Optional parent ProgId - /// Set to true if an extension is found; used for error-checking. - /// Whether or not this ProgId is the first one found in the parent class. - /// This element's Id. - private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string icon = null; - var iconIndex = CompilerConstants.IntegerNotSet; - string noOpen = null; - string progId = null; - var progIdAdvertise = YesNoType.NotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - progId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Advertise": - progIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "Icon": - icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "IconIndex": - iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); - break; - case "NoOpen": - noOpen = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise)) - { - this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString())); - } - else if (YesNoType.NotSet != progIdAdvertise) - { - advertise = progIdAdvertise; - } - - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex)) - { - this.Core.Write(ErrorMessages.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers)); - } - - var firstProgIdForNestedClass = YesNoType.Yes; - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Extension": - this.ParseExtensionElement(child, componentId, advertise, progId); - foundExtension = true; - break; - case "ProgId": - // Only allow one nested ProgId. If we have a child, we should not have a parent. - if (null == parent) - { - if (YesNoType.Yes == advertise) - { - this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass); - } - else if (YesNoType.No == advertise) - { - this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass); - } - - firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first. - } - else - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.ProgIdNestedTooDeep(childSourceLineNumbers)); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (YesNoType.Yes == advertise) - { - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ProgIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, progId)) - { - ProgId = progId, - ParentProgIdRef = parent, - ClassRef = classId, - Description = description, - }); - - if (null != icon) - { - symbol.IconRef = icon; - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); - } - - if (CompilerConstants.IntegerNotSet != iconIndex) - { - symbol.IconIndex = iconIndex; - } - - this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Class); - } - } - else if (YesNoType.No == advertise) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, String.Empty, description, componentId); - if (null != classId) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId); - if (null != parent) // if this is a version independent ProgId - { - if (YesNoType.Yes == firstProgIdForClass) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId); - } - else - { - if (YesNoType.Yes == firstProgIdForClass) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId); - } - } - } - - if (null != icon) // ProgId's Default Icon - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); - - icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); - - if (CompilerConstants.IntegerNotSet != iconIndex) - { - icon = String.Concat(icon, ",", iconIndex); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId); - } - } - - if (null != noOpen) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name - } - - // raise an error for an orphaned ProgId - if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId) - { - this.Core.Write(WarningMessages.OrphanedProgId(sourceLineNumbers, progId)); - } - - return progId; - } - - /// - /// Parses a property element. - /// - /// Element to parse. - private void ParsePropertyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var admin = false; - var complianceCheck = false; - var hidden = false; - var secure = false; - var suppressModularization = YesNoType.NotSet; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Admin": - admin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ComplianceCheck": - complianceCheck = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Hidden": - hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Secure": - secure = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SuppressModularization": - suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if ("ProductID" == id.Id) - { - this.Core.Write(WarningMessages.ProductIdAuthored(sourceLineNumbers)); - } - else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id) - { - this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); - } - - if ("ErrorDialog" == id.Id) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, value); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - { - switch (child.Name.LocalName) - { - case "ProductSearch": - this.ParseProductSearchElement(child, id.Id); - secure = true; - break; - default: - // let ParseSearchSignatures handle standard AppSearch children and unknown elements - break; - } - } - } - } - - this.Core.InnerTextDisallowed(node); - - // see if this property is used for appSearch - var signatures = this.ParseSearchSignatures(node); - - // If we're doing CCP then there must be a signature. - if (complianceCheck && 0 == signatures.Count) - { - this.Core.Write(ErrorMessages.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes")); - } - - foreach (var sig in signatures) - { - if (complianceCheck && !this.Core.EncounteredError) - { - this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, sig))); - } - - this.AddAppSearch(sourceLineNumbers, id, sig); - } - - // If we're doing AppSearch get that setup. - if (0 < signatures.Count) - { - this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); - } - else // just a normal old property. - { - // If the property value is empty and none of the flags are set, print out a warning that we're ignoring - // the element. - if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden) - { - this.Core.Write(WarningMessages.PropertyUseless(sourceLineNumbers, id.Id)); - } - else // there is a value and/or a flag set, do that. - { - this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); - } - } - - if (!this.Core.EncounteredError && YesNoType.Yes == suppressModularization) - { - this.Core.Write(WarningMessages.PropertyModularizationSuppressed(sourceLineNumbers)); - - this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) - { - SuppressIdentifier = id.Id - }); - } - } - - /// - /// Parses a RegistryKey element. - /// - /// Element to parse. - /// Identifier for parent component. - /// Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet. - /// Parent key for this Registry element when nested. - /// true if the component is 64-bit. - /// Identifier of this registry key since it could be the component's keypath. - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseRegistryKeyElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var key = parentKey; // default to parent key path - var forceCreateOnInstall = false; - var forceDeleteOnUninstall = false; - var keyPath = YesNoType.NotSet; - - possibleKeyPath = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ForceCreateOnInstall": - forceCreateOnInstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ForceDeleteOnUninstall": - forceDeleteOnUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (null != parentKey) - { - key = Path.Combine(parentKey, key); - } - key = key?.TrimEnd('\\'); - break; - case "Root": - if (root.HasValue) - { - this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); - } - - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - var name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null); - - if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present - { - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); - } - } - else // does not generate a Registry row, so no Id should be present - { - if (null != id) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true)); - } - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - key = String.Empty; // set the key to something to prevent null reference exceptions - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - string possibleChildKeyPath = null; - - switch (child.Name.LocalName) - { - case "RegistryKey": - if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) - { - if (YesNoType.Yes == keyPath) - { - this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); - } - - possibleKeyPath = possibleChildKeyPath; // the child is the key path - keyPath = YesNoType.Yes; - } - else if (null == possibleKeyPath && null != possibleChildKeyPath) - { - possibleKeyPath = possibleChildKeyPath; - } - break; - case "RegistryValue": - if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) - { - if (YesNoType.Yes == keyPath) - { - this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); - } - - possibleKeyPath = possibleChildKeyPath; // the child is the key path - keyPath = YesNoType.Yes; - } - else if (null == possibleKeyPath && null != possibleChildKeyPath) - { - possibleKeyPath = possibleChildKeyPath; - } - break; - case "Permission": - if (!forceCreateOnInstall) - { - this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); - } - this.ParsePermissionElement(child, id.Id, "Registry"); - break; - case "PermissionEx": - if (!forceCreateOnInstall) - { - this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); - } - this.ParsePermissionExElement(child, id.Id, "Registry"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (!this.Core.EncounteredError && null != name) - { - this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - ComponentRef = componentId, - }); - } - - return keyPath; - } - - /// - /// Parses a RegistryValue element. - /// - /// Element to parse. - /// Identifier for parent component. - /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. - /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. - /// true if the component is 64-bit. - /// Identifier of this registry key since it could be the component's keypath. - /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseRegistryValueElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var key = parentKey; // default to parent key path - string name = null; - string value = null; - string action = null; - var valueType = RegistryValueType.String; - var actionType = RegistryValueActionType.Write; - var keyPath = YesNoType.NotSet; - - possibleKeyPath = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "append": - actionType = RegistryValueActionType.Append; - break; - case "prepend": - actionType = RegistryValueActionType.Prepend; - break; - case "write": - actionType = RegistryValueActionType.Write; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "append", "prepend", "write")); - break; - } - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (null != parentKey) - { - if (parentKey.EndsWith("\\", StringComparison.Ordinal)) - { - key = String.Concat(parentKey, key); - } - else - { - key = String.Concat(parentKey, "\\", key); - } - } - break; - case "KeyPath": - keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - if (root.HasValue) - { - this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); - } - - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "binary": - valueType = RegistryValueType.Binary; - break; - case "expandable": - valueType = RegistryValueType.Expandable; - break; - case "integer": - valueType = RegistryValueType.Integer; - break; - case "multiString": - valueType = RegistryValueType.MultiString; - break; - case "string": - valueType = RegistryValueType.String; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "binary", "expandable", "integer", "multiString", "string")); - break; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)(root ?? RegistryRootType.Unknown)).ToString(), LowercaseOrNull(key), LowercaseOrNull(name)); - } - - if (RegistryValueType.MultiString != valueType && (RegistryValueActionType.Append == actionType || RegistryValueActionType.Prepend == actionType)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "MultiString": - case "MultiStringValue": - if (RegistryValueType.MultiString != valueType && null != value) - { - this.Core.Write(ErrorMessages.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type")); - } - else - { - value = this.ParseRegistryMultiStringElement(child, value); - } - break; - case "Permission": - this.ParsePermissionElement(child, id.Id, "Registry"); - break; - case "PermissionEx": - this.ParsePermissionExElement(child, id.Id, "Registry"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - //switch (typeType) - //{ - //case Wix.RegistryValue.TypeType.binary: - // value = String.Concat("#x", value); - // break; - //case Wix.RegistryValue.TypeType.expandable: - // value = String.Concat("#%", value); - // break; - //case Wix.RegistryValue.TypeType.integer: - // value = String.Concat("#", value); - // break; - //case Wix.RegistryValue.TypeType.multiString: - // switch (actionType) - // { - // case Wix.RegistryValue.ActionType.append: - // value = String.Concat("[~]", value); - // break; - // case Wix.RegistryValue.ActionType.prepend: - // value = String.Concat(value, "[~]"); - // break; - // case Wix.RegistryValue.ActionType.write: - // default: - // if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) - // { - // value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); - // } - // break; - // } - // break; - //case Wix.RegistryValue.TypeType.@string: - // // escape the leading '#' character for string registry keys - // if (null != value && value.StartsWith("#", StringComparison.Ordinal)) - // { - // value = String.Concat("#", value); - // } - // break; - //} - - // value may be set by child MultiStringValue elements, so it must be checked here - if (null == value && valueType != RegistryValueType.Binary) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - else if (0 == value?.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values - { - this.Core.Write(ErrorMessages.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name)); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - Value = value, - ValueType = valueType, - ValueAction = actionType, - ComponentRef = componentId, - }); - } - - // If this was just a regular registry key (that could be the key path) - // and no child registry key set the possible key path, let's make this - // Registry/@Id a possible key path. - if (null == possibleKeyPath) - { - possibleKeyPath = id.Id; - } - - return keyPath; - } - - private string ParseRegistryMultiStringElement(XElement node, string value) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string multiStringValue = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Value": - multiStringValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - this.Core.ParseForExtensionElements(node); - - return null == value ? multiStringValue ?? "[~]" : String.Concat(value, "[~]", multiStringValue); - } - - /// - /// Parses a RemoveRegistryKey element. - /// - /// The element to parse. - /// The component identifier of the parent element. - private void ParseRemoveRegistryKeyElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - RemoveRegistryActionType? actionType = null; - string key = null; - var name = "-"; - RegistryRootType? root = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Action": - var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (actionValue) - { - case "removeOnInstall": - actionType = RemoveRegistryActionType.RemoveOnInstall; - break; - case "removeOnUninstall": - actionType = RemoveRegistryActionType.RemoveOnUninstall; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "removeOnInstall", "removeOnUninstall")); - break; - } - //if (0 < action.Length) - //{ - // if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType)) - // { - // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall")); - // } - //} - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - if (!actionType.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - Action = actionType.Value, - ComponentRef = componentId, - }); - } - } - - /// - /// Parses a RemoveRegistryValue element. - /// - /// The element to parse. - /// The component identifier of the parent element. - private void ParseRemoveRegistryValueElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string name = null; - RegistryRootType? root = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Root": - root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // generate the identifier if it wasn't provided - if (null == id) - { - id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); - } - - if (!root.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); - } - - if (null == key) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) - { - Root = root.Value, - Key = key, - Name = name, - ComponentRef = componentId - }); - } - } - - /// - /// Parses a remove file element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of the parent component's directory. - private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string directoryId = null; - string subdirectory = null; - string name = null; - bool? onInstall = null; - bool? onUninstall = null; - string propertyId = null; - string shortName = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, true); - break; - case "On": - var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (onValue) - { - case "install": - onInstall = true; - break; - case "uninstall": - onUninstall = true; - break; - case "both": - onInstall = true; - onUninstall = true; - break; - } - break; - case "Property": - propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (!onInstall.HasValue && !onUninstall.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); - } - - if (String.IsNullOrEmpty(propertyId)) - { - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); - } - else if (!String.IsNullOrEmpty(directoryId)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); - } - else if (!String.IsNullOrEmpty(subdirectory)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); - } - - if (null == id) - { - var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; - id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - FileName = name, - ShortFileName = shortName, - DirPropertyRef = directoryId ?? propertyId ?? parentDirectory, - OnInstall = onInstall, - OnUninstall = onUninstall, - }); - } - } - - /// - /// Parses a RemoveFolder element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of parent component's directory. - private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string directoryId = null; - string subdirectory = null; - bool? onInstall = null; - bool? onUninstall = null; - string propertyId = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "On": - var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (onValue) - { - case "install": - onInstall = true; - break; - case "uninstall": - onUninstall = true; - break; - case "both": - onInstall = true; - onUninstall = true; - break; - } - break; - case "Property": - propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (!onInstall.HasValue && !onUninstall.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); - } - - if (String.IsNullOrEmpty(propertyId)) - { - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); - } - else if (!String.IsNullOrEmpty(directoryId)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); - } - else if (!String.IsNullOrEmpty(subdirectory)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); - } - - if (null == id) - { - var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; - id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId, on.ToString()); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - DirPropertyRef = directoryId ?? propertyId, - OnInstall = onInstall, - OnUninstall = onUninstall - }); - } - } - - /// - /// Parses a reserve cost element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional and default identifier of referenced directory. - private void ParseReserveCostElement(XElement node, string componentId, string directoryId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string subdirectory = null; - var runFromSource = CompilerConstants.IntegerNotSet; - var runLocal = CompilerConstants.IntegerNotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "RunFromSource": - runFromSource = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "RunLocal": - runLocal = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); - - if (null == id) - { - id = this.Core.CreateIdentifier("rc", componentId, directoryId); - } - - if (CompilerConstants.IntegerNotSet == runFromSource) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource")); - } - - if (CompilerConstants.IntegerNotSet == runLocal) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ReserveCostSymbol(sourceLineNumbers, id) - { - ComponentRef = componentId, - ReserveFolder = directoryId, - ReserveLocal = runLocal, - ReserveSource = runFromSource - }); - } - } - - /// - /// Parses a sequence element. - /// - /// Element to parse. - /// Name of sequence table. - private void ParseSequenceElement(XElement node, SequenceTable sequenceTable) - { - // Parse each action in the sequence. - foreach (var child in node.Elements()) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - var actionName = child.Name.LocalName; - string afterAction = null; - string beforeAction = null; - string condition = null; - var customAction = "Custom" == actionName; - var overridable = false; - var exitSequence = CompilerConstants.IntegerNotSet; - var sequence = CompilerConstants.IntegerNotSet; - var showDialog = "Show" == actionName; - var specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName; - var specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName; - var suppress = false; - - foreach (var attrib in child.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - if (customAction) - { - actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.CustomAction, actionName); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "After": - if (customAction || showDialog || specialAction || specialStandardAction) - { - afterAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), afterAction); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "Before": - if (customAction || showDialog || specialAction || specialStandardAction) - { - beforeAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), beforeAction); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "Condition": - condition = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - case "Dialog": - if (showDialog) - { - actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); - this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.Dialog, actionName); - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "OnExit": - if (customAction || showDialog || specialAction) - { - var exitValue = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - switch (exitValue) - { - case "success": - exitSequence = -1; - break; - case "cancel": - exitSequence = -2; - break; - case "error": - exitSequence = -3; - break; - case "suspend": - exitSequence = -4; - break; - } - } - else - { - this.Core.UnexpectedAttribute(child, attrib); - } - break; - case "Overridable": - overridable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); - break; - case "Sequence": - sequence = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "Suppress": - suppress = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (customAction && "Custom" == actionName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); - } - else if (showDialog && "Show" == actionName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog")); - } - - if (CompilerConstants.IntegerNotSet != sequence) - { - if (CompilerConstants.IntegerNotSet != exitSequence) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit")); - } - else if (null != beforeAction || null != afterAction) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After")); - } - } - else // sequence not specified use OnExit (which may also be not set). - { - sequence = exitSequence; - } - - if (null != beforeAction && null != afterAction) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before")); - } - else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction) - { - this.Core.Write(ErrorMessages.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName)); - } - - // action that is scheduled to occur before/after itself - if (beforeAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction)); - } - else if (afterAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction)); - } - - // normal standard actions cannot be set overridable by the user (since they are overridable by default) - if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction) - { - this.Core.Write(ErrorMessages.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable")); - } - - // suppress cannot be specified at the same time as Before, After, or Sequence - if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable")); - } - - this.Core.ParseForExtensionElements(child); - - // add the row and any references needed - if (!this.Core.EncounteredError) - { - if (suppress) - { - this.Core.AddSymbol(new WixSuppressActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName)) - { - SequenceTable = sequenceTable, - Action = actionName - }); - } - else - { - var symbol = this.Core.AddSymbol(new WixActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName)) - { - SequenceTable = sequenceTable, - Action = actionName, - Condition = condition, - Before = beforeAction, - After = afterAction, - Overridable = overridable, - }); - - if (CompilerConstants.IntegerNotSet != sequence) - { - symbol.Sequence = sequence; - } - } - } - } - - this.Core.InnerTextDisallowed(node); - } - - - /// - /// Parses a service config element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional element containing parent's service name. - private void ParseServiceConfigElement(XElement node, string componentId, string serviceName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string delayedAutoStart = null; - string failureActionsWhen = null; - var name = serviceName; - var install = false; - var reinstall = false; - var uninstall = false; - string preShutdownDelay = null; - string requiredPrivileges = null; - string sid = null; - - this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "DelayedAutoStart": - delayedAutoStart = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (delayedAutoStart) - { - case "no": - delayedAutoStart = "0"; - break; - case "yes": - delayedAutoStart = "1"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - case "FailureActionsWhen": - failureActionsWhen = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (failureActionsWhen) - { - case "failedToStop": - failureActionsWhen = "0"; - break; - case "failedToStopOrReturnedError": - failureActionsWhen = "1"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - case "OnInstall": - install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == install) - //{ - // events |= MsiInterop.MsidbServiceConfigEventInstall; - //} - break; - case "OnReinstall": - reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == reinstall) - //{ - // events |= MsiInterop.MsidbServiceConfigEventReinstall; - //} - break; - case "OnUninstall": - uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - //if (YesNoType.Yes == uninstall) - //{ - // events |= MsiInterop.MsidbServiceConfigEventUninstall; - //} - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - case "PreShutdownDelay": - preShutdownDelay = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "ServiceName": - if (!String.IsNullOrEmpty(serviceName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); - } - - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ServiceSid": - sid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (sid) - { - case "none": - sid = "0"; - break; - case "restricted": - sid = "3"; - break; - case "unrestricted": - sid = "1"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // Get the ServiceConfig required privilegs. - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "RequiredPrivilege": - requiredPrivileges = this.ParseRequiredPrivilege(child, requiredPrivileges); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); - } - else if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (!install && !reinstall && !uninstall) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); - } - - if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid)) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege")); - } - - if (!this.Core.EncounteredError) - { - if (!String.IsNullOrEmpty(delayedAutoStart)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".DS"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.DelayedAutoStart, - Argument = delayedAutoStart, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(failureActionsWhen)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".FA"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.FailureActionsFlag, - Argument = failureActionsWhen, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(sid)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".SS"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.ServiceSidInfo, - Argument = sid, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(requiredPrivileges)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".RP"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.RequiredPrivilegesInfo, - Argument = requiredPrivileges, - ComponentRef = componentId, - }); - } - - if (!String.IsNullOrEmpty(preShutdownDelay)) - { - this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".PD"))) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ConfigType = MsiServiceConfigType.PreshutdownInfo, - Argument = preShutdownDelay, - ComponentRef = componentId, - }); - } - } - } - - private string ParseRequiredPrivilege(XElement node, string requiredPrivileges) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string privilege = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - privilege = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (privilege) - { - case "assignPrimaryToken": - privilege = "SeAssignPrimaryTokenPrivilege"; - break; - case "audit": - privilege = "SeAuditPrivilege"; - break; - case "backup": - privilege = "SeBackupPrivilege"; - break; - case "changeNotify": - privilege = "SeChangeNotifyPrivilege"; - break; - case "createGlobal": - privilege = "SeCreateGlobalPrivilege"; - break; - case "createPagefile": - privilege = "SeCreatePagefilePrivilege"; - break; - case "createPermanent": - privilege = "SeCreatePermanentPrivilege"; - break; - case "createSymbolicLink": - privilege = "SeCreateSymbolicLinkPrivilege"; - break; - case "createToken": - privilege = "SeCreateTokenPrivilege"; - break; - case "debug": - privilege = "SeDebugPrivilege"; - break; - case "enableDelegation": - privilege = "SeEnableDelegationPrivilege"; - break; - case "impersonate": - privilege = "SeImpersonatePrivilege"; - break; - case "increaseBasePriority": - privilege = "SeIncreaseBasePriorityPrivilege"; - break; - case "increaseQuota": - privilege = "SeIncreaseQuotaPrivilege"; - break; - case "increaseWorkingSet": - privilege = "SeIncreaseWorkingSetPrivilege"; - break; - case "loadDriver": - privilege = "SeLoadDriverPrivilege"; - break; - case "lockMemory": - privilege = "SeLockMemoryPrivilege"; - break; - case "machineAccount": - privilege = "SeMachineAccountPrivilege"; - break; - case "manageVolume": - privilege = "SeManageVolumePrivilege"; - break; - case "profileSingleProcess": - privilege = "SeProfileSingleProcessPrivilege"; - break; - case "relabel": - privilege = "SeRelabelPrivilege"; - break; - case "remoteShutdown": - privilege = "SeRemoteShutdownPrivilege"; - break; - case "restore": - privilege = "SeRestorePrivilege"; - break; - case "security": - privilege = "SeSecurityPrivilege"; - break; - case "shutdown": - privilege = "SeShutdownPrivilege"; - break; - case "syncAgent": - privilege = "SeSyncAgentPrivilege"; - break; - case "systemEnvironment": - privilege = "SeSystemEnvironmentPrivilege"; - break; - case "systemProfile": - privilege = "SeSystemProfilePrivilege"; - break; - case "systemTime": - case "modifySystemTime": - privilege = "SeSystemtimePrivilege"; - break; - case "takeOwnership": - privilege = "SeTakeOwnershipPrivilege"; - break; - case "tcb": - case "trustedComputerBase": - privilege = "SeTcbPrivilege"; - break; - case "timeZone": - case "modifyTimeZone": - privilege = "SeTimeZonePrivilege"; - break; - case "trustedCredManAccess": - case "trustedCredentialManagerAccess": - privilege = "SeTrustedCredManAccessPrivilege"; - break; - case "undock": - privilege = "SeUndockPrivilege"; - break; - case "unsolicitedInput": - privilege = "SeUnsolicitedInputPrivilege"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - if (privilege == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - this.Core.ParseForExtensionElements(node); - - return (requiredPrivileges == null) ? privilege : String.Concat(requiredPrivileges, "[~]", privilege); - } - - /// - /// Parses a service config failure actions element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Optional element containing parent's service name. - private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var name = serviceName; - var install = false; - var reinstall = false; - var uninstall = false; - int? resetPeriod = null; - string rebootMessage = null; - string command = null; - string actions = null; - string actionsDelays = null; - - this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Command": - command = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "OnInstall": - install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "OnReinstall": - reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "OnUninstall": - uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RebootMessage": - rebootMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - case "ResetPeriod": - resetPeriod = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "ServiceName": - if (!String.IsNullOrEmpty(serviceName)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); - } - - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - // Get the ServiceConfigFailureActions actions. - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Failure": - string action = null; - string delay = null; - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - - foreach (var childAttrib in child.Attributes()) - { - if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace) - { - switch (childAttrib.Name.LocalName) - { - case "Action": - action = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - switch (action) - { - case "none": - action = "0"; - break; - case "restartComputer": - action = "2"; - break; - case "restartService": - action = "1"; - break; - case "runCommand": - action = "3"; - break; - default: - // allow everything else to pass through that are hopefully "formatted" Properties. - break; - } - break; - case "Delay": - delay = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); - break; - default: - this.Core.UnexpectedAttribute(child, childAttrib); - break; - } - } - } - - if (String.IsNullOrEmpty(action)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action")); - } - - if (String.IsNullOrEmpty(delay)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay")); - } - - if (!String.IsNullOrEmpty(actions)) - { - actions = String.Concat(actions, "[~]"); - } - actions = String.Concat(actions, action); - - if (!String.IsNullOrEmpty(actionsDelays)) - { - actionsDelays = String.Concat(actionsDelays, "[~]"); - } - actionsDelays = String.Concat(actionsDelays, delay); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); - } - else if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (!install && !reinstall && !uninstall) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiServiceConfigFailureActionsSymbol(sourceLineNumbers, id) - { - Name = name, - OnInstall = install, - OnReinstall = reinstall, - OnUninstall = uninstall, - ResetPeriod = resetPeriod, - RebootMessage = rebootMessage, - Command = command, - Actions = actions, - DelayActions = actionsDelays, - ComponentRef = componentId, - }); - } - } - - /// - /// Parses a service control element. - /// - /// Element to parse. - /// Identifier of parent component. - private void ParseServiceControlElement(XElement node, string componentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string arguments = null; - Identifier id = null; - string name = null; - var installRemove = false; - var uninstallRemove = false; - var installStart = false; - var uninstallStart = false; - var installStop = false; - var uninstallStop = false; - bool? wait = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Remove": - var removeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (removeValue) - { - case "install": - installRemove = true; - break; - case "uninstall": - uninstallRemove = true; - break; - case "both": - installRemove = true; - uninstallRemove = true; - break; - case "": - break; - } - break; - case "Start": - var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (startValue) - { - case "install": - installStart = true; - break; - case "uninstall": - uninstallStart = true; - break; - case "both": - installStart = true; - uninstallStart = true; - break; - case "": - break; - } - break; - case "Stop": - var stopValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (stopValue) - { - case "install": - installStop = true; - break; - case "uninstall": - uninstallStop = true; - break; - case "both": - installStop = true; - uninstallStop = true; - break; - case "": - break; - } - break; - case "Wait": - wait = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - // get the ServiceControl arguments - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ServiceArgument": - arguments = this.ParseServiceArgument(child, arguments); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ServiceControlSymbol(sourceLineNumbers, id) - { - Name = name, - InstallRemove = installRemove, - UninstallRemove = uninstallRemove, - InstallStart = installStart, - UninstallStart = uninstallStart, - InstallStop = installStop, - UninstallStop = uninstallStop, - Arguments = arguments, - Wait = wait, - ComponentRef = componentId - }); - } - } - - private string ParseServiceArgument(XElement node, string arguments) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string argument = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Value": - argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - } - - if (argument == null) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - return (arguments == null) ? argument : String.Concat(arguments, "[~]", argument); - } - - /// - /// Parses a service dependency element. - /// - /// Element to parse. - /// Parsed sevice dependency name. - private string ParseServiceDependencyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string dependency = null; - var group = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Group": - group = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == dependency) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - return group ? String.Concat("+", dependency) : dependency; - } - - /// - /// Parses a service install element. - /// - /// Element to parse. - /// Identifier of parent component. - /// - private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string account = null; - string arguments = null; - string dependencies = null; - string description = null; - string displayName = null; - var eraseDescription = false; - string loadOrderGroup = null; - string name = null; - string password = null; - - var serviceType = ServiceType.OwnProcess; - var startType = ServiceStartType.Demand; - var errorControl = ServiceErrorControl.Normal; - var interactive = false; - var vital = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Account": - account = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Arguments": - arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "EraseDescription": - eraseDescription = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ErrorControl": - var errorControlValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (errorControlValue) - { - case "ignore": - errorControl = ServiceErrorControl.Ignore; - break; - case "normal": - errorControl = ServiceErrorControl.Normal; - break; - case "critical": - errorControl = ServiceErrorControl.Critical; - break; - case "": // error case handled by GetAttributeValue() - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical")); - break; - } - break; - case "Interactive": - interactive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "LoadOrderGroup": - loadOrderGroup = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Password": - password = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Start": - var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (startValue) - { - case "auto": - startType = ServiceStartType.Auto; - break; - case "demand": - startType = ServiceStartType.Demand; - break; - case "disabled": - startType = ServiceStartType.Disabled; - break; - case "boot": - case "system": - this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue)); - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled")); - break; - } - break; - case "Type": - var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (typeValue) - { - case "ownProcess": - serviceType = ServiceType.OwnProcess; - break; - case "shareProcess": - serviceType = ServiceType.ShareProcess; - break; - case "kernelDriver": - case "systemDriver": - this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue)); - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess")); - break; - } - break; - case "Vital": - vital = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else if (null == id) - { - id = this.Core.CreateIdentifierFromFilename(name); - } - - if (0 == startType) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start")); - } - - if (eraseDescription) - { - description = "[~]"; - } - - // get the ServiceInstall dependencies and config - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "PermissionEx": - this.ParsePermissionExElement(child, id.Id, "ServiceInstall"); - break; - case "ServiceConfig": - this.ParseServiceConfigElement(child, componentId, name); - break; - case "ServiceConfigFailureActions": - this.ParseServiceConfigFailureActionsElement(child, componentId, name); - break; - case "ServiceDependency": - dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "ServiceInstallId", id?.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - - if (null != dependencies) - { - dependencies = String.Concat(dependencies, "[~]"); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ServiceInstallSymbol(sourceLineNumbers, id) - { - Name = name, - DisplayName = displayName, - ServiceType = serviceType, - StartType = startType, - ErrorControl = errorControl, - LoadOrderGroup = loadOrderGroup, - Dependencies = dependencies, - StartName = account, - Password = password, - Arguments = arguments, - ComponentRef = componentId, - Description = description, - Interactive = interactive, - Vital = vital - }); - } - } - - /// - /// Parses a SetDirectory element. - /// - /// Element to parse. - private void ParseSetDirectoryElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string actionName = null; - string id = null; - string condition = null; - var executionType = CustomActionExecutionType.Immediate; - var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); - break; - case "Sequence": - var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (sequenceValue) - { - case "execute": - sequences = new[] { SequenceTable.InstallExecuteSequence }; - break; - case "first": - executionType = CustomActionExecutionType.FirstSequence; - break; - case "ui": - sequences = new[] { SequenceTable.InstallUISequence }; - break; - case "both": - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); - break; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (String.IsNullOrEmpty(actionName)) - { - actionName = String.Concat("Set", id); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName)) - { - ExecutionType = executionType, - SourceType = CustomActionSourceType.Directory, - TargetType = CustomActionTargetType.TextData, - Source = id, - Target = value - }); - - foreach (var sequence in sequences) - { - this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, afterAction: "CostInitialize"); - } - } - } - - /// - /// Parses a SetProperty element. - /// - /// Element to parse. - private void ParseSetPropertyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string actionName = null; - string id = null; - string condition = null; - string afterAction = null; - string beforeAction = null; - var executionType = CustomActionExecutionType.Immediate; - var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "After": - afterAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Before": - beforeAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Sequence": - var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (sequenceValue) - { - case "execute": - sequences = new[] { SequenceTable.InstallExecuteSequence }; - break; - case "first": - executionType = CustomActionExecutionType.FirstSequence; - break; - case "ui": - sequences = new[] { SequenceTable.InstallUISequence }; - break; - case "both": - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); - break; - } - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - else if (String.IsNullOrEmpty(actionName)) - { - actionName = String.Concat("Set", id); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - if (null != beforeAction && null != afterAction) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before")); - } - else if (null == beforeAction && null == afterAction) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id")); - } - - this.Core.ParseForExtensionElements(node); - - // add the row and any references needed - if (!this.Core.EncounteredError) - { - // action that is scheduled to occur before/after itself - if (beforeAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction)); - } - else if (afterAction == actionName) - { - this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction)); - } - - this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName)) - { - ExecutionType = executionType, - SourceType = CustomActionSourceType.Property, - TargetType = CustomActionTargetType.TextData, - Source = id, - Target = value, - }); - - foreach (var sequence in sequences) - { - this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, beforeAction, afterAction); - } - } - } - - /// - /// Parses a SFP catalog element. - /// - /// Element to parse. - /// Parent SFPCatalog. - private void ParseSFPFileElement(XElement node, string parentSFPCatalog) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new FileSFPCatalogSymbol(sourceLineNumbers) - { - FileRef = id, - SFPCatalogRef = parentSFPCatalog - }); - } - } - - /// - /// Parses a SFP catalog element. - /// - /// Element to parse. - /// Parent SFPCatalog. - private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string parentName = null; - string dependency = null; - string name = null; - string sourceFile = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Dependency": - dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - parentSFPCatalog = name; - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "SFPCatalog": - this.ParseSFPCatalogElement(child, ref parentName); - if (null != dependency && parentName == dependency) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); - } - dependency = parentName; - break; - case "SFPFile": - this.ParseSFPFileElement(child, name); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null == dependency) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new SFPCatalogSymbol(sourceLineNumbers) - { - SFPCatalog = name, - Catalog = sourceFile, - Dependency = dependency - }); - } - } - - /// - /// Parses a shortcut element. - /// - /// Element to parse. - /// Identifer for parent component. - /// Local name of parent element. - /// Default identifier of parent (which is usually the target). - /// Flag to indicate whether the parent element is the keypath of a component or not (will only be true for file parent elements). - private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var advertise = false; - string arguments = null; - string description = null; - string descriptionResourceDll = null; - int? descriptionResourceId = null; - string directoryId = null; - string subdirectory = null; - string displayResourceDll = null; - int? displayResourceId = null; - int? hotkey = null; - string icon = null; - int? iconIndex = null; - string name = null; - string shortName = null; - ShortcutShowType? show = null; - string target = null; - string workingDirectoryId = null; - string workingSubdirectory = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Advertise": - advertise = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Arguments": - arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DescriptionResourceDll": - descriptionResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DescriptionResourceId": - descriptionResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Directory": - directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); - break; - case "Subdirectory": - subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "DisplayResourceDll": - displayResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayResourceId": - displayResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Hotkey": - hotkey = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Icon": - icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); - break; - case "IconIndex": - iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "ShortName": - shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); - break; - case "Show": - var showValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (showValue) - { - case "normal": - show = ShortcutShowType.Normal; - break; - case "maximized": - show = ShortcutShowType.Maximized; - break; - case "minimized": - show = ShortcutShowType.Minimized; - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); - break; - } - break; - case "Target": - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "WorkingDirectory": - workingDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, workingDirectoryId); - break; - case "WorkingSubdirectory": - workingSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (advertise && null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes")); - } - - if (null == directoryId) - { - if ("Component" == parentElementLocalName) - { - directoryId = defaultTarget; - } - else - { - this.Core.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component")); - } - } - - directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); - - if (null != descriptionResourceDll) - { - if (!descriptionResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId")); - } - } - else - { - if (descriptionResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll")); - } - } - - if (null != displayResourceDll) - { - if (!displayResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId")); - } - } - else - { - if (displayResourceId.HasValue) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll")); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - - workingDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, workingDirectoryId, workingSubdirectory, "WorkingDirectory", "WorkingSubdirectory"); - - if ("Component" != parentElementLocalName && null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName)); - } - - if (null == id) - { - id = this.Core.CreateIdentifier("sct", directoryId, LowercaseOrNull(name)); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Icon": - icon = this.ParseIconElement(child); - break; - case "ShortcutProperty": - this.ParseShortcutPropertyElement(child, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - if (advertise) - { - if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName) - { - this.Core.Write(WarningMessages.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget)); - } - - target = Guid.Empty.ToString("B"); - } - else if (null != target) - { - } - else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) - { - target = "[" + defaultTarget + "]"; - } - else if ("File" == parentElementLocalName) - { - target = "[#" + defaultTarget + "]"; - } - - this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) - { - DirectoryRef = directoryId, - Name = name, - ShortName = shortName, - ComponentRef = componentId, - Target = target, - Arguments = arguments, - Description = description, - Hotkey = hotkey, - IconRef = icon, - IconIndex = iconIndex, - Show = show, - WorkingDirectory = workingDirectoryId, - DisplayResourceDll = displayResourceDll, - DisplayResourceId = displayResourceId, - DescriptionResourceDll = descriptionResourceDll, - DescriptionResourceId = descriptionResourceId, - }); - } - } - - /// - /// Parses a shortcut property element. - /// - /// Element to parse. - /// - private void ParseShortcutPropertyElement(XElement node, string shortcutId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string key = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Key": - key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (String.IsNullOrEmpty(key)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); - } - else if (null == id) - { - id = this.Core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant()); - } - - if (String.IsNullOrEmpty(value)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiShortcutPropertySymbol(sourceLineNumbers, id) - { - ShortcutRef = shortcutId, - PropertyKey = key, - PropVariantValue = value - }); - } - } - - /// - /// Parses a typelib element. - /// - /// Element to parse. - /// Identifier of parent component. - /// Identifier of file that acts as typelib server. - /// true if the component is 64-bit. - private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - var advertise = YesNoType.NotSet; - var cost = CompilerConstants.IntegerNotSet; - string description = null; - var flags = 0; - string helpDirectoryId = null; - string helpSubdirectory = null; - var language = CompilerConstants.IntegerNotSet; - var majorVersion = CompilerConstants.IntegerNotSet; - var minorVersion = CompilerConstants.IntegerNotSet; - var resourceId = CompilerConstants.LongNotSet; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Advertise": - advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Control": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 2; - } - break; - case "Cost": - cost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "HasDiskImage": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 8; - } - break; - case "HelpDirectory": - helpDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, helpDirectoryId); - break; - case "HelpSubdirectory": - helpSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); - break; - case "Hidden": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 4; - } - break; - case "Language": - language = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "MajorVersion": - majorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, UInt16.MaxValue); - break; - case "MinorVersion": - minorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); - break; - case "ResourceId": - resourceId = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, Int32.MinValue, Int32.MaxValue); - break; - case "Restricted": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - flags |= 1; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (CompilerConstants.IntegerNotSet == language) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); - language = CompilerConstants.IllegalInteger; - } - - helpDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, helpDirectoryId, helpSubdirectory, "HelpDirectory", "HelpSubdirectory"); - - // build up the typelib version string for the registry if the major or minor version was specified - string registryVersion = null; - if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) - { - if (CompilerConstants.IntegerNotSet != majorVersion) - { - registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat); - } - else - { - registryVersion = "0"; - } - - if (CompilerConstants.IntegerNotSet != minorVersion) - { - registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat)); - } - else - { - registryVersion = String.Concat(registryVersion, ".0"); - } - } - - // if the advertise state has not been set, default to non-advertised - if (YesNoType.NotSet == advertise) - { - advertise = YesNoType.No; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "AppId": - this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion); - break; - case "Class": - this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null); - break; - case "Interface": - this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (YesNoType.Yes == advertise) - { - if (CompilerConstants.LongNotSet != resourceId) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId")); - } - - if (0 != flags) - { - if (0x1 == (flags & 0x1)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes")); - } - - if (0x2 == (flags & 0x2)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes")); - } - - if (0x4 == (flags & 0x4)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes")); - } - - if (0x8 == (flags & 0x8)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes")); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new TypeLibSymbol(sourceLineNumbers) - { - LibId = id, - Language = language, - ComponentRef = componentId, - Description = description, - DirectoryRef = helpDirectoryId, - FeatureRef = Guid.Empty.ToString("B") - }); - - if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) - { - symbol.Version = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0); - } - - if (CompilerConstants.IntegerNotSet != cost) - { - symbol.Cost = cost; - } - } - } - else if (YesNoType.No == advertise) - { - if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no")); - } - - if (null == fileServer) - { - this.Core.Write(ErrorMessages.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File")); - } - - if (null == registryVersion) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no")); - } - - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description] - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId); - - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId] - var path = String.Concat("[#", fileServer, "]"); - if (CompilerConstants.LongNotSet != resourceId) - { - path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat)); - } - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId); - - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags] - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId); - - if (null != helpDirectoryId) - { - // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory] - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectoryId, "]"), componentId); - } - } - } - - /// - /// Parses an upgrade element. - /// - /// Element to parse. - private void ParseUpgradeElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - // process the UpgradeVersion children here - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - - switch (child.Name.LocalName) - { - case "Property": - this.ParsePropertyElement(child); - this.Core.Write(WarningMessages.DeprecatedUpgradeProperty(childSourceLineNumbers)); - break; - case "UpgradeVersion": - this.ParseUpgradeVersionElement(child, id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - // No rows created here. All row creation is done in ParseUpgradeVersionElement. - } - - /// - /// Parse upgrade version element. - /// - /// Element to parse. - /// Upgrade code. - private void ParseUpgradeVersionElement(XElement node, string upgradeId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - - string actionProperty = null; - string language = null; - string maximum = null; - string minimum = null; - var excludeLanguages = false; - var ignoreFailures = false; - var includeMax = false; - var includeMin = true; - var migrateFeatures = false; - var onlyDetect = false; - string removeFeatures = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "ExcludeLanguages": - excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IgnoreRemoveFailure": - ignoreFailures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IncludeMaximum": - includeMax = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "IncludeMinimum": // this is "yes" by default - includeMin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Language": - language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Minimum": - minimum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "Maximum": - maximum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "MigrateFeatures": - migrateFeatures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "OnlyDetect": - onlyDetect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Property": - actionProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "RemoveFeatures": - removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == actionProperty) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty) - { - this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty)); - } - - if (null == minimum && null == maximum) - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) - { - UpgradeCode = upgradeId, - VersionMin = minimum, - VersionMax = maximum, - Language = language, - ExcludeLanguages = excludeLanguages, - IgnoreRemoveFailures = ignoreFailures, - VersionMaxInclusive = includeMax, - VersionMinInclusive = includeMin, - MigrateFeatures = migrateFeatures, - OnlyDetect = onlyDetect, - Remove = removeFeatures, - ActionProperty = actionProperty - }); - - // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence - // if at least one row in Upgrade table lacks the OnlyDetect attribute. - if (!onlyDetect) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", "RemoveExistingProducts"); - } - } - } - - /// - /// Parses a verb element. - /// - /// Element to parse. - /// Extension verb is releated to. - /// Optional progId for extension. - /// Identifier for parent component. - /// Flag if verb is advertised. - private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - string argument = null; - string command = null; - var sequence = CompilerConstants.IntegerNotSet; - string targetFile = null; - string targetProperty = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Argument": - argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Command": - command = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Sequence": - sequence = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "TargetFile": - targetFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, targetFile); - break; - case "TargetProperty": - targetProperty = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null != targetFile && null != targetProperty) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty")); - } - - this.Core.ParseForExtensionElements(node); - - if (YesNoType.Yes == advertise) - { - if (null != targetFile) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile")); - } - - if (null != targetProperty) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty")); - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new VerbSymbol(sourceLineNumbers) - { - ExtensionRef = extension, - Verb = id, - Command = command, - Argument = argument, - }); - - if (CompilerConstants.IntegerNotSet != sequence) - { - symbol.Sequence = sequence; - } - } - } - else if (YesNoType.No == advertise) - { - if (CompilerConstants.IntegerNotSet != sequence) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no")); - } - - if (null == targetFile && null == targetProperty) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no")); - } - - string target = null; - if (null != targetFile) - { - target = String.Concat("\"[#", targetFile, "]\""); - } - else if (null != targetProperty) - { - target = String.Concat("\"[", targetProperty, "]\""); - } - - if (null != argument) - { - target = String.Concat(target, " ", argument); - } - - var prefix = progId ?? String.Concat(".", extension); - - if (null != command) - { - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId); - } - - this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId); - } - } - - /// - /// Parses a WixVariable element. - /// - /// Element to parse. - private void ParseWixVariableElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var overridable = false; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Overridable": - overridable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixVariableSymbol(sourceLineNumbers, id) - { - Value = value, - Overridable = overridable - }); - } - } - - private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var compressionLevel = this.Core.GetAttributeValue(sourceLineNumbers, attribute); - switch (compressionLevel) - { - case "high": - return CompressionLevel.High; - case "low": - return CompressionLevel.Low; - case "medium": - return CompressionLevel.Medium; - case "mszip": - return CompressionLevel.Mszip; - case "none": - return CompressionLevel.None; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalCompressionLevel(sourceLineNumbers, compressionLevel)); - break; - } - - return null; - } - } -} diff --git a/src/WixToolset.Core/Compiler_Patch.cs b/src/WixToolset.Core/Compiler_Patch.cs deleted file mode 100644 index c9cae183..00000000 --- a/src/WixToolset.Core/Compiler_Patch.cs +++ /dev/null @@ -1,657 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses an patch element. - /// - /// The element to parse. - private void ParsePatchElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string patchId = null; - string codepage = null; - ////bool versionMismatches = false; - ////bool productMismatches = false; - var allowRemoval = false; - string classification = null; - string clientPatchId = null; - string description = null; - string displayName = null; - string comments = null; - string manufacturer = null; - var minorUpdateTargetRTM = YesNoType.NotSet; - string moreInfoUrl = null; - var optimizeCA = CompilerConstants.IntegerNotSet; - var optimizedInstallMode = YesNoType.NotSet; - string targetProductName = null; - // string replaceGuids = String.Empty; - var apiPatchingSymbolFlags = 0; - var optimizePatchSizeForLargeFiles = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - patchId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); - break; - case "Codepage": - codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); - break; - case "AllowMajorVersionMismatches": - ////versionMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "AllowProductCodeMismatches": - ////productMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "AllowRemoval": - allowRemoval = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - case "Classification": - classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ClientPatchId": - clientPatchId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Comments": - comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Manufacturer": - manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MinorUpdateTargetRTM": - minorUpdateTargetRTM = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "MoreInfoURL": - moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "OptimizedInstallMode": - optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "TargetProductName": - targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ApiPatchingSymbolNoImagehlpFlag": - apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoImagehlp : 0; - break; - case "ApiPatchingSymbolNoFailuresFlag": - apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoFailures : 0; - break; - case "ApiPatchingSymbolUndecoratedTooFlag": - apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolUndecoratedToo : 0; - break; - case "OptimizePatchSizeForLargeFiles": - optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (patchId == null || patchId == "*") - { - // auto-generate at compile time, since this value gets dispersed to several locations - patchId = Common.GenerateGuid(); - } - this.activeName = patchId; - - if (null == this.activeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - if (null == classification) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); - } - if (null == clientPatchId) - { - clientPatchId = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); - } - if (null == description) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); - } - if (null == displayName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); - } - if (null == manufacturer) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); - } - - this.Core.CreateActiveSection(this.activeName, SectionType.Patch, this.Context.CompilationId); - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "PatchInformation": - this.ParsePatchInformationElement(child); - break; - case "Media": - this.ParseMediaElement(child, patchId); - break; - case "OptimizeCustomActions": - optimizeCA = this.ParseOptimizeCustomActionsElement(child); - break; - case "PatchFamily": - this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Patch, patchId); - break; - case "PatchFamilyRef": - this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.Patch, patchId); - break; - case "PatchFamilyGroup": - this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Patch, patchId); - break; - case "PatchFamilyGroupRef": - this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Patch, patchId); - break; - case "PatchProperty": - this.ParsePatchPropertyElement(child, true); - break; - case "TargetProductCodes": - this.ParseTargetProductCodesElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPatchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, patchId)) - { - Codepage = codepage, - ClientPatchId = clientPatchId, - OptimizePatchSizeForLargeFiles = optimizePatchSizeForLargeFiles, - ApiPatchingSymbolFlags = apiPatchingSymbolFlags, - }); - - if (allowRemoval) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "AllowRemoval", allowRemoval ? "1" : "0"); - } - - if (null != classification) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "Classification", classification); - } - - // always generate the CreationTimeUTC - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", DateTime.UtcNow.ToString("MM-dd-yy HH:mm", CultureInfo.InvariantCulture)); - } - - if (null != description) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "Description", description); - } - - if (null != displayName) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); - } - - if (null != manufacturer) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturer); - } - - if (YesNoType.NotSet != minorUpdateTargetRTM) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", YesNoType.Yes == minorUpdateTargetRTM ? "1" : "0"); - } - - if (null != moreInfoUrl) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); - } - - if (CompilerConstants.IntegerNotSet != optimizeCA) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); - } - - if (YesNoType.NotSet != optimizedInstallMode) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); - } - - if (null != targetProductName) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); - } - - if (null != comments) - { - this.AddMsiPatchMetadata(sourceLineNumbers, null, "Comments", comments); - } - } - // TODO: do something with versionMismatches and productMismatches - } - - /// - /// Parses the OptimizeCustomActions element. - /// - /// Element to parse. - /// The combined integer value for callers to store as appropriate. - private int ParseOptimizeCustomActionsElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var optimizeCA = OptimizeCAFlags.None; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "SkipAssignment": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - optimizeCA |= OptimizeCAFlags.SkipAssignment; - } - break; - case "SkipImmediate": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - optimizeCA |= OptimizeCAFlags.SkipImmediate; - } - break; - case "SkipDeferred": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - optimizeCA |= OptimizeCAFlags.SkipDeferred; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - return (int)optimizeCA; - } - - /// - /// Parses a PatchFamily element. - /// - /// The element to parse. - /// - /// - private void ParsePatchFamilyElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string productCode = null; - string version = null; - var attributes = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "ProductCode": - productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Version": - version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "Supersede": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= 0x1; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - if (String.IsNullOrEmpty(version)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); - } - else if (!CompilerCore.IsValidProductVersion(version)) - { - this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); - } - - // find unexpected child elements - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "All": - this.ParseAllElement(child); - break; - case "BinaryRef": - this.ParsePatchChildRefElement(child, "Binary"); - break; - case "ComponentRef": - this.ParsePatchChildRefElement(child, "Component"); - break; - case "CustomActionRef": - this.ParsePatchChildRefElement(child, "CustomAction"); - break; - case "DirectoryRef": - this.ParsePatchChildRefElement(child, "Directory"); - break; - case "DigitalCertificateRef": - this.ParsePatchChildRefElement(child, "MsiDigitalCertificate"); - break; - case "FeatureRef": - this.ParsePatchChildRefElement(child, "Feature"); - break; - case "IconRef": - this.ParsePatchChildRefElement(child, "Icon"); - break; - case "PropertyRef": - this.ParsePatchChildRefElement(child, "Property"); - break; - case "SoftwareTagRef": - this.ParseTagRefElement(child); - break; - case "UIRef": - this.ParsePatchChildRefElement(child, "WixUI"); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new MsiPatchSequenceSymbol(sourceLineNumbers) - { - PatchFamily = id.Id, - ProductCode = productCode, - Sequence = version, - Attributes = attributes - }); - - if (ComplexReferenceParentType.Unknown != parentType) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, id.Id, ComplexReferenceParentType.Patch == parentType); - } - } - } - - /// - /// Parses a PatchFamilyGroup element. - /// - /// Element to parse. - /// - /// - private void ParsePatchFamilyGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "PatchFamily": - this.ParsePatchFamilyElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); - break; - case "PatchFamilyRef": - this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); - break; - case "PatchFamilyGroupRef": - this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixPatchFamilyGroupSymbol(sourceLineNumbers, id)); - - //Add this PatchFamilyGroup and its parent in WixGroup. - this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PatchFamilyGroup, id.Id); - } - } - - /// - /// Parses a PatchFamilyGroup reference element. - /// - /// Element to parse. - /// The type of parent. - /// Identifier of parent element. - private void ParsePatchFamilyGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) - { - Debug.Assert(ComplexReferenceParentType.PatchFamilyGroup == parentType || ComplexReferenceParentType.Patch == parentType); - - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string id = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixPatchFamilyGroup, id); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamilyGroup, id, true); - } - } - - /// - /// Parses a TargetProductCodes element. - /// - /// The element to parse. - private void ParseTargetProductCodesElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var replace = false; - var targetProductCodes = new List(); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Replace": - replace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "TargetProductCode": - var id = this.ParseTargetProductCodeElement(child); - if (0 == String.CompareOrdinal("*", id)) - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWhenNested(sourceLineNumbers, child.Name.LocalName, "Id", id, node.Name.LocalName)); - } - else - { - targetProductCodes.Add(id); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - // By default, target ProductCodes should be added. - if (!replace) - { - this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers) - { - ProductCode = "*" - }); - } - - foreach (var targetProductCode in targetProductCodes) - { - this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers) - { - ProductCode = targetProductCode - }); - } - } - } - - private void AddMsiPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) - { - this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property)) - { - Company = company, - Property = property, - Value = value - }); - } - } -} diff --git a/src/WixToolset.Core/Compiler_PatchCreation.cs b/src/WixToolset.Core/Compiler_PatchCreation.cs deleted file mode 100644 index 81ae4121..00000000 --- a/src/WixToolset.Core/Compiler_PatchCreation.cs +++ /dev/null @@ -1,1265 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses a patch creation element. - /// - /// The element to parse. - private void ParsePatchCreationElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var clean = true; // Default is to clean - var codepage = 0; - string outputPath = null; - var productMismatches = false; - var replaceGuids = String.Empty; - string sourceList = null; - string symbolFlags = null; - var targetProducts = String.Empty; - var versionMismatches = false; - var wholeFiles = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - this.activeName = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "AllowMajorVersionMismatches": - versionMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "AllowProductCodeMismatches": - productMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "CleanWorkingFolder": - clean = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Codepage": - codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); - break; - case "OutputPath": - outputPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SourceList": - sourceList = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SymbolFlags": - symbolFlags = String.Format(CultureInfo.InvariantCulture, "0x{0:x8}", this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, 0, UInt32.MaxValue)); - break; - case "WholeFilesOnly": - wholeFiles = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == this.activeName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - this.Core.CreateActiveSection(this.activeName, SectionType.PatchCreation, this.Context.CompilationId); - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Family": - this.ParseFamilyElement(child); - break; - case "PatchInformation": - this.ParsePatchInformationElement(child); - break; - case "PatchMetadata": - this.ParsePatchMetadataElement(child); - break; - case "PatchProperty": - this.ParsePatchPropertyElement(child, false); - break; - case "PatchSequence": - this.ParsePatchSequenceElement(child); - break; - case "ReplacePatch": - replaceGuids = String.Concat(replaceGuids, this.ParseReplacePatchElement(child)); - break; - case "TargetProductCode": - var targetProduct = this.ParseTargetProductCodeElement(child); - if (0 < targetProducts.Length) - { - targetProducts = String.Concat(targetProducts, ";"); - } - targetProducts = String.Concat(targetProducts, targetProduct); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - this.AddPrivateProperty(sourceLineNumbers, "PatchGUID", this.activeName); - this.AddPrivateProperty(sourceLineNumbers, "AllowProductCodeMismatches", productMismatches ? "1" : "0"); - this.AddPrivateProperty(sourceLineNumbers, "AllowProductVersionMajorMismatches", versionMismatches ? "1" : "0"); - this.AddPrivateProperty(sourceLineNumbers, "DontRemoveTempFolderWhenFinished", clean ? "0" : "1"); - this.AddPrivateProperty(sourceLineNumbers, "IncludeWholeFilesOnly", wholeFiles ? "1" : "0"); - - if (null != symbolFlags) - { - this.AddPrivateProperty(sourceLineNumbers, "ApiPatchingSymbolFlags", symbolFlags); - } - - if (0 < replaceGuids.Length) - { - this.AddPrivateProperty(sourceLineNumbers, "ListOfPatchGUIDsToReplace", replaceGuids); - } - - if (0 < targetProducts.Length) - { - this.AddPrivateProperty(sourceLineNumbers, "ListOfTargetProductCodes", targetProducts); - } - - if (null != outputPath) - { - this.AddPrivateProperty(sourceLineNumbers, "PatchOutputPath", outputPath); - } - - if (null != sourceList) - { - this.AddPrivateProperty(sourceLineNumbers, "PatchSourceList", sourceList); - } - } - - /// - /// Parses a family element. - /// - /// The element to parse. - private void ParseFamilyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var diskId = CompilerConstants.IntegerNotSet; - string diskPrompt = null; - string mediaSrcProp = null; - string name = null; - var sequenceStart = CompilerConstants.IntegerNotSet; - string volumeLabel = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "DiskId": - diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); - break; - case "DiskPrompt": - diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MediaSrcProp": - mediaSrcProp = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SequenceStart": - sequenceStart = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); - break; - case "VolumeLabel": - volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == name) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - else if (0 < name.Length) - { - if (8 < name.Length) // check the length - { - this.Core.Write(ErrorMessages.FamilyNameTooLong(sourceLineNumbers, node.Name.LocalName, "Name", name, name.Length)); - } - else // check for illegal characters - { - foreach (var character in name) - { - if (!Char.IsLetterOrDigit(character) && '_' != character) - { - this.Core.Write(ErrorMessages.IllegalFamilyName(sourceLineNumbers, node.Name.LocalName, "Name", name)); - } - } - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "UpgradeImage": - this.ParseUpgradeImageElement(child, name); - break; - case "ExternalFile": - this.ParseExternalFileElement(child, name); - break; - case "ProtectFile": - this.ParseProtectFileElement(child, name); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ImageFamiliesSymbol(sourceLineNumbers) - { - Family = name, - MediaSrcPropName = mediaSrcProp, - DiskPrompt = diskPrompt, - VolumeLabel = volumeLabel - }); - - if (CompilerConstants.IntegerNotSet != diskId) - { - symbol.MediaDiskId = diskId; - } - - if (CompilerConstants.IntegerNotSet != sequenceStart) - { - symbol.FileSequenceStart = sequenceStart; - } - } - } - - /// - /// Parses an upgrade image element. - /// - /// The element to parse. - /// The family for this element. - private void ParseUpgradeImageElement(XElement node, string family) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string sourceFile = null; - string sourcePatch = null; - var symbols = new List(); - string upgrade = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - upgrade = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (13 < upgrade.Length) - { - this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", upgrade, 13)); - } - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SourcePatch": - sourcePatch = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == upgrade) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "SymbolPath": - symbols.Add(this.ParseSymbolPathElement(child)); - break; - case "TargetImage": - this.ParseTargetImageElement(child, upgrade, family); - break; - case "UpgradeFile": - this.ParseUpgradeFileElement(child, upgrade); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new UpgradedImagesSymbol(sourceLineNumbers) - { - Upgraded = upgrade, - MsiPath = sourceFile, - PatchMsiPath = sourcePatch, - SymbolPaths = String.Join(";", symbols), - Family = family - }); - } - } - - /// - /// Parses an upgrade file element. - /// - /// The element to parse. - /// The upgrade key for this element. - private void ParseUpgradeFileElement(XElement node, string upgrade) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var allowIgnoreOnError = false; - string file = null; - var ignore = false; - var symbols = new List(); - var wholeFile = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AllowIgnoreOnError": - allowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "File": - file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Ignore": - ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "WholeFile": - wholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == file) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "SymbolPath": - symbols.Add(this.ParseSymbolPathElement(child)); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - if (ignore) - { - this.Core.AddSymbol(new UpgradedFilesToIgnoreSymbol(sourceLineNumbers) - { - Upgraded = upgrade, - FTK = file - }); - } - else - { - this.Core.AddSymbol(new UpgradedFilesOptionalDataSymbol(sourceLineNumbers) - { - Upgraded = upgrade, - FTK = file, - SymbolPaths = String.Join(";", symbols), - AllowIgnoreOnPatchError = allowIgnoreOnError, - IncludeWholeFile = wholeFile - }); - } - } - } - - /// - /// Parses a target image element. - /// - /// The element to parse. - /// The upgrade key for this element. - /// The family key for this element. - private void ParseTargetImageElement(XElement node, string upgrade, string family) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var ignore = false; - var order = CompilerConstants.IntegerNotSet; - string sourceFile = null; - string symbols = null; - string target = null; - string validation = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (target.Length > 13) - { - this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", target, 13)); - } - break; - case "IgnoreMissingFiles": - ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Order": - order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Validation": - validation = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == target) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - if (null == sourceFile) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); - } - - if (CompilerConstants.IntegerNotSet == order) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "SymbolPath": - if (null != symbols) - { - symbols = String.Concat(symbols, ";", this.ParseSymbolPathElement(child)); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - case "TargetFile": - this.ParseTargetFileElement(child, target, family); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new TargetImagesSymbol(sourceLineNumbers) - { - Target = target, - MsiPath = sourceFile, - SymbolPaths = symbols, - Upgraded = upgrade, - Order = order, - ProductValidateFlags = validation, - IgnoreMissingSrcFiles = ignore - }); - } - } - - /// - /// Parses an upgrade file element. - /// - /// The element to parse. - /// The upgrade key for this element. - /// The family key for this element. - private void ParseTargetFileElement(XElement node, string target, string family) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string file = null; - string ignoreLengths = null; - string ignoreOffsets = null; - string protectLengths = null; - string protectOffsets = null; - string symbols = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == file) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "IgnoreRange": - this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); - break; - case "ProtectRange": - this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); - break; - case "SymbolPath": - symbols = this.ParseSymbolPathElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new TargetFilesOptionalDataSymbol(sourceLineNumbers) - { - Target = target, - FTK = file, - SymbolPaths = symbols, - IgnoreOffsets = ignoreOffsets, - IgnoreLengths = ignoreLengths, - }); - - if (null != protectOffsets) - { - symbol.RetainOffsets = protectOffsets; - - this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) - { - Family = family, - FTK = file, - RetainOffsets = protectOffsets, - RetainLengths = protectLengths, - }); - } - } - } - - /// - /// Parses an external file element. - /// - /// The element to parse. - /// The family for this element. - private void ParseExternalFileElement(XElement node, string family) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string file = null; - string ignoreLengths = null; - string ignoreOffsets = null; - var order = CompilerConstants.IntegerNotSet; - string protectLengths = null; - string protectOffsets = null; - string source = null; - string symbols = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "File": - file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Order": - order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); - break; - case "Source": - source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == file) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); - } - - if (null == source) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Source")); - } - - if (CompilerConstants.IntegerNotSet == order) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "IgnoreRange": - this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); - break; - case "ProtectRange": - this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); - break; - case "SymbolPath": - symbols = this.ParseSymbolPathElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new ExternalFilesSymbol(sourceLineNumbers) - { - Family = family, - FTK = file, - FilePath = source, - SymbolPaths = symbols, - IgnoreOffsets = ignoreOffsets, - IgnoreLengths = ignoreLengths, - }); - - if (null != protectOffsets) - { - symbol.RetainOffsets = protectOffsets; - } - - if (CompilerConstants.IntegerNotSet != order) - { - symbol.Order = order; - } - - if (null != protectOffsets) - { - this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) - { - Family = family, - FTK = file, - RetainOffsets = protectOffsets, - RetainLengths = protectLengths, - }); - } - } - } - - /// - /// Parses a protect file element. - /// - /// The element to parse. - /// The family for this element. - private void ParseProtectFileElement(XElement node, string family) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string file = null; - string protectLengths = null; - string protectOffsets = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "File": - file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == file) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "ProtectRange": - this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null == protectOffsets || null == protectLengths) - { - this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "ProtectRange")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) - { - Family = family, - FTK = file, - RetainOffsets = protectOffsets, - RetainLengths = protectLengths - }); - } - } - - /// - /// Parses a range element (ProtectRange, IgnoreRange, etc). - /// - /// The element to parse. - /// Reference to the offsets string. - /// Reference to the lengths string. - private void ParseRangeElement(XElement node, ref string offsets, ref string lengths) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string length = null; - string offset = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Length": - length = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Offset": - offset = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == length) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Length")); - } - - if (null == offset) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); - } - - this.Core.ParseForExtensionElements(node); - - if (null != lengths) - { - lengths = String.Concat(lengths, ",", length); - } - else - { - lengths = length; - } - - if (null != offsets) - { - offsets = String.Concat(offsets, ",", offset); - } - else - { - offsets = offset; - } - } - - /// - /// Parses a patch metadata element. - /// - /// Element to parse. - private void ParsePatchMetadataElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var allowRemoval = YesNoType.NotSet; - string classification = null; - string creationTimeUtc = null; - string description = null; - string displayName = null; - string manufacturerName = null; - string minorUpdateTargetRTM = null; - string moreInfoUrl = null; - var optimizeCA = CompilerConstants.IntegerNotSet; - var optimizedInstallMode = YesNoType.NotSet; - string targetProductName = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "AllowRemoval": - allowRemoval = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Classification": - classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "CreationTimeUTC": - creationTimeUtc = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ManufacturerName": - manufacturerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MinorUpdateTargetRTM": - minorUpdateTargetRTM = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "MoreInfoURL": - moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "OptimizedInstallMode": - optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "TargetProductName": - targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (YesNoType.NotSet == allowRemoval) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AllowRemoval")); - } - - if (null == classification) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); - } - - if (null == description) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); - } - - if (null == displayName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); - } - - if (null == manufacturerName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ManufacturerName")); - } - - if (null == moreInfoUrl) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "MoreInfoURL")); - } - - if (null == targetProductName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "TargetProductName")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "CustomProperty": - this.ParseCustomPropertyElement(child); - break; - case "OptimizeCustomActions": - optimizeCA = this.ParseOptimizeCustomActionsElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (!this.Core.EncounteredError) - { - if (YesNoType.NotSet != allowRemoval) - { - this.AddPatchMetadata(sourceLineNumbers, null, "AllowRemoval", YesNoType.Yes == allowRemoval ? "1" : "0"); - } - - if (null != classification) - { - this.AddPatchMetadata(sourceLineNumbers, null, "Classification", classification); - } - - if (null != creationTimeUtc) - { - this.AddPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", creationTimeUtc); - } - - if (null != description) - { - this.AddPatchMetadata(sourceLineNumbers, null, "Description", description); - } - - if (null != displayName) - { - this.AddPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); - } - - if (null != manufacturerName) - { - this.AddPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturerName); - } - - if (null != minorUpdateTargetRTM) - { - this.AddPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", minorUpdateTargetRTM); - } - - if (null != moreInfoUrl) - { - this.AddPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); - } - - if (CompilerConstants.IntegerNotSet != optimizeCA) - { - this.AddPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); - } - - if (YesNoType.NotSet != optimizedInstallMode) - { - this.AddPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); - } - - if (null != targetProductName) - { - this.AddPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); - } - } - } - - /// - /// Parses a custom property element for the PatchMetadata table. - /// - /// Element to parse. - private void ParseCustomPropertyElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string company = null; - string property = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Company": - company = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Property": - property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == company) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); - } - - if (null == property) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.AddPatchMetadata(sourceLineNumbers, company, property, value); - } - } - - /// - /// Parses a patch sequence element. - /// - /// The element to parse. - private void ParsePatchSequenceElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string family = null; - string target = null; - string sequence = null; - var attributes = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "PatchFamily": - family = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "ProductCode": - if (null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "TargetImage")); - } - target = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); - break; - case "Target": - if (null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "TargetImage", "ProductCode")); - } - this.Core.Write(WarningMessages.DeprecatedPatchSequenceTargetAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "TargetImage": - if (null != target) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "ProductCode")); - } - target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.TargetImages, target); - break; - case "Sequence": - sequence = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); - break; - case "Supersede": - if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) - { - attributes |= 0x1; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == family) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "PatchFamily")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new PatchSequenceSymbol(sourceLineNumbers) - { - PatchFamily = family, - Target = target, - Sequence = sequence, - Supersede = attributes, - }); - } - } - - private void AddPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) - { - this.Core.AddSymbol(new PatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property)) - { - Company = company, - Property = property, - Value = value, - }); - } - } -} diff --git a/src/WixToolset.Core/Compiler_Tag.cs b/src/WixToolset.Core/Compiler_Tag.cs deleted file mode 100644 index cf55c448..00000000 --- a/src/WixToolset.Core/Compiler_Tag.cs +++ /dev/null @@ -1,315 +0,0 @@ -// 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 -{ - using System; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - /// - /// Parses a Tag element for Software Id Tag registration under a Bundle element. - /// - /// The element to parse. - private void ParseBundleTagElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string name = null; - string regid = null; - string installPath = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "Regid": - regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "InstallDirectory": - case "Bitness": - this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Package")); - break; - case "InstallPath": - installPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (String.IsNullOrEmpty(name)) - { - name = node.Parent?.Attribute("Name")?.Value; - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - } - - if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) - { - this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); - } - - if (String.IsNullOrEmpty(regid)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); - } - else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); - } - - if (String.IsNullOrEmpty(installPath)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPath")); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixBundleTagSymbol(sourceLineNumbers) - { - Filename = String.Concat(name, ".swidtag"), - Regid = regid, - Name = name, - InstallPath = installPath - }); - } - } - - /// - /// Parses a Tag element for Software Id Tag registration under a Package element. - /// - /// The element to parse. - private void ParsePackageTagElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string name = null; - string regid = null; - string feature = null; - string installDirectory = null; - var win64 = this.Context.IsCurrentPlatform64Bit; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); - break; - case "Regid": - regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Feature": - feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "InstallDirectory": - installDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "InstallPath": - this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bundle")); - break; - case "Bitness": - var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - switch (bitnessValue) - { - case "always32": - win64 = false; - break; - case "always64": - win64 = true; - break; - case "default": - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); - break; - } - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (String.IsNullOrEmpty(name)) - { - name = node.Parent?.Attribute("Name")?.Value; - - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); - } - } - - if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) - { - this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); - } - - if (String.IsNullOrEmpty(regid)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); - } - else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); - return; - } - else if (id == null) - { - id = this.CreateTagId(regid); - } - - if (String.IsNullOrEmpty(installDirectory)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallDirectory")); - } - - if (!this.Core.EncounteredError) - { - var fileName = String.Concat(name, ".swidtag"); - - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, installDirectory); - this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) - { - Name = "swidtag", - ParentDirectoryRef = installDirectory, - ComponentGuidGenerationSeed = "4BAD0C8B-3AF0-BFE3-CC83-094749A1C4B1" - }); - - this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) - { - ComponentId = "*", - DirectoryRef = id.Id, - KeyPath = id.Id, - KeyPathType = ComponentKeyPathType.File, - Location = ComponentLocation.LocalOnly, - Win64 = win64 - }); - - this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) - { - ComponentRef = id.Id, - Name = fileName, - DiskId = 1, - Attributes = FileSymbolAttributes.ReadOnly, - }); - - if (!String.IsNullOrEmpty(feature)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); - } - else - { - feature = "WixSwidTag"; - this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, feature)) - { - Title = "ISO/IEC 19770-2", - Level = 1, - InstallDefault = FeatureInstallDefault.Local, - Display = 0, - DisallowAdvertise = true, - DisallowAbsent = true, - }); - } - this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); - - this.Core.EnsureTable(sourceLineNumbers, "SoftwareIdentificationTag"); - this.Core.AddSymbol(new WixProductTagSymbol(sourceLineNumbers, id) - { - FileRef = id.Id, - Regid = regid, - Name = name - }); - } - } - - /// - /// Parses a TagRef element for Software Id Tag registration under a PatchFamily element. - /// - /// The element to parse. - private void ParseTagRefElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string regid = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Regid": - regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (String.IsNullOrEmpty(regid)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); - } - else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) - { - this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); - } - - if (!this.Core.EncounteredError) - { - var id = this.CreateTagId(regid); - - this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers, id) - { - Table = SymbolDefinitions.Component.Name, - PrimaryKeys = id.Id - }); - } - } - - private Identifier CreateTagId(string regid) => this.Core.CreateIdentifier("tag", regid, ".product.tag"); - } -} diff --git a/src/WixToolset.Core/Compiler_UI.cs b/src/WixToolset.Core/Compiler_UI.cs deleted file mode 100644 index d712ec91..00000000 --- a/src/WixToolset.Core/Compiler_UI.cs +++ /dev/null @@ -1,1808 +0,0 @@ -// 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 -{ - using System; - using System.Collections; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - - /// - /// Compiler of the WiX toolset. - /// - internal partial class Compiler : ICompiler - { - // NameToBit arrays - private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; - private static readonly string[] HyperlinkControlAttributes = { "Transparent" }; - private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; - private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; - private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; - private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; - private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; - private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; - private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; - private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; - private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; - private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; - private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; - - /// - /// Parses UI elements. - /// - /// Element to parse. - private void ParseUIElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var embeddedUICount = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "BillboardAction": - this.ParseBillboardActionElement(child); - break; - case "ComboBox": - this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem"); - break; - case "Dialog": - this.ParseDialogElement(child); - break; - case "DialogRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Dialog); - break; - case "EmbeddedUI": - if (0 < embeddedUICount) // there can be only one embedded UI - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); - } - this.ParseEmbeddedUIElement(child); - ++embeddedUICount; - break; - case "Error": - this.ParseErrorElement(child); - break; - case "ListBox": - this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem"); - break; - case "ListView": - this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem"); - break; - case "ProgressText": - this.ParseActionTextElement(child); - break; - case "Publish": - var order = 0; - this.ParsePublishElement(child, null, null, ref order); - break; - case "RadioButtonGroup": - var radioButtonType = this.ParseRadioButtonGroupElement(child, null, RadioButtonType.NotSet); - if (RadioButtonType.Bitmap == radioButtonType || RadioButtonType.Icon == radioButtonType) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.RadioButtonBitmapAndIconDisallowed(childSourceLineNumbers)); - } - break; - case "TextStyle": - this.ParseTextStyleElement(child); - break; - case "UIText": - this.ParseUITextElement(child); - break; - - // the following are available indentically under the UI and Product elements for document organization use only - case "AdminUISequence": - this.ParseSequenceElement(child, SequenceTable.AdminUISequence); - break; - case "InstallUISequence": - this.ParseSequenceElement(child, SequenceTable.InstallUISequence); - break; - case "Binary": - this.ParseBinaryElement(child); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "PropertyRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.Property); - break; - case "UIRef": - this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); - break; - - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null != id && !this.Core.EncounteredError) - { - this.Core.AddSymbol(new WixUISymbol(sourceLineNumbers, id)); - } - } - - /// - /// Parses a list item element. - /// - /// Element to parse. - /// Type of symbol to create. - /// Identifier of property referred to by list item. - /// Relative order of list items. - private void ParseListItemElement(XElement node, SymbolDefinitionType symbolType, string property, ref int order) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string icon = null; - string text = null; - string value = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Icon": - if (SymbolDefinitionType.ListView == symbolType) - { - icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, icon); - } - else - { - this.Core.Write(ErrorMessages.IllegalAttributeExceptOnElement(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ListView")); - } - break; - case "Text": - text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - switch (symbolType) - { - case SymbolDefinitionType.ComboBox: - this.Core.AddSymbol(new ComboBoxSymbol(sourceLineNumbers) - { - Property = property, - Order = ++order, - Value = value, - Text = text, - }); - break; - case SymbolDefinitionType.ListBox: - this.Core.AddSymbol(new ListBoxSymbol(sourceLineNumbers) - { - Property = property, - Order = ++order, - Value = value, - Text = text, - }); - break; - case SymbolDefinitionType.ListView: - var symbol = this.Core.AddSymbol(new ListViewSymbol(sourceLineNumbers) - { - Property = property, - Order = ++order, - Value = value, - Text = text, - }); - - if (null != icon) - { - symbol.BinaryRef = icon; - } - break; - default: - throw new ArgumentOutOfRangeException(nameof(symbolType)); - } - } - } - - /// - /// Parses a radio button element. - /// - /// Element to parse. - /// Identifier of property referred to by radio button. - /// Relative order of radio buttons. - /// Type of this radio button. - private RadioButtonType ParseRadioButtonElement(XElement node, string property, ref int order) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var type = RadioButtonType.NotSet; - string value = null; - string x = null; - string y = null; - string width = null; - string height = null; - string text = null; - string tooltip = null; - string help = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Bitmap": - if (RadioButtonType.NotSet != type) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Icon", "Text")); - } - text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); - type = RadioButtonType.Bitmap; - break; - case "Height": - height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Help": - help = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Icon": - if (RadioButtonType.NotSet != type) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Text")); - } - text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); - type = RadioButtonType.Icon; - break; - case "Text": - if (RadioButtonType.NotSet != type) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Icon")); - } - text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - type = RadioButtonType.Text; - break; - case "ToolTip": - tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Value": - value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Width": - width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "X": - x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Y": - y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == value) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); - } - - if (null == x) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); - } - - if (null == y) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); - } - - if (null == width) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); - } - - if (null == height) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - var symbol = this.Core.AddSymbol(new RadioButtonSymbol(sourceLineNumbers) - { - Property = property, - Order = ++order, - Value = value, - Text = text, - Help = (null != tooltip || null != help) ? String.Concat(tooltip, "|", help) : null - }); - - symbol.Set((int)RadioButtonSymbolFields.X, x); - symbol.Set((int)RadioButtonSymbolFields.Y, y); - symbol.Set((int)RadioButtonSymbolFields.Width, width); - symbol.Set((int)RadioButtonSymbolFields.Height, height); - } - - return type; - } - - /// - /// Parses a billboard element. - /// - /// Element to parse. - private void ParseBillboardActionElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string action = null; - var order = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - action = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", action); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == action) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Billboard": - order = order + 1; - this.ParseBillboardElement(child, action, order); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - } - - /// - /// Parses a billboard element. - /// - /// Element to parse. - /// Action for the billboard. - /// Order of the billboard. - private void ParseBillboardElement(XElement node, string action, int order) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string feature = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Feature": - feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("bil", action, order.ToString(), feature); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Control": - // These are all thrown away. - ControlSymbol lastTabSymbol = null; - string firstControl = null; - string defaultControl = null; - string cancelControl = null; - - this.ParseControlElement(child, id.Id, SymbolDefinitionType.BBControl, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new BillboardSymbol(sourceLineNumbers, id) - { - FeatureRef = feature, - Action = action, - Ordering = order - }); - } - } - - /// - /// Parses a control group element. - /// - /// Element to parse. - /// Symbol type referred to by control group. - /// Expected child elements. - private void ParseControlGroupElement(XElement node, SymbolDefinitionType symbolType, string childTag) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var order = 0; - string property = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Property": - property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == property) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - if (childTag != child.Name.LocalName) - { - this.Core.UnexpectedElement(node, child); - } - - switch (child.Name.LocalName) - { - case "ListItem": - this.ParseListItemElement(child, symbolType, property, ref order); - break; - case "Property": - this.ParsePropertyElement(child); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - } - - /// - /// Parses a radio button control group element. - /// - /// Element to parse. - /// Property associated with this radio button group. - /// Specifies the current type of radio buttons in the group. - /// The current type of radio buttons in the group. - private RadioButtonType ParseRadioButtonGroupElement(XElement node, string property, RadioButtonType groupType) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var order = 0; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Property": - property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == property) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "RadioButton": - var type = this.ParseRadioButtonElement(child, property, ref order); - if (RadioButtonType.NotSet == groupType) - { - groupType = type; - } - else if (groupType != type) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - this.Core.Write(ErrorMessages.RadioButtonTypeInconsistent(childSourceLineNumbers)); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - - return groupType; - } - - /// - /// Parses an action text element. - /// - /// Element to parse. - private void ParseActionTextElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string action = null; - string message = null; - string template = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Action": - action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Message": - message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Template": - template = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == action) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ActionTextSymbol(sourceLineNumbers) - { - Action = action, - Description = message, - Template = template, - }); - } - } - - /// - /// Parses an ui text element. - /// - /// Element to parse. - private void ParseUITextElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string text = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Value": - text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - id = this.Core.CreateIdentifier("txt", text); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new UITextSymbol(sourceLineNumbers, id) - { - Text = text, - }); - } - } - - /// - /// Parses a text style element. - /// - /// Element to parse. - private void ParseTextStyleElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - int? red = null; - int? green = null; - int? blue = null; - var bold = false; - var italic = false; - var strike = false; - var underline = false; - string faceName = null; - var size = "0"; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - - // RGB Values - case "Red": - var redColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); - if (CompilerConstants.IllegalInteger != redColor) - { - red = redColor; - } - break; - case "Green": - var greenColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); - if (CompilerConstants.IllegalInteger != greenColor) - { - green = greenColor; - } - break; - case "Blue": - var blueColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); - if (CompilerConstants.IllegalInteger != blueColor) - { - blue = blueColor; - } - break; - - // Style values - case "Bold": - bold = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Italic": - italic = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Strike": - strike = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Underline": - underline = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - - // Font values - case "FaceName": - faceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Size": - size = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.CreateIdentifier("txs", faceName, size.ToString(), (red ?? 0).ToString(), (green ?? 0).ToString(), (blue ?? 0).ToString(), bold.ToString(), italic.ToString(), strike.ToString(), underline.ToString()); - } - - if (null == faceName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "FaceName")); - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new TextStyleSymbol(sourceLineNumbers, id) - { - FaceName = faceName, - LocalizedSize = size, - Red = red, - Green = green, - Blue = blue, - Bold = bold, - Italic = italic, - Strike = strike, - Underline = underline, - }); - } - } - - /// - /// Parses a dialog element. - /// - /// Element to parse. - private void ParseDialogElement(XElement node) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - var hidden = false; - var modal = true; - var minimize = true; - var customPalette = false; - var errorDialog = false; - var keepModeless = false; - var height = 0; - string title = null; - var leftScroll = false; - var rightAligned = false; - var rightToLeft = false; - var systemModal = false; - var trackDiskSpace = false; - var width = 0; - var x = 50; - var y = 50; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Height": - height = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Title": - title = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Width": - width = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "X": - x = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); - break; - case "Y": - y = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); - break; - case "CustomPalette": - customPalette = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "ErrorDialog": - errorDialog = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Hidden": - hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "KeepModeless": - keepModeless = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "LeftScroll": - leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Modeless": - modal = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "NoMinimize": - minimize = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RightAligned": - rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RightToLeft": - rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "SystemModal": - systemModal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "TrackDiskSpace": - trackDiskSpace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - - ControlSymbol lastTabSymbol = null; - string cancelControl = null; - string defaultControl = null; - string firstControl = null; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "Control": - this.ParseControlElement(child, id.Id, SymbolDefinitionType.Control, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - if (null != lastTabSymbol && null != lastTabSymbol.Control) - { - if (firstControl != lastTabSymbol.Control) - { - lastTabSymbol.NextControlRef = firstControl; - } - } - - if (null == firstControl) - { - this.Core.Write(ErrorMessages.NoFirstControlSpecified(sourceLineNumbers, id.Id)); - } - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new DialogSymbol(sourceLineNumbers, id) - { - HCentering = x, - VCentering = y, - Width = width, - Height = height, - CustomPalette = customPalette, - ErrorDialog = errorDialog, - Visible = !hidden, - Modal = modal, - KeepModeless = keepModeless, - LeftScroll = leftScroll, - Minimize = minimize, - RightAligned = rightAligned, - RightToLeft = rightToLeft, - SystemModal = systemModal, - TrackDiskSpace = trackDiskSpace, - Title = title, - FirstControlRef = firstControl, - DefaultControlRef = defaultControl, - CancelControlRef = cancelControl, - }); - } - } - - /// - /// Parses a control element. - /// - /// Element to parse. - /// Identifier for parent dialog. - /// Table control belongs in. - /// Last control in the tab order. - /// Name of the first control in the tab order. - /// Name of the default control. - /// Name of the candle control. - private void ParseControlElement(XElement node, string dialog, SymbolDefinitionType symbolType, ref ControlSymbol lastTabSymbol, ref string firstControl, ref string defaultControl, ref string cancelControl) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier controlId = null; - var bits = new BitArray(32); - string checkBoxPropertyRef = null; - string checkboxValue = null; - string controlType = null; - var disabled = false; - string height = null; - string help = null; - var isCancel = false; - var isDefault = false; - var notTabbable = false; - string property = null; - var publishOrder = 0; - string sourceFile = null; - string text = null; - string tooltip = null; - var radioButtonsType = RadioButtonType.NotSet; - string width = null; - string x = null; - string y = null; - - string defaultCondition = null; - string enableCondition = null; - string disableCondition = null; - string hideCondition = null; - string showCondition = null; - - var hidden = false; - var sunken = false; - var indirect = false; - var integer = false; - var rightToLeft = false; - var rightAligned = false; - var leftScroll = false; - - // The rest of the method relies on the control's Type, so we have to get that first. - var typeAttribute = node.Attribute("Type"); - if (null == typeAttribute) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); - } - else - { - controlType = this.Core.GetAttributeValue(sourceLineNumbers, typeAttribute); - } - - string[] specialAttributes; - switch (controlType) - { - case "Billboard": - specialAttributes = null; - notTabbable = true; - disabled = true; - - this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Billboard); - break; - case "Bitmap": - specialAttributes = BitmapControlAttributes; - notTabbable = true; - disabled = true; - break; - case "CheckBox": - specialAttributes = CheckboxControlAttributes; - break; - case "ComboBox": - specialAttributes = ComboboxControlAttributes; - break; - case "DirectoryCombo": - specialAttributes = VolumeControlAttributes; - break; - case "DirectoryList": - specialAttributes = null; - break; - case "Edit": - specialAttributes = EditControlAttributes; - break; - case "GroupBox": - specialAttributes = null; - notTabbable = true; - break; - case "Hyperlink": - specialAttributes = HyperlinkControlAttributes; - break; - case "Icon": - specialAttributes = IconControlAttributes; - notTabbable = true; - disabled = true; - break; - case "Line": - specialAttributes = null; - notTabbable = true; - disabled = true; - break; - case "ListBox": - specialAttributes = ListboxControlAttributes; - break; - case "ListView": - specialAttributes = ListviewControlAttributes; - break; - case "MaskedEdit": - specialAttributes = EditControlAttributes; - break; - case "PathEdit": - specialAttributes = EditControlAttributes; - break; - case "ProgressBar": - specialAttributes = ProgressControlAttributes; - notTabbable = true; - disabled = true; - break; - case "PushButton": - specialAttributes = ButtonControlAttributes; - break; - case "RadioButtonGroup": - specialAttributes = RadioControlAttributes; - break; - case "ScrollableText": - specialAttributes = null; - break; - case "SelectionTree": - specialAttributes = null; - break; - case "Text": - specialAttributes = TextControlAttributes; - notTabbable = true; - break; - case "VolumeCostList": - specialAttributes = VolumeControlAttributes; - notTabbable = true; - break; - case "VolumeSelectCombo": - specialAttributes = VolumeControlAttributes; - break; - default: - specialAttributes = null; - notTabbable = true; - break; - } - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - controlId = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Type": // already processed - break; - case "Cancel": - isCancel = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "CheckBoxPropertyRef": - checkBoxPropertyRef = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "CheckBoxValue": - checkboxValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Default": - isDefault = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "DefaultCondition": - defaultCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "EnableCondition": - enableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DisableCondition": - disableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "HideCondition": - hideCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ShowCondition": - showCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Height": - height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Help": - help = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "IconSize": - var iconSizeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - if (null != specialAttributes) - { - switch (iconSizeValue) - { - case "16": - this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); - break; - case "32": - this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); - break; - case "48": - this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); - this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); - break; - case "": - break; - default: - this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "16", "32", "48")); - break; - } - } - else - { - this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "Type")); - } - break; - case "Property": - property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "TabSkip": - notTabbable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Text": - text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "ToolTip": - tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Width": - width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "X": - x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Y": - y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Disabled": - disabled = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Hidden": - hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Sunken": - sunken = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Indirect": - indirect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "Integer": - integer = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RightToLeft": - rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "RightAligned": - rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - case "LeftScroll": - leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - break; - default: - var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); - if (null == specialAttributes || !this.Core.TrySetBitFromName(specialAttributes, attrib.Name.LocalName, attribValue, bits, 16)) - { - this.Core.UnexpectedAttribute(node, attrib); - } - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - var attributes = this.Core.CreateIntegerFromBitArray(bits); - - //if (disabled) - //{ - // attributes |= WindowsInstallerConstants.MsidbControlAttributesEnabled; // bit will be inverted when stored - //} - - if (null == height) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); - } - - if (null == width) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); - } - - if (null == x) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); - } - - if (null == y) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); - } - - if (null == controlId) - { - controlId = this.Core.CreateIdentifier("ctl", dialog, x, y, height, width); - } - - if (isCancel) - { - cancelControl = controlId.Id; - } - - if (isDefault) - { - defaultControl = controlId.Id; - } - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); - switch (child.Name.LocalName) - { - case "Binary": - this.ParseBinaryElement(child); - break; - case "ComboBox": - this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem"); - break; - case "ListBox": - this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem"); - break; - case "ListView": - this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem"); - break; - case "Property": - this.ParsePropertyElement(child); - break; - case "Publish": - this.ParsePublishElement(child, dialog ?? String.Empty, controlId.Id, ref publishOrder); - break; - case "RadioButtonGroup": - radioButtonsType = this.ParseRadioButtonGroupElement(child, property, radioButtonsType); - break; - case "Subscribe": - this.ParseSubscribeElement(child, dialog, controlId.Id); - break; - case "Text": - foreach (var attrib in child.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - case "Value": - text = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(child, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(child, attrib); - } - } - - this.Core.InnerTextDisallowed(child); - - if (!String.IsNullOrEmpty(text) && null != sourceFile) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "SourceFile", "Value")); - } - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child); - } - } - - this.Core.InnerTextDisallowed(node); - - // If the radio buttons have icons, then we need to add the icon attribute. - switch (radioButtonsType) - { - case RadioButtonType.Bitmap: - attributes |= WindowsInstallerConstants.MsidbControlAttributesBitmap; - break; - case RadioButtonType.Icon: - attributes |= WindowsInstallerConstants.MsidbControlAttributesIcon; - break; - case RadioButtonType.Text: - // Text is the default so nothing needs to be added bits - break; - } - - // the logic for creating control rows is a little tricky because of the way tabable controls are set - IntermediateSymbol symbol = null; - if (!this.Core.EncounteredError) - { - if ("CheckBox" == controlType) - { - if (String.IsNullOrEmpty(property) && String.IsNullOrEmpty(checkBoxPropertyRef)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef", true)); - } - else if (!String.IsNullOrEmpty(property) && !String.IsNullOrEmpty(checkBoxPropertyRef)) - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef")); - } - else if (!String.IsNullOrEmpty(property)) - { - this.Core.AddSymbol(new CheckBoxSymbol(sourceLineNumbers) - { - Property = property, - Value = checkboxValue, - }); - } - else - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CheckBox, checkBoxPropertyRef); - } - } - - var id = new Identifier(controlId.Access, dialog, controlId.Id); - - if (SymbolDefinitionType.BBControl == symbolType) - { - var bbSymbol = this.Core.AddSymbol(new BBControlSymbol(sourceLineNumbers, id) - { - BillboardRef = dialog, - BBControl = controlId.Id, - Type = controlType, - Attributes = attributes, - Enabled = !disabled, - Indirect = indirect, - Integer = integer, - LeftScroll = leftScroll, - RightAligned = rightAligned, - RightToLeft = rightToLeft, - Sunken = sunken, - Visible = !hidden, - Text = text, - SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile } - }); - - bbSymbol.Set((int)BBControlSymbolFields.X, x); - bbSymbol.Set((int)BBControlSymbolFields.Y, y); - bbSymbol.Set((int)BBControlSymbolFields.Width, width); - bbSymbol.Set((int)BBControlSymbolFields.Height, height); - - symbol = bbSymbol; - } - else - { - var controlSymbol = this.Core.AddSymbol(new ControlSymbol(sourceLineNumbers, id) - { - DialogRef = dialog, - Control = controlId.Id, - Type = controlType, - Attributes = attributes, - Enabled = !disabled, - Indirect = indirect, - Integer = integer, - LeftScroll = leftScroll, - RightAligned = rightAligned, - RightToLeft = rightToLeft, - Sunken = sunken, - Visible = !hidden, - Property = !String.IsNullOrEmpty(property) ? property : checkBoxPropertyRef, - Text = text, - Help = (null == tooltip && null == help) ? null : String.Concat(tooltip, "|", help), // Separator is required, even if only one is non-null.}; - SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile } - }); - - controlSymbol.Set((int)BBControlSymbolFields.X, x); - controlSymbol.Set((int)BBControlSymbolFields.Y, y); - controlSymbol.Set((int)BBControlSymbolFields.Width, width); - controlSymbol.Set((int)BBControlSymbolFields.Height, height); - - symbol = controlSymbol; - } - - if (!String.IsNullOrEmpty(defaultCondition)) - { - this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = controlId.Id, - Action = "Default", - Condition = defaultCondition, - }); - } - - if (!String.IsNullOrEmpty(enableCondition)) - { - this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = controlId.Id, - Action = "Enable", - Condition = enableCondition, - }); - } - - if (!String.IsNullOrEmpty(disableCondition)) - { - this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = controlId.Id, - Action = "Disable", - Condition = disableCondition, - }); - } - - if (!String.IsNullOrEmpty(hideCondition)) - { - this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = controlId.Id, - Action = "Hide", - Condition = hideCondition, - }); - } - - if (!String.IsNullOrEmpty(showCondition)) - { - this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = controlId.Id, - Action = "Show", - Condition = showCondition, - }); - } - } - - if (!notTabbable) - { - if (symbol is ControlSymbol controlSymbol) - { - if (null != lastTabSymbol) - { - lastTabSymbol.NextControlRef = controlSymbol.Control; - } - lastTabSymbol = controlSymbol; - } - else if (symbol != null) - { - this.Core.Write(ErrorMessages.TabbableControlNotAllowedInBillboard(sourceLineNumbers, node.Name.LocalName, controlType)); - } - - if (null == firstControl) - { - firstControl = controlId.Id; - } - } - - // bitmap and icon controls contain a foreign key into the binary table in the text column; - // add a reference if the identifier of the binary entry is known during compilation - if (("Bitmap" == controlType || "Icon" == controlType) && Common.IsIdentifier(text)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); - } - } - - /// - /// Parses a publish control event element. - /// - /// Element to parse. - /// Identifier of parent dialog. - /// Identifier of parent control. - /// Relative order of controls. - private void ParsePublishElement(XElement node, string dialog, string control, ref int order) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string argument = null; - string condition = null; - string controlEvent = null; - string property = null; - - // give this control event a unique ordering - order++; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Condition": - condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Control": - if (null != control) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); - } - control = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - break; - case "Dialog": - if (null != dialog) - { - this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); - } - dialog = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, dialog); - break; - case "Event": - controlEvent = Compiler.UppercaseFirstChar(this.Core.GetAttributeValue(sourceLineNumbers, attrib)); - break; - case "Order": - order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 2147483647); - break; - case "Property": - property = String.Concat("[", this.Core.GetAttributeValue(sourceLineNumbers, attrib), "]"); - break; - case "Value": - argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - if (null == control) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Control")); - } - - if (null == dialog) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dialog")); - } - - if (null == controlEvent && null == property) // need to specify at least one - { - this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); - } - else if (null != controlEvent && null != property) // cannot specify both - { - this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); - } - - if (null == argument) - { - if (null != controlEvent) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value", "Event")); - } - else if (null != property) - { - // if this is setting a property to null, put a special value in the argument column - argument = "{}"; - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new ControlEventSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = control, - Event = controlEvent ?? property, - Argument = argument, - Condition = condition, - Ordering = order - }); - } - - if ("DoAction" == controlEvent && null != argument) - { - // if we're not looking at a standard action or a formatted string then create a reference - // to the custom action. - if (!WindowsInstallerStandard.IsStandardAction(argument) && !this.Core.ContainsProperty(argument)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CustomAction, argument); - } - } - - // if we're referring to a dialog but not through a property, add it to the references - if (("NewDialog" == controlEvent || "SpawnDialog" == controlEvent || "SpawnWaitDialog" == controlEvent || "SelectionBrowse" == controlEvent) && Common.IsIdentifier(argument)) - { - this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, argument); - } - } - - /// - /// Parses a control subscription element. - /// - /// Element to parse. - /// Identifier of dialog. - /// Identifier of control. - private void ParseSubscribeElement(XElement node, string dialog, string control) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - string controlAttribute = null; - string eventMapping = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Attribute": - controlAttribute = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); - break; - case "Event": - eventMapping = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); - break; - default: - this.Core.UnexpectedAttribute(node, attrib); - break; - } - } - else - { - this.Core.ParseExtensionAttribute(node, attrib); - } - } - - this.Core.ParseForExtensionElements(node); - - if (!this.Core.EncounteredError) - { - this.Core.AddSymbol(new EventMappingSymbol(sourceLineNumbers) - { - DialogRef = dialog, - ControlRef = control, - Event = eventMapping, - Attribute = controlAttribute, - }); - } - } - } -} diff --git a/src/WixToolset.Core/ComponentKeyPath.cs b/src/WixToolset.Core/ComponentKeyPath.cs deleted file mode 100644 index 8e9c5776..00000000 --- a/src/WixToolset.Core/ComponentKeyPath.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class ComponentKeyPath : IComponentKeyPath - { - /// - /// Identifier of the resource to be a key path. - /// - public string Id { get; set; } - - /// - /// Indicates whether the key path was explicitly set for this resource. - /// - public bool Explicit { get; set; } - - /// - /// Type of resource to be the key path. - /// - public PossibleKeyPathType Type { get; set; } - } -} diff --git a/src/WixToolset.Core/DecompileContext.cs b/src/WixToolset.Core/DecompileContext.cs deleted file mode 100644 index a7ec03fd..00000000 --- a/src/WixToolset.Core/DecompileContext.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class DecompileContext : IDecompileContext - { - internal DecompileContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public string DecompilePath { get; set; } - - public OutputType DecompileType { get; set; } - - public IReadOnlyCollection Extensions { get; set; } - - public string ExtractFolder { get; set; } - - public string CabinetExtractFolder { get; set; } - - public string BaseSourcePath { get; set; } - - public string IntermediateFolder { get; set; } - - public bool IsAdminImage { get; set; } - - public string OutputPath { get; set; } - - public bool SuppressCustomTables { get; set; } - - public bool SuppressDroppingEmptyTables { get; set; } - - public bool SuppressExtractCabinets { get; set; } - - public bool SuppressUI { get; set; } - - public bool TreatProductAsModule { get; set; } - } -} diff --git a/src/WixToolset.Core/DecompileResult.cs b/src/WixToolset.Core/DecompileResult.cs deleted file mode 100644 index fc24cab7..00000000 --- a/src/WixToolset.Core/DecompileResult.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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 -{ - using System.Collections.Generic; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class DecompileResult : IDecompileResult - { - public XDocument Document { get; set; } - - public IReadOnlyCollection ExtractedFilePaths { get; set; } - - public Platform? Platform { get; set; } - } -} diff --git a/src/WixToolset.Core/Decompiler.cs b/src/WixToolset.Core/Decompiler.cs deleted file mode 100644 index 859f582b..00000000 --- a/src/WixToolset.Core/Decompiler.cs +++ /dev/null @@ -1,68 +0,0 @@ -// 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 -{ - using System; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Decompiler of the WiX toolset. - /// - internal class Decompiler : IDecompiler - { - internal Decompiler(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IDecompileResult Decompile(IDecompileContext context) - { - // Pre-decompile. - // - foreach (var extension in context.Extensions) - { - extension.PreDecompile(context); - } - - // Decompile. - // - var result = this.BackendDecompile(context); - - if (result != null) - { - // Post-decompile. - // - foreach (var extension in context.Extensions) - { - extension.PostDecompile(result); - } - } - - return result; - } - - private IDecompileResult BackendDecompile(IDecompileContext context) - { - var extensionManager = context.ServiceProvider.GetService(); - - var backendFactories = extensionManager.GetServices(); - - foreach (var factory in backendFactories) - { - if (factory.TryCreateBackend(context.DecompileType.ToString(), context.OutputPath, out var backend)) - { - var result = backend.Decompile(context); - return result; - } - } - - // TODO: messaging that a backend could not be found to decompile the decompile type? - - return null; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs deleted file mode 100644 index cfa78623..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/BackendHelper.cs +++ /dev/null @@ -1,176 +0,0 @@ -// 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.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Core.Bind; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class BackendHelper : IBackendHelper - { - private static readonly string[] ReservedFileNames = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; - - public BackendHelper(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - } - - private IMessaging Messaging { get; } - - public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) - { - return new FileFacade(file, assembly); - } - - public IFileFacade CreateFileFacade(FileRow fileRow) - { - return new FileFacade(fileRow); - } - - public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) - { - return new FileFacade(true, fileSymbol); - } - - public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) - { - var sourceFullPath = this.GetValidatedFullPath(sourceLineNumbers, source); - - var destinationFullPath = this.GetValidatedFullPath(sourceLineNumbers, destination); - - return (String.IsNullOrEmpty(sourceFullPath) || String.IsNullOrEmpty(destinationFullPath)) ? null : new FileTransfer - { - Source = sourceFullPath, - Destination = destinationFullPath, - Move = move, - SourceLineNumbers = sourceLineNumbers, - Redundant = String.Equals(sourceFullPath, destinationFullPath, StringComparison.OrdinalIgnoreCase) - }; - } - - public string CreateGuid() - { - return Common.GenerateGuid(); - } - - public string CreateGuid(Guid namespaceGuid, string value) - { - return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); - } - - public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) - { - return new ResolvedDirectory - { - DirectoryParent = directoryParent, - Name = name - }; - } - - public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) - { - var command = new ExtractEmbeddedFilesCommand(this, embeddedFiles); - command.Execute(); - - return command.TrackedFiles; - } - - public string GenerateIdentifier(string prefix, params string[] args) - { - return Common.GenerateIdentifier(prefix, args); - } - - public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) - { - return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging); - } - - public int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) - { - return Common.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); - } - - public string GetMsiFileName(string value, bool source, bool longName) - { - return Common.GetName(value, source, longName); - } - - public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) - { - var command = new ResolveDelayedFieldsCommand(this.Messaging, delayedFields, variableCache); - command.Execute(); - } - - public string[] SplitMsiFileName(string value) - { - return Common.GetNames(value); - } - - public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) - { - return new TrackedFile(path, type, sourceLineNumbers); - } - - public bool IsValidBinderVariable(string variable) - { - return Common.IsValidBinderVariable(variable); - } - - public bool IsValidFourPartVersion(string version) - { - return Common.IsValidFourPartVersion(version); - } - - public bool IsValidIdentifier(string id) - { - return Common.IsIdentifier(id); - } - - public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) - { - return Common.IsValidLongFilename(filename, allowWildcards, allowRelative); - } - - public bool IsValidShortFilename(string filename, bool allowWildcards) - { - return Common.IsValidShortFilename(filename, allowWildcards); - } - - private string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path) - { - try - { - var result = Path.GetFullPath(path); - - var filename = Path.GetFileName(result); - - foreach (var reservedName in ReservedFileNames) - { - if (reservedName.Equals(filename, StringComparison.OrdinalIgnoreCase)) - { - this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path)); - return null; - } - } - - return result; - } - catch (ArgumentException) - { - this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path)); - } - catch (PathTooLongException) - { - this.Messaging.Write(ErrorMessages.PathTooLong(sourceLineNumbers, path)); - } - - return null; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs b/src/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs deleted file mode 100644 index 2340ed9e..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs +++ /dev/null @@ -1,233 +0,0 @@ -// 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.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class ExtensionManager : IExtensionManager - { - private const string UserWixFolderName = ".wix4"; - private const string MachineWixFolderName = "WixToolset4"; - private const string ExtensionsFolderName = "extensions"; - - private readonly List extensionFactories = new List(); - private readonly Dictionary> loadedExtensionsByType = new Dictionary>(); - - public ExtensionManager(IWixToolsetCoreServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - private IWixToolsetCoreServiceProvider ServiceProvider { get; } - - public void Add(Assembly extensionAssembly) - { - var types = extensionAssembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && typeof(IExtensionFactory).IsAssignableFrom(t)); - var factories = types.Select(this.CreateExtensionFactory).ToList(); - - if (!factories.Any()) - { - var path = Path.GetFullPath(new Uri(extensionAssembly.CodeBase).LocalPath); - throw new WixException(ErrorMessages.InvalidExtension(path, "The extension does not implement IExtensionFactory. All extensions must have at least one implementation of IExtensionFactory.")); - } - - this.extensionFactories.AddRange(factories); - } - - public void Load(string extensionPath) - { - var checkPath = extensionPath; - var checkedPaths = new List { checkPath }; - try - { - if (!TryLoadFromPath(checkPath, out var assembly) && !Path.IsPathRooted(extensionPath)) - { - if (TryParseExtensionReference(extensionPath, out var extensionId, out var extensionVersion)) - { - foreach (var cachePath in this.CacheLocations()) - { - var extensionFolder = Path.Combine(cachePath, extensionId); - - var versionFolder = extensionVersion; - if (String.IsNullOrEmpty(versionFolder) && !TryFindLatestVersionInFolder(extensionFolder, out versionFolder)) - { - checkedPaths.Add(extensionFolder); - continue; - } - - checkPath = Path.Combine(extensionFolder, versionFolder, "tools", extensionId + ".dll"); - checkedPaths.Add(checkPath); - - if (TryLoadFromPath(checkPath, out assembly)) - { - break; - } - } - } - } - - if (assembly == null) - { - throw new WixException(ErrorMessages.CouldNotFindExtensionInPaths(extensionPath, checkedPaths)); - } - - this.Add(assembly); - } - catch (ReflectionTypeLoadException rtle) - { - throw new WixException(ErrorMessages.InvalidExtension(checkPath, String.Join(Environment.NewLine, rtle.LoaderExceptions.Select(le => le.ToString())))); - } - catch (WixException) - { - throw; - } - catch (Exception e) - { - throw new WixException(ErrorMessages.InvalidExtension(checkPath, e.Message), e); - } - } - - public IReadOnlyCollection GetServices() where T : class - { - if (!this.loadedExtensionsByType.TryGetValue(typeof(T), out var extensions)) - { - extensions = new List(); - - foreach (var factory in this.extensionFactories) - { - if (factory.TryCreateExtension(typeof(T), out var obj) && obj is T extension) - { - extensions.Add(extension); - } - } - - this.loadedExtensionsByType.Add(typeof(T), extensions); - } - - return extensions.Cast().ToList(); - } - - private IExtensionFactory CreateExtensionFactory(Type type) - { - var constructor = type.GetConstructor(new[] { typeof(IWixToolsetCoreServiceProvider) }); - if (constructor != null) - { - return (IExtensionFactory)constructor.Invoke(new[] { this.ServiceProvider }); - } - - return (IExtensionFactory)Activator.CreateInstance(type); - } - - private IEnumerable CacheLocations() - { - var path = Path.Combine(Environment.CurrentDirectory, UserWixFolderName, ExtensionsFolderName); - if (Directory.Exists(path)) - { - yield return path; - } - - path = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - path = Path.Combine(path, UserWixFolderName, ExtensionsFolderName); - if (Directory.Exists(path)) - { - yield return path; - } - - if (Environment.Is64BitOperatingSystem) - { - path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), MachineWixFolderName, ExtensionsFolderName); - if (Directory.Exists(path)) - { - yield return path; - } - } - - path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), MachineWixFolderName, ExtensionsFolderName); - if (Directory.Exists(path)) - { - yield return path; - } - - path = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath), ExtensionsFolderName); - if (Directory.Exists(path)) - { - yield return path; - } - } - - private static bool TryParseExtensionReference(string extensionReference, out string extensionId, out string extensionVersion) - { - extensionId = extensionReference ?? String.Empty; - extensionVersion = String.Empty; - - var index = extensionId.LastIndexOf('/'); - if (index > 0) - { - extensionVersion = extensionReference.Substring(index + 1); - extensionId = extensionReference.Substring(0, index); - - if (!NuGet.Versioning.NuGetVersion.TryParse(extensionVersion, out _)) - { - return false; - } - - if (String.IsNullOrEmpty(extensionId)) - { - return false; - } - } - - return true; - } - - private static bool TryFindLatestVersionInFolder(string basePath, out string foundVersionFolder) - { - foundVersionFolder = null; - - try - { - NuGet.Versioning.NuGetVersion version = null; - foreach (var versionPath in Directory.GetDirectories(basePath)) - { - var versionFolder = Path.GetFileName(versionPath); - if (NuGet.Versioning.NuGetVersion.TryParse(versionFolder, out var checkVersion) && - (version == null || version < checkVersion)) - { - foundVersionFolder = versionFolder; - version = checkVersion; - } - } - } - catch (IOException) - { - } - - return !String.IsNullOrEmpty(foundVersionFolder); - } - - private static bool TryLoadFromPath(string extensionPath, out Assembly assembly) - { - try - { - if (File.Exists(extensionPath)) - { - assembly = Assembly.LoadFrom(extensionPath); - return true; - } - } - catch (IOException e) when (e is FileLoadException || e is FileNotFoundException) - { - } - - assembly = null; - return false; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/FileFacade.cs b/src/WixToolset.Core/ExtensibilityServices/FileFacade.cs deleted file mode 100644 index f85d4842..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/FileFacade.cs +++ /dev/null @@ -1,172 +0,0 @@ -// 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.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility.Data; - - internal class FileFacade : IFileFacade - { - public FileFacade(FileSymbol file, AssemblySymbol assembly) - { - this.FileSymbol = file; - this.AssemblySymbol = assembly; - - this.Identifier = file.Id; - this.ComponentRef = file.ComponentRef; - } - - public FileFacade(bool fromModule, FileSymbol file) - { - this.FromModule = fromModule; - this.FileSymbol = file; - - this.Identifier = file.Id; - this.ComponentRef = file.ComponentRef; - } - - public FileFacade(FileRow row) - { - this.FromTransform = true; - this.FileRow = row; - - this.Identifier = new Identifier(AccessModifier.Section, row.File); - this.ComponentRef = row.Component; - } - - public bool FromModule { get; } - - public bool FromTransform { get; } - - private FileRow FileRow { get; } - - private FileSymbol FileSymbol { get; } - - private AssemblySymbol AssemblySymbol { get; } - - public string Id => this.Identifier.Id; - - public Identifier Identifier { get; } - - public string ComponentRef { get; } - - public int DiskId - { - get => this.FileRow == null ? this.FileSymbol.DiskId ?? 1 : this.FileRow.DiskId; - set - { - if (this.FileRow == null) - { - this.FileSymbol.DiskId = value; - } - else - { - this.FileRow.DiskId = value; - } - } - } - - public string FileName => this.FileRow == null ? this.FileSymbol.Name : this.FileRow.FileName; - - public int FileSize - { - get => this.FileRow == null ? this.FileSymbol.FileSize : this.FileRow.FileSize; - set - { - if (this.FileRow == null) - { - this.FileSymbol.FileSize = value; - } - else - { - this.FileRow.FileSize = value; - } - } - } - - public string Language - { - get => this.FileRow == null ? this.FileSymbol.Language : this.FileRow.Language; - set - { - if (this.FileRow == null) - { - this.FileSymbol.Language = value; - } - else - { - this.FileRow.Language = value; - } - } - } - - public int? PatchGroup => this.FileRow == null ? this.FileSymbol.PatchGroup : null; - - public int Sequence - { - get => this.FileRow == null ? this.FileSymbol.Sequence : this.FileRow.Sequence; - set - { - if (this.FileRow == null) - { - this.FileSymbol.Sequence = value; - } - else - { - this.FileRow.Sequence = value; - } - } - } - - public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers; - - public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source; - - public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; - - public bool Uncompressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - - public string Version - { - get => this.FileRow == null ? this.FileSymbol.Version : this.FileRow.Version; - set - { - if (this.FileRow == null) - { - this.FileSymbol.Version = value; - } - else - { - this.FileRow.Version = value; - } - } - } - - public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblySymbol?.Type : null; - - public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblySymbol?.ApplicationFileRef : throw new NotImplementedException(); - - public string AssemblyManifestFileRef => this.FileRow == null ? this.AssemblySymbol?.ManifestFileRef : throw new NotImplementedException(); - - /// - /// Gets the set of MsiAssemblyName rows created for this file. - /// - /// RowCollection of MsiAssemblyName table. - public List AssemblyNames { get; set; } - - /// - /// Gets or sets the MsiFileHash row for this file. - /// - public MsiFileHashSymbol Hash { get; set; } - - /// - /// Allows direct access to the underlying FileRow as requried for patching. - /// - public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException(); - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/FileTransfer.cs b/src/WixToolset.Core/ExtensibilityServices/FileTransfer.cs deleted file mode 100644 index 2cad7cce..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/FileTransfer.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.ExtensibilityServices -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class FileTransfer : IFileTransfer - { - public string Source { get; set; } - - public string Destination { get; set; } - - public bool Move { get; set; } - - public SourceLineNumber SourceLineNumbers { get; set; } - - public bool Redundant { get; set; } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/Messaging.cs b/src/WixToolset.Core/ExtensibilityServices/Messaging.cs deleted file mode 100644 index afcd9244..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/Messaging.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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.ExtensibilityServices -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class Messaging : IMessaging - { - private IMessageListener listener; - private readonly HashSet suppressedWarnings = new HashSet(); - private readonly HashSet warningsAsErrors = new HashSet(); - - public bool EncounteredError { get; private set; } - - public int LastErrorNumber { get; private set; } - - public bool ShowVerboseMessages { get; set; } - - public bool SuppressAllWarnings { get; set; } - - public bool WarningsAsError { get; set; } - - public void ElevateWarningMessage(int warningNumber) => this.warningsAsErrors.Add(warningNumber); - - public void SetListener(IMessageListener listener) => this.listener = listener; - - public void SuppressWarningMessage(int warningNumber) => this.suppressedWarnings.Add(warningNumber); - - public void Write(Message message) - { - var level = this.CalculateMessageLevel(message); - - if (level == MessageLevel.Nothing) - { - return; - } - - if (level == MessageLevel.Error) - { - this.EncounteredError = true; - this.LastErrorNumber = message.Id; - } - - if (this.listener != null) - { - this.listener.Write(message); - } - else if (level == MessageLevel.Error) - { - throw new WixException(message); - } - } - - public void Write(string message, bool verbose = false) - { - if (!verbose || this.ShowVerboseMessages) - { - this.listener?.Write(message); - } - } - - /// - /// Determines the level of this message, when taking into account warning-as-error, - /// warning level, verbosity level and message suppressed by the caller. - /// - /// Event arguments for the message. - /// MessageLevel representing the level of this message. - private MessageLevel CalculateMessageLevel(Message message) - { - var level = message.Level; - - if (level == MessageLevel.Verbose) - { - if (!this.ShowVerboseMessages) - { - level = MessageLevel.Nothing; - } - } - else if (level == MessageLevel.Warning) - { - if (this.SuppressAllWarnings || this.suppressedWarnings.Contains(message.Id)) - { - level = MessageLevel.Nothing; - } - else if (this.WarningsAsError || this.warningsAsErrors.Contains(message.Id)) - { - level = MessageLevel.Error; - } - } - - level = this.listener?.CalculateMessageLevel(this, message, level) ?? level; - - return level; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs deleted file mode 100644 index c1368190..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ /dev/null @@ -1,863 +0,0 @@ -// 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.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Xml; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ParseHelper : IParseHelper - { - public ParseHelper(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = serviceProvider.GetService(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private ISymbolDefinitionCreator Creator { get; set; } - - public bool ContainsProperty(string possibleProperty) - { - return Common.ContainsProperty(possibleProperty); - } - - public void CreateComplexReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) - { - - section.AddSymbol(new WixComplexReferenceSymbol(sourceLineNumbers) - { - Parent = parentId, - ParentType = parentType, - ParentLanguage = parentLanguage, - Child = childId, - ChildType = childType, - IsPrimary = isPrimary - }); - - this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId); - } - - public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) - { - if (null == id) - { - id = this.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); - } - - var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) - { - ParentDirectoryRef = parentId, - Name = name, - ShortName = shortName, - SourceName = sourceName, - SourceShortName = shortSourceName - }); - - return symbol.Id; - } - - public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax, IDictionary sectionCachedInlinedDirectoryIds) - { - if (String.IsNullOrEmpty(parentId)) - { - throw new ArgumentNullException(nameof(parentId)); - } - - if (String.IsNullOrEmpty(inlineSyntax)) - { - inlineSyntax = this.GetAttributeLongFilename(sourceLineNumbers, attribute, false, true); - } - - if (String.IsNullOrEmpty(inlineSyntax)) - { - return parentId; - } - - inlineSyntax = inlineSyntax.Trim('\\', '/'); - - var cacheKey = String.Concat(parentId, ":", inlineSyntax); - - if (!sectionCachedInlinedDirectoryIds.TryGetValue(cacheKey, out var id)) - { - var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, id: null, parentId, inlineSyntax); - - id = identifier.Id; - } - else - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id); - } - - return id; - } - - public string CreateGuid(Guid namespaceGuid, string value) - { - return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); - } - - public Identifier CreateIdentifier(string prefix, params string[] args) - { - var id = Common.GenerateIdentifier(prefix, args); - return new Identifier(AccessModifier.Section, id); - } - - public Identifier CreateIdentifierFromFilename(string filename) - { - var id = Common.GetIdentifierFromName(filename); - return new Identifier(AccessModifier.Section, id); - } - - public string CreateIdentifierValueFromPlatform(string name, Platform currentPlatform, BurnPlatforms supportedPlatforms) - { - string suffix = null; - - switch (currentPlatform) - { - case Platform.X86: - if ((supportedPlatforms & BurnPlatforms.X86) == BurnPlatforms.X86) - { - suffix = "_X86"; - } - break; - case Platform.X64: - if ((supportedPlatforms & BurnPlatforms.X64) == BurnPlatforms.X64) - { - suffix = "_X64"; - } - break; - case Platform.ARM64: - if ((supportedPlatforms & BurnPlatforms.ARM64) == BurnPlatforms.ARM64) - { - suffix = "_A64"; - } - break; - } - - return suffix == null ? null : name + suffix; - } - - public Identifier CreateRegistrySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId, bool escapeLeadingHash) - { - if (RegistryRootType.Unknown == root) - { - throw new ArgumentOutOfRangeException(nameof(root)); - } - - if (null == key) - { - throw new ArgumentNullException(nameof(key)); - } - - if (null == componentId) - { - throw new ArgumentNullException(nameof(componentId)); - } - - // Escape the leading '#' character for string registry values. - if (escapeLeadingHash && null != value && value.StartsWith("#", StringComparison.Ordinal)) - { - value = String.Concat("#", value); - } - - var id = this.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), key.ToLowerInvariant(), (null != name ? name.ToLowerInvariant() : name)); - - var symbol = section.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) - { - Root = root, - Key = key, - Name = name, - Value = value, - ComponentRef = componentId, - }); - - return symbol.Id; - } - - public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) - { - section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers) - { - Table = symbolName, - PrimaryKeys = primaryKey - }); - } - - public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) - { - section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers) - { - Table = symbolName, - PrimaryKeys = String.Join("/", primaryKeys) - }); - } - - public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) - { - this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKey); - } - - public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) - { - this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKeys); - } - - public void CreateWixGroupSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) - { - if (null == parentId || ComplexReferenceParentType.Unknown == parentType) - { - return; - } - - if (null == childId) - { - throw new ArgumentNullException(nameof(childId)); - } - - section.AddSymbol(new WixGroupSymbol(sourceLineNumbers) - { - ParentId = parentId, - ParentType = parentType, - ChildId = childId, - ChildType = childType, - }); - } - - public void CreateWixSearchSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId) - { - // TODO: verify variable is not a standard bundle variable - if (variable == null) - { - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable")); - } - - section.AddSymbol(new WixSearchSymbol(sourceLineNumbers, id) - { - Variable = variable, - Condition = condition, - BundleExtensionRef = bundleExtensionId, - }); - - if (after != null) - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixSearch, after); - // TODO: We're currently defaulting to "always run after", which we will need to change... - this.CreateWixSearchRelationSymbol(section, sourceLineNumbers, id, after, 2); - } - - if (!String.IsNullOrEmpty(bundleExtensionId)) - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixBundleExtension, bundleExtensionId); - } - } - - public void CreateWixSearchRelationSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, int attributes) - { - section.AddSymbol(new WixSearchRelationSymbol(sourceLineNumbers, id) - { - ParentSearchRef = parentId, - Attributes = attributes, - }); - } - - public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, Identifier identifier = null) - { - if (this.Creator == null) - { - this.CreateSymbolDefinitionCreator(); - } - - if (!this.Creator.TryGetSymbolDefinitionByName(symbolName, out var symbolDefinition)) - { - throw new ArgumentException(nameof(symbolName)); - } - - return this.CreateSymbol(section, sourceLineNumbers, symbolDefinition, identifier); - } - - public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, Identifier identifier = null) - { - return section.AddSymbol(symbolDefinition.CreateSymbol(sourceLineNumbers, identifier)); - } - - public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) - { - section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers) - { - Table = tableDefinition.Name, - }); - - // TODO: Check if the given table definition is a custom table. For now we have to assume that it isn't. - //this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableDefinition.Name); - } - - public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName) - { - section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers) - { - Table = tableName, - }); - - if (this.Creator == null) - { - this.CreateSymbolDefinitionCreator(); - } - - // TODO: The tableName may not be the same as the symbolName. For now, we have to assume that it is. - // We don't add custom table definitions to the tableDefinitions collection, - // so if it's not in there, it better be a custom table. If the Id is just wrong, - // instead of a custom table, we get an unresolved reference at link time. - if (!this.Creator.TryGetSymbolDefinitionByName(tableName, out var _)) - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableName); - } - } - - public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) - { - if (null == attribute) - { - throw new ArgumentNullException(nameof(attribute)); - } - - var emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly; - var value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); - - if (String.IsNullOrEmpty(value)) - { - if (canBeEmpty) - { - return String.Empty; - } - } - else - { - if (generatable && value == "*") - { - return value; - } - - if (Guid.TryParse(value, out var guid)) - { - return guid.ToString("B").ToUpperInvariant(); - } - - if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal)) - { - return value; - } - - if (value.StartsWith("PUT-GUID-", StringComparison.OrdinalIgnoreCase) || - value.StartsWith("{PUT-GUID-", StringComparison.OrdinalIgnoreCase)) - { - this.Messaging.Write(ErrorMessages.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - else - { - this.Messaging.Write(ErrorMessages.IllegalGuidValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return CompilerConstants.IllegalGuid; - } - - public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var access = AccessModifier.Global; - var value = Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, EmptyRule.CanBeEmpty); - - var separator = value.IndexOf(' '); - if (separator > 0) - { - var prefix = value.Substring(0, separator); - switch (prefix) - { - case "global": - case "public": - case "package": - access = AccessModifier.Global; - break; - - case "internal": - case "library": - access = AccessModifier.Library; - break; - - case "file": - case "protected": - access = AccessModifier.File; - break; - - case "private": - case "fragment": - case "section": - access = AccessModifier.Section; - break; - - default: - return null; - } - - value = value.Substring(separator + 1).Trim(); - } - - if (!Common.IsIdentifier(value)) - { - this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - return null; - } - else if (72 < value.Length) - { - this.Messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - - return new Identifier(access, value); - } - - public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute); - } - - public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) - { - return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum); - } - - public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards, bool allowRelative) - { - if (null == attribute) - { - throw new ArgumentNullException("attribute"); - } - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (!String.IsNullOrEmpty(value)) - { - if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value)) - { - if (allowRelative) - { - this.Messaging.Write(ErrorMessages.IllegalRelativeLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - else - { - this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - else if (allowRelative) - { - value = this.GetCanonicalRelativePath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value); - } - else if (CompilerCore.IsAmbiguousFilename(value)) - { - this.Messaging.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return value; - } - - public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) - { - Debug.Assert(minimum > CompilerConstants.LongNotSet && minimum > CompilerConstants.IllegalLong, "The legal values for this attribute collide with at least one sentinel used during parsing."); - - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (0 < value.Length) - { - try - { - var longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture.NumberFormat); - - if (CompilerConstants.LongNotSet == longValue || CompilerConstants.IllegalLong == longValue) - { - this.Messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, longValue)); - } - else if (minimum > longValue || maximum < longValue) - { - this.Messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, longValue, minimum, maximum)); - longValue = CompilerConstants.IllegalLong; - } - - return longValue; - } - catch (FormatException) - { - this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - catch (OverflowException) - { - this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - } - - return CompilerConstants.IllegalLong; - } - - public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) - { - return Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, emptyRule); - } - - public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) - { - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - if (String.IsNullOrEmpty(value)) - { - return null; - } - - switch (value) - { - case "HKCR": - return RegistryRootType.ClassesRoot; - - case "HKCU": - return RegistryRootType.CurrentUser; - - case "HKLM": - return RegistryRootType.LocalMachine; - - case "HKU": - return RegistryRootType.Users; - - case "HKMU": - if (allowHkmu) - { - return RegistryRootType.MachineUser; - } - break; - } - - if (allowHkmu) - { - this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKMU", "HKCR", "HKCU", "HKLM", "HKU")); - } - else - { - this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKCR", "HKCU", "HKLM", "HKU")); - } - - return RegistryRootType.Unknown; - } - - public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - if (!String.IsNullOrEmpty(value)) - { - if (Version.TryParse(value, out var version)) - { - return version.ToString(); - } - - // Allow versions to contain binder variables. - if (Common.ContainsValidBinderVariable(value)) - { - return value; - } - - this.Messaging.Write(ErrorMessages.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - } - - return null; - } - - public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - switch (value) - { - case "yes": - case "true": - return YesNoDefaultType.Yes; - - case "no": - case "false": - return YesNoDefaultType.No; - - case "default": - return YesNoDefaultType.Default; - - default: - this.Messaging.Write(ErrorMessages.IllegalYesNoDefaultValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - return YesNoDefaultType.IllegalValue; - } - } - - public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) - { - var value = this.GetAttributeValue(sourceLineNumbers, attribute); - - switch (value) - { - case "yes": - case "true": - return YesNoType.Yes; - - case "no": - case "false": - return YesNoType.No; - - default: - this.Messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); - return YesNoType.IllegalValue; - } - } - - public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) - { - return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging); - } - - public SourceLineNumber GetSourceLineNumbers(XElement element) - { - return Preprocessor.GetSourceLineNumbers(element); - } - - public string GetConditionInnerText(XElement element) - { - var value = Common.GetInnerText(element)?.Trim().Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '); - - // Return null for a non-existant condition. - return String.IsNullOrEmpty(value) ? null : value; - } - - public string GetTrimmedInnerText(XElement element) - { - var value = Common.GetInnerText(element); - return value?.Trim(); - } - - public void InnerTextDisallowed(XElement element) - { - if (element.Nodes().Any(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType)) - { - var innerText = Common.GetInnerText(element); - if (!String.IsNullOrWhiteSpace(innerText)) - { - var sourceLineNumbers = this.GetSourceLineNumbers(element); - this.Messaging.Write(ErrorMessages.IllegalInnerText(sourceLineNumbers, element.Name.LocalName, innerText)); - } - } - } - - public bool IsValidIdentifier(string value) - { - return Common.IsIdentifier(value); - } - - public bool IsValidLocIdentifier(string identifier) - { - return Common.TryParseWixVariable(identifier, 0, out var parsed) && parsed.Index == 0 && parsed.Length == identifier.Length && parsed.Namespace == "loc"; - } - - public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) - { - return Common.IsValidLongFilename(filename, allowWildcards, allowRelative); - } - - public bool IsValidShortFilename(string filename, bool allowWildcards) - { - return Common.IsValidShortFilename(filename, allowWildcards); - } - - public void ParseExtensionAttribute(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement element, XAttribute attribute, IDictionary context = null) - { - // Ignore attributes defined by the W3C because we'll assume they are always right. - if ((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || - attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal)) - { - return; - } - - if (ParseHelper.TryFindExtension(extensions, attribute.Name.NamespaceName, out var extension)) - { - extension.ParseAttribute(intermediate, section, element, attribute, context); - } - else - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); - this.Messaging.Write(ErrorMessages.UnhandledExtensionAttribute(sourceLineNumbers, element.Name.LocalName, attribute.Name.LocalName, attribute.Name.NamespaceName)); - } - } - - public void ParseExtensionElement(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context = null) - { - if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) - { - extension.ParseElement(intermediate, section, parentElement, element, context); - } - else - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); - this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); - } - } - - public IComponentKeyPath ParsePossibleKeyPathExtensionElement(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) - { - IComponentKeyPath keyPath = null; - - if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) - { - keyPath = extension.ParsePossibleKeyPathElement(intermediate, section, parentElement, element, context); - } - else - { - var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); - this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); - } - - return keyPath; - } - - public void ParseForExtensionElements(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement element) - { - var checkInnerText = false; - - foreach (var child in element.Nodes()) - { - if (child is XElement childElement) - { - if (element.Name.Namespace == childElement.Name.Namespace) - { - this.UnexpectedElement(element, childElement); - } - else - { - this.ParseExtensionElement(extensions, intermediate, section, element, childElement); - } - } - else - { - checkInnerText = true; - } - } - - if (checkInnerText) - { - this.InnerTextDisallowed(element); - } - } - - public WixActionSymbol ScheduleActionSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition, string beforeAction, string afterAction, bool overridable = false) - { - var actionId = new Identifier(access, sequence, actionName); - - var actionSymbol = section.AddSymbol(new WixActionSymbol(sourceLineNumbers, actionId) - { - SequenceTable = sequence, - Action = actionName, - Condition = condition, - Before = beforeAction, - After = afterAction, - Overridable = overridable, - }); - - if (null != beforeAction) - { - if (WindowsInstallerStandard.IsStandardAction(beforeAction)) - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), beforeAction); - } - else - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, beforeAction); - } - } - - if (null != afterAction) - { - if (WindowsInstallerStandard.IsStandardAction(afterAction)) - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), afterAction); - } - else - { - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, afterAction); - } - } - - return actionSymbol; - } - - public void CreateCustomActionReference(SourceLineNumber sourceLineNumbers, IntermediateSection section, string customAction, Platform currentPlatform, CustomActionPlatforms supportedPlatforms) - { - if (!this.Messaging.EncounteredError) - { - var suffix = "_X86"; - - switch (currentPlatform) - { - case Platform.X64: - if ((supportedPlatforms & CustomActionPlatforms.X64) == CustomActionPlatforms.X64) - { - suffix = "_X64"; - } - break; - case Platform.ARM64: - if ((supportedPlatforms & CustomActionPlatforms.ARM64) == CustomActionPlatforms.ARM64) - { - suffix = "_A64"; - } - break; - } - - this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, customAction + suffix); - } - } - - public void UnexpectedAttribute(XElement element, XAttribute attribute) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); - Common.UnexpectedAttribute(this.Messaging, sourceLineNumbers, attribute); - } - - public void UnexpectedElement(XElement parentElement, XElement childElement) - { - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(childElement); - this.Messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, parentElement.Name.LocalName, childElement.Name.LocalName)); - } - - private void CreateSymbolDefinitionCreator() - { - this.Creator = this.ServiceProvider.GetService(); - } - - private static bool TryFindExtension(IEnumerable extensions, XNamespace ns, out ICompilerExtension extension) - { - extension = null; - - foreach (var ext in extensions) - { - if (ext.Namespace == ns) - { - extension = ext; - break; - } - } - - return extension != null; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs b/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs deleted file mode 100644 index 72be2bcb..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/PathResolver.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class PathResolver : IPathResolver - { - public string GetCanonicalDirectoryPath(Dictionary directories, Dictionary componentIdGenSeeds, string directory, Platform platform) - { - if (!directories.TryGetValue(directory, out var resolvedDirectory)) - { - throw new WixException(ErrorMessages.ExpectedDirectory(directory)); - } - - if (null == resolvedDirectory.Path) - { - if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(directory)) - { - resolvedDirectory.Path = componentIdGenSeeds[directory]; - } - else if (WindowsInstallerStandard.IsStandardDirectory(directory)) - { - resolvedDirectory.Path = WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directory, platform); - } - else - { - var name = resolvedDirectory.Name?.ToLowerInvariant(); - - if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) - { - resolvedDirectory.Path = name; - } - else - { - var parentPath = this.GetCanonicalDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, platform); - - if (null != resolvedDirectory.Name) - { - resolvedDirectory.Path = Path.Combine(parentPath, name); - } - else - { - resolvedDirectory.Path = parentPath; - } - } - } - } - - return resolvedDirectory.Path; - } - - public string GetDirectoryPath(Dictionary directories, string directory) - { - if (!directories.TryGetValue(directory, out var resolvedDirectory)) - { - throw new WixException(ErrorMessages.ExpectedDirectory(directory)); - } - - if (null == resolvedDirectory.Path) - { - var name = resolvedDirectory.Name; - - if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) - { - resolvedDirectory.Path = name; - } - else - { - var parentPath = this.GetDirectoryPath(directories, resolvedDirectory.DirectoryParent); - - if (null != resolvedDirectory.Name) - { - resolvedDirectory.Path = Path.Combine(parentPath, name); - } - else - { - resolvedDirectory.Path = parentPath; - } - } - } - - return resolvedDirectory.Path; - } - - public string GetFileSourcePath(Dictionary directories, string directoryId, string fileName, bool compressed, bool useLongName) - { - var fileSourcePath = Common.GetName(fileName, true, useLongName); - - if (compressed) - { - // Use just the file name of the file since all uncompressed files must appear - // in the root of the image in a compressed package. - } - else - { - // Get the relative path of where we want the file to be layed out as specified - // in the Directory table. - var directoryPath = this.GetDirectoryPath(directories, directoryId); - fileSourcePath = Path.Combine(directoryPath, fileSourcePath); - } - - // Strip off "SourceDir" if it's still on there. - if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal)) - { - fileSourcePath = fileSourcePath.Substring(10); - } - - return fileSourcePath; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs deleted file mode 100644 index b0c87bcf..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs +++ /dev/null @@ -1,499 +0,0 @@ -// 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.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class PreprocessHelper : IPreprocessHelper - { - private static readonly char[] VariableSplitter = new char[] { '.' }; - private static readonly char[] ArgumentSplitter = new char[] { ',' }; - - public PreprocessHelper(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = this.ServiceProvider.GetService(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private Dictionary ExtensionsByPrefix { get; set; } - - public void AddVariable(IPreprocessContext context, string name, string value) - { - this.AddVariable(context, name, value, true); - } - - public void AddVariable(IPreprocessContext context, string name, string value, bool showWarning) - { - var currentValue = this.GetVariableValue(context, "var", name); - - if (null == currentValue) - { - context.Variables.Add(name, value); - } - else - { - if (showWarning && value != currentValue) - { - this.Messaging.Write(WarningMessages.VariableDeclarationCollision(context.CurrentSourceLineNumber, name, value, currentValue)); - } - - context.Variables[name] = value; - } - } - - public string EvaluateFunction(IPreprocessContext context, string function) - { - var prefixParts = function.Split(VariableSplitter, 2); - - // Check to make sure there are 2 parts and neither is an empty string. - if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length) - { - throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); - } - - var prefix = prefixParts[0]; - var functionParts = prefixParts[1].Split(new char[] { '(' }, 2); - - // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren. - if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal)) - { - throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); - } - - var functionName = functionParts[0]; - - // Remove the trailing closing paren. - var allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1); - - // Parse the arguments and preprocess them. - var args = allArgs.Split(ArgumentSplitter); - for (var i = 0; i < args.Length; i++) - { - args[i] = this.PreprocessString(context, args[i].Trim()); - } - - var result = this.EvaluateFunction(context, prefix, functionName, args); - - // If the function didn't evaluate, try to evaluate the original value as a variable to support - // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve. - if (result == null) - { - result = this.GetVariableValue(context, function, true); - } - - return result; - } - - public string EvaluateFunction(IPreprocessContext context, string prefix, string function, string[] args) - { - if (String.IsNullOrEmpty(prefix)) - { - throw new ArgumentNullException("prefix"); - } - - if (String.IsNullOrEmpty(function)) - { - throw new ArgumentNullException("function"); - } - - switch (prefix) - { - case "fun": - switch (function) - { - case "AutoVersion": - // Make sure the base version is specified - if (args.Length == 0 || String.IsNullOrEmpty(args[0])) - { - throw new WixException(ErrorMessages.InvalidPreprocessorFunctionAutoVersion(context.CurrentSourceLineNumber)); - } - - // Build = days since 1/1/2000; Revision = seconds since midnight / 2 - var now = DateTime.UtcNow; - var build = now - new DateTime(2000, 1, 1); - var revision = now - new DateTime(now.Year, now.Month, now.Day); - - return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2)); - - default: - return null; - } - - default: - var extensionsByPrefix = this.GetExtensionsByPrefix(); - if (extensionsByPrefix.TryGetValue(prefix, out var extension)) - { - try - { - return extension.EvaluateFunction(prefix, function, args); - } - catch (Exception e) - { - throw new WixException(ErrorMessages.PreprocessorExtensionEvaluateFunctionFailed(context.CurrentSourceLineNumber, prefix, function, String.Join(",", args), e.Message)); - } - } - else - { - return null; - } - } - } - - public string GetVariableValue(IPreprocessContext context, string variable, bool allowMissingPrefix) - { - // Strip the "$(" off the front and the ")" off the back. - if (variable.StartsWith("$(", StringComparison.Ordinal)) - { - variable = variable.Substring(2, variable.Length - 3); - } - - var parts = variable.Split(VariableSplitter, 2); - - if (1 == parts.Length) // missing prefix - { - if (allowMissingPrefix) - { - return this.GetVariableValue(context, "var", parts[0]); - } - else - { - throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); - } - } - else - { - // check for empty variable name - if (0 < parts[1].Length) - { - string result = this.GetVariableValue(context, parts[0], parts[1]); - - // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix - if (null == result && allowMissingPrefix && variable.Contains(".")) - { - result = this.GetVariableValue(context, "var", variable); - } - - return result; - } - else - { - throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); - } - } - } - - public string GetVariableValue(IPreprocessContext context, string prefix, string name) - { - if (String.IsNullOrEmpty(prefix)) - { - throw new ArgumentNullException("prefix"); - } - - if (String.IsNullOrEmpty(name)) - { - throw new ArgumentNullException("name"); - } - - switch (prefix) - { - case "env": - return Environment.GetEnvironmentVariable(name); - - case "sys": - switch (name) - { - case "CURRENTDIR": - return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar); - - case "SOURCEFILEDIR": - return String.Concat(Path.GetDirectoryName(context.CurrentSourceLineNumber.FileName), Path.DirectorySeparatorChar); - - case "SOURCEFILEPATH": - return context.CurrentSourceLineNumber.FileName; - - case "PLATFORM": - this.Messaging.Write(WarningMessages.DeprecatedPreProcVariable(context.CurrentSourceLineNumber, "$(sys.PLATFORM)", "$(sys.BUILDARCH)")); - - goto case "BUILDARCH"; - - case "BUILDARCH": - switch (context.Platform) - { - case Platform.X86: - return "x86"; - - case Platform.X64: - return "x64"; - - case Platform.ARM64: - return "arm64"; - - default: - throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", context.Platform.ToString()); - } - - case "WIXMAJORVERSION": - return ThisAssembly.AssemblyFileVersion.Split('.')[0]; - - case "WIXVERSION": - return ThisAssembly.AssemblyFileVersion; - - default: - return null; - } - - case "var": - return context.Variables.TryGetValue(name, out var result) ? result : null; - - default: - var extensionsByPrefix = this.GetExtensionsByPrefix(); - if (extensionsByPrefix.TryGetValue(prefix, out var extension)) - { - try - { - return extension.GetVariableValue(prefix, name); - } - catch (Exception e) - { - throw new WixException(ErrorMessages.PreprocessorExtensionGetVariableValueFailed(context.CurrentSourceLineNumber, prefix, name, e.Message)); - } - } - else - { - return null; - } - } - } - - public void PreprocessPragma(IPreprocessContext context, string pragmaName, string args, XContainer parent) - { - var prefixParts = pragmaName.Split(VariableSplitter, 2); - - // Check to make sure there are 2 parts and neither is an empty string. - if (2 != prefixParts.Length) - { - throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); - } - - var prefix = prefixParts[0]; - var pragma = prefixParts[1]; - - if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma)) - { - throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); - } - - switch (prefix) - { - case "wix": - switch (pragma) - { - // Add any core defined pragmas here - default: - this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); - break; - } - break; - - default: - var extensionsByPrefix = this.GetExtensionsByPrefix(); - if (extensionsByPrefix.TryGetValue(prefix, out var extension)) - { - if (!extension.ProcessPragma(prefix, pragma, args, parent)) - { - this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); - } - } - break; - } - } - - public string PreprocessString(IPreprocessContext context, string value) - { - var sb = new StringBuilder(); - var currentPosition = 0; - var end = 0; - - while (-1 != (currentPosition = value.IndexOf('$', end))) - { - if (end < currentPosition) - { - sb.Append(value, end, currentPosition - end); - } - - end = currentPosition + 1; - - var remainder = value.Substring(end); - if (remainder.StartsWith("$", StringComparison.Ordinal)) - { - sb.Append("$"); - end++; - } - else if (remainder.StartsWith("(loc.", StringComparison.Ordinal)) - { - currentPosition = remainder.IndexOf(')'); - if (-1 == currentPosition) - { - this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); - break; - } - - sb.Append("$"); // just put the resource reference back as was - sb.Append(remainder, 0, currentPosition + 1); - - end += currentPosition + 1; - } - else if (remainder.StartsWith("(", StringComparison.Ordinal)) - { - var openParenCount = 1; - var closingParenCount = 0; - var isFunction = false; - var foundClosingParen = false; - - // find the closing paren - int closingParenPosition; - for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++) - { - switch (remainder[closingParenPosition]) - { - case '(': - openParenCount++; - isFunction = true; - break; - - case ')': - closingParenCount++; - break; - } - - if (openParenCount == closingParenCount) - { - foundClosingParen = true; - break; - } - } - - // Environment variables may contain parens so if it looks - // like a function, check to see if the environment variable - // prefix was explicitly provided. - if (isFunction && remainder.StartsWith("(env.", StringComparison.Ordinal)) - { - isFunction = false; - } - - // move the currentPosition to the closing paren - currentPosition += closingParenPosition; - - if (!foundClosingParen) - { - if (isFunction) - { - this.Messaging.Write(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, remainder)); - break; - } - else - { - this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); - break; - } - } - - var subString = remainder.Substring(1, closingParenPosition - 1); - string result = null; - if (isFunction) - { - result = this.EvaluateFunction(context, subString); - } - else - { - result = this.GetVariableValue(context, subString, true); - } - - if (null == result) - { - if (isFunction) - { - this.Messaging.Write(ErrorMessages.UndefinedPreprocessorFunction(context.CurrentSourceLineNumber, subString)); - break; - } - else - { - this.Messaging.Write(ErrorMessages.UndefinedPreprocessorVariable(context.CurrentSourceLineNumber, subString)); - break; - } - } - else - { - if (!isFunction) - { - //this.OnResolvedVariable(new ResolvedVariableEventArgs(context.CurrentSourceLineNumber, subString, result)); - } - } - - sb.Append(result); - end += closingParenPosition + 1; - } - else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing - { - sb.Append('$'); - } - } - - if (end < value.Length) - { - sb.Append(value.Substring(end)); - } - - return sb.ToString(); - } - - public void RemoveVariable(IPreprocessContext context, string name) - { - if (!context.Variables.Remove(name)) - { - this.Messaging.Write(ErrorMessages.CannotReundefineVariable(context.CurrentSourceLineNumber, name)); - } - } - - private Dictionary GetExtensionsByPrefix() - { - if (this.ExtensionsByPrefix == null) - { - this.ExtensionsByPrefix = new Dictionary(); - - var extensionManager = this.ServiceProvider.GetService(); - - var extensions = extensionManager.GetServices(); - - foreach (var extension in extensions) - { - if (null != extension.Prefixes) - { - foreach (string prefix in extension.Prefixes) - { - if (!this.ExtensionsByPrefix.ContainsKey(prefix)) - { - this.ExtensionsByPrefix.Add(prefix, extension); - } - } - } - } - } - - return this.ExtensionsByPrefix; - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs b/src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs deleted file mode 100644 index cc8acfdd..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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.ExtensibilityServices -{ - using WixToolset.Extensibility.Data; - - internal class ResolvedDirectory : IResolvedDirectory - { - public string DirectoryParent { get; set; } - - public string Name { get; set; } - - public string Path { get; set; } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs b/src/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs deleted file mode 100644 index a2486130..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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.ExtensibilityServices -{ - using System; - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class SymbolDefinitionCreator : ISymbolDefinitionCreator - { - public SymbolDefinitionCreator(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - private IServiceProvider ServiceProvider { get; } - - private IEnumerable ExtensionData { get; set; } - - private Dictionary CustomDefinitionByName { get; } = new Dictionary(); - - public void AddCustomSymbolDefinition(IntermediateSymbolDefinition definition) - { - if (!this.CustomDefinitionByName.TryGetValue(definition.Name, out var existing) || definition.Revision > existing.Revision) - { - this.CustomDefinitionByName[definition.Name] = definition; - } - } - - public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition) - { - // First, look in the built-ins. - symbolDefinition = SymbolDefinitions.ByName(name); - - if (symbolDefinition == null) - { - if (this.ExtensionData == null) - { - this.LoadExtensionData(); - } - - // Second, look in the extensions. - foreach (var data in this.ExtensionData) - { - if (data.TryGetSymbolDefinitionByName(name, out symbolDefinition)) - { - break; - } - } - - // Finally, look in the custom symbol definitions provided during an intermediate load. - if (symbolDefinition == null) - { - this.CustomDefinitionByName.TryGetValue(name, out symbolDefinition); - } - } - - return symbolDefinition != null; - } - - private void LoadExtensionData() - { - var extensionManager = this.ServiceProvider.GetService(); - - this.ExtensionData = extensionManager.GetServices(); - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/TrackedFile.cs b/src/WixToolset.Core/ExtensibilityServices/TrackedFile.cs deleted file mode 100644 index 028cddbf..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/TrackedFile.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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.ExtensibilityServices -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class TrackedFile : ITrackedFile - { - public TrackedFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers) - { - this.Path = path; - this.Type = type; - this.SourceLineNumbers = sourceLineNumbers; - this.Clean = (type == TrackedFileType.Intermediate || type == TrackedFileType.Final); - } - - public bool Clean { get; set; } - - public string Path { get; set; } - - public SourceLineNumber SourceLineNumbers { get; set; } - - public TrackedFileType Type { get; set; } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/Uuid.cs b/src/WixToolset.Core/ExtensibilityServices/Uuid.cs deleted file mode 100644 index ad9eea26..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/Uuid.cs +++ /dev/null @@ -1,81 +0,0 @@ -// 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.ExtensibilityServices -{ - using System; - using System.Net; - using System.Security.Cryptography; - using System.Text; - - /// - /// Implementation of RFC 4122 - A Universally Unique Identifier (UUID) URN Namespace. - /// - internal static class Uuid - { - /// - /// Creates a version 3 name-based UUID. - /// - /// The namespace UUID. - /// The value. - /// The UUID for the given namespace and value. - public static Guid NewUuid(Guid namespaceGuid, string value) - { - byte[] namespaceBytes = namespaceGuid.ToByteArray(); - short uuidVersion = (short)0x5000; - - // get the fields of the guid which are in host byte ordering - int timeLow = BitConverter.ToInt32(namespaceBytes, 0); - short timeMid = BitConverter.ToInt16(namespaceBytes, 4); - short timeHiAndVersion = BitConverter.ToInt16(namespaceBytes, 6); - - // convert to network byte ordering - timeLow = IPAddress.HostToNetworkOrder(timeLow); - timeMid = IPAddress.HostToNetworkOrder(timeMid); - timeHiAndVersion = IPAddress.HostToNetworkOrder(timeHiAndVersion); - - // get the bytes from the value - byte[] valueBytes = Encoding.Unicode.GetBytes(value); - - // fill-in the hash input buffer - byte[] buffer = new byte[namespaceBytes.Length + valueBytes.Length]; - Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, buffer, 0, 4); - Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, buffer, 4, 2); - Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, buffer, 6, 2); - Buffer.BlockCopy(namespaceBytes, 8, buffer, 8, 8); - Buffer.BlockCopy(valueBytes, 0, buffer, 16, valueBytes.Length); - - // perform the appropriate hash of the namespace and value - byte[] hash; - using (SHA1 sha1 = SHA1.Create()) - { - hash = sha1.ComputeHash(buffer); - } - - // get the fields of the hash which are in network byte ordering - timeLow = BitConverter.ToInt32(hash, 0); - timeMid = BitConverter.ToInt16(hash, 4); - timeHiAndVersion = BitConverter.ToInt16(hash, 6); - - // convert to network byte ordering - timeLow = IPAddress.NetworkToHostOrder(timeLow); - timeMid = IPAddress.NetworkToHostOrder(timeMid); - timeHiAndVersion = IPAddress.NetworkToHostOrder(timeHiAndVersion); - - // set the version and variant bits - timeHiAndVersion &= 0x0FFF; - timeHiAndVersion += uuidVersion; - hash[8] &= 0x3F; - hash[8] |= 0x80; - - // put back the converted values into a 128-bit value - byte[] guidBits = new byte[16]; - Buffer.BlockCopy(hash, 0, guidBits, 0, 16); - - Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, guidBits, 0, 4); - Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, guidBits, 4, 2); - Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, guidBits, 6, 2); - - return new Guid(guidBits); - } - } -} diff --git a/src/WixToolset.Core/ExtensibilityServices/WixBranding.cs b/src/WixToolset.Core/ExtensibilityServices/WixBranding.cs deleted file mode 100644 index 56300400..00000000 --- a/src/WixToolset.Core/ExtensibilityServices/WixBranding.cs +++ /dev/null @@ -1,124 +0,0 @@ -// 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. - -using System.Resources; - -[assembly: NeutralResourcesLanguage("en-US")] - -namespace WixToolset.Core.ExtensibilityServices -{ - using System; - using System.Diagnostics; - using System.IO; - using System.Reflection; - using WixToolset.Extensibility.Services; - - /// - /// Branding strings. - /// - internal class WixBranding : IWixBranding - { - /// - /// News URL for the distribution. - /// - public static string NewsUrl = "http://wixtoolset.org/news/"; - - /// - /// Short product name for the distribution. - /// - public static string ShortProduct = "WiX Toolset"; - - /// - /// Support URL for the distribution. - /// - public static string SupportUrl = "http://wixtoolset.org/"; - - /// - /// Telemetry URL format for the distribution. - /// - public static string TelemetryUrlFormat = "http://wixtoolset.org/telemetry/v{0}/?r={1}"; - - /// - /// VS Extensions Landing page Url for the distribution. - /// - public static string VSExtensionsLandingUrl = "http://wixtoolset.org/releases/"; - - public string GetCreatingApplication() - { - return this.ReplacePlaceholders("[AssemblyProduct] ([FileVersion])"); - } - - public string ReplacePlaceholders(string original, Assembly assembly = null) - { - if (assembly == null) - { - assembly = typeof(WixBranding).Assembly; - } - - var commonVersionPath = Path.Combine(Path.GetDirectoryName(typeof(WixBranding).Assembly.Location), "wixver.dll"); - if (File.Exists(commonVersionPath)) - { - var commonFileVersion = FileVersionInfo.GetVersionInfo(commonVersionPath); - - original = original.Replace("[FileCopyright]", commonFileVersion.LegalCopyright); - original = original.Replace("[FileVersion]", commonFileVersion.FileVersion); - } - - var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location); - - original = original.Replace("[FileComments]", fileVersion.Comments); - original = original.Replace("[FileCopyright]", fileVersion.LegalCopyright); - original = original.Replace("[FileProductName]", fileVersion.ProductName); - original = original.Replace("[FileVersion]", fileVersion.FileVersion); - - if (original.Contains("[FileVersionMajorMinor]")) - { - var version = new Version(fileVersion.FileVersion); - original = original.Replace("[FileVersionMajorMinor]", String.Concat(version.Major, ".", version.Minor)); - } - - if (TryGetAttribute(assembly, out AssemblyCompanyAttribute company)) - { - original = original.Replace("[AssemblyCompany]", company.Company); - } - - if (TryGetAttribute(assembly, out AssemblyCopyrightAttribute copyright)) - { - original = original.Replace("[AssemblyCopyright]", copyright.Copyright); - } - - if (TryGetAttribute(assembly, out AssemblyDescriptionAttribute description)) - { - original = original.Replace("[AssemblyDescription]", description.Description); - } - - if (TryGetAttribute(assembly, out AssemblyProductAttribute product)) - { - original = original.Replace("[AssemblyProduct]", product.Product); - } - - if (TryGetAttribute(assembly, out AssemblyTitleAttribute title)) - { - original = original.Replace("[AssemblyTitle]", title.Title); - } - - original = original.Replace("[NewsUrl]", NewsUrl); - original = original.Replace("[ShortProduct]", ShortProduct); - original = original.Replace("[SupportUrl]", SupportUrl); - - return original; - } - - private static bool TryGetAttribute(Assembly assembly, out T attribute) where T : Attribute - { - attribute = null; - - var customAttributes = assembly.GetCustomAttributes(typeof(T), false); - if (null != customAttributes && 0 < customAttributes.Length) - { - attribute = customAttributes[0] as T; - } - - return null != attribute; - } - } -} diff --git a/src/WixToolset.Core/IBinder.cs b/src/WixToolset.Core/IBinder.cs deleted file mode 100644 index a1b66f42..00000000 --- a/src/WixToolset.Core/IBinder.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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 -{ - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface IBinder - { - IBindResult Bind(IBindContext context); - } -} diff --git a/src/WixToolset.Core/ICompiler.cs b/src/WixToolset.Core/ICompiler.cs deleted file mode 100644 index 0aae579a..00000000 --- a/src/WixToolset.Core/ICompiler.cs +++ /dev/null @@ -1,13 +0,0 @@ -// 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 -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface ICompiler - { - Intermediate Compile(ICompileContext context); - } -} diff --git a/src/WixToolset.Core/IDecompiler.cs b/src/WixToolset.Core/IDecompiler.cs deleted file mode 100644 index 74ec26de..00000000 --- a/src/WixToolset.Core/IDecompiler.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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 -{ - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface IDecompiler - { - IDecompileResult Decompile(IDecompileContext context); - } -} diff --git a/src/WixToolset.Core/ILayoutCreator.cs b/src/WixToolset.Core/ILayoutCreator.cs deleted file mode 100644 index cdff2a78..00000000 --- a/src/WixToolset.Core/ILayoutCreator.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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 -{ - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface ILayoutCreator - { - void Layout(ILayoutContext context); - } -} diff --git a/src/WixToolset.Core/ILibrarian.cs b/src/WixToolset.Core/ILibrarian.cs deleted file mode 100644 index 0fcedea5..00000000 --- a/src/WixToolset.Core/ILibrarian.cs +++ /dev/null @@ -1,13 +0,0 @@ -// 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 -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface ILibrarian - { - Intermediate Combine(ILibraryContext context); - } -} diff --git a/src/WixToolset.Core/ILinker.cs b/src/WixToolset.Core/ILinker.cs deleted file mode 100644 index 11cc2c87..00000000 --- a/src/WixToolset.Core/ILinker.cs +++ /dev/null @@ -1,13 +0,0 @@ -// 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 -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation - public interface ILinker - { - Intermediate Link(ILinkContext context); - } -} diff --git a/src/WixToolset.Core/ILocalizationParser.cs b/src/WixToolset.Core/ILocalizationParser.cs deleted file mode 100644 index 0e70aa0e..00000000 --- a/src/WixToolset.Core/ILocalizationParser.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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 -{ - using System.Xml.Linq; - using WixToolset.Data; - - /// - /// Parses localization source files. - /// - public interface ILocalizationParser - { - /// - /// Loads a localization file from a path on disk. - /// - /// Path to localization file saved on disk. - /// Returns the loaded localization file. - Localization ParseLocalization(string path); - - /// - /// Loads a localization file from memory. - /// - /// Document to parse as localization file. - /// Returns the loaded localization file. - Localization ParseLocalization(XDocument document); - } -} diff --git a/src/WixToolset.Core/IPreprocessor.cs b/src/WixToolset.Core/IPreprocessor.cs deleted file mode 100644 index f6ed5fed..00000000 --- a/src/WixToolset.Core/IPreprocessor.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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 -{ - using System.Xml; - using WixToolset.Extensibility.Data; - -#pragma warning disable 1591 // TODO: add documentation, move into Extensibility - public interface IPreprocessor - { - IPreprocessResult Preprocess(IPreprocessContext context); - - IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader); - } -} diff --git a/src/WixToolset.Core/IResolver.cs b/src/WixToolset.Core/IResolver.cs deleted file mode 100644 index db25edbe..00000000 --- a/src/WixToolset.Core/IResolver.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 -{ - using WixToolset.Extensibility.Data; - - /// - /// Resolves localization and bind variables. - /// - public interface IResolver - { - /// - /// Resolve localization and bind variables. - /// - /// Resolve context. - /// Resolve result. - IResolveResult Resolve(IResolveContext context); - } -} diff --git a/src/WixToolset.Core/IUnbinder.cs b/src/WixToolset.Core/IUnbinder.cs deleted file mode 100644 index 2b4daaa5..00000000 --- a/src/WixToolset.Core/IUnbinder.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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 -{ - using WixToolset.Data; - -#pragma warning disable 1591 // TODO: add documentation, move into Extensibility - public interface IUnbinder - { - Intermediate Unbind(string file, OutputType outputType, string exportBasePath); - } -} diff --git a/src/WixToolset.Core/IncludedFile.cs b/src/WixToolset.Core/IncludedFile.cs deleted file mode 100644 index 25d51191..00000000 --- a/src/WixToolset.Core/IncludedFile.cs +++ /dev/null @@ -1,14 +0,0 @@ -// 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 -{ - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class IncludedFile : IIncludedFile - { - public string Path { get; set; } - - public SourceLineNumber SourceLineNumbers { get; set; } - } -} diff --git a/src/WixToolset.Core/IncribeContext.cs b/src/WixToolset.Core/IncribeContext.cs deleted file mode 100644 index 9d7055ab..00000000 --- a/src/WixToolset.Core/IncribeContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 -{ - using System; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class InscribeContext : IInscribeContext - { - public InscribeContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public string IntermediateFolder { get; set; } - - public string InputFilePath { get; set; } - - public string SignedEngineFile { get; set; } - - public string OutputFile { get; set; } - } -} diff --git a/src/WixToolset.Core/LayoutContext.cs b/src/WixToolset.Core/LayoutContext.cs deleted file mode 100644 index 4b8c7b99..00000000 --- a/src/WixToolset.Core/LayoutContext.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class LayoutContext : ILayoutContext - { - internal LayoutContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IReadOnlyCollection Extensions { get; set; } - - public IReadOnlyCollection FileSystemExtensions { get; set; } - - public IReadOnlyCollection FileTransfers { get; set; } - - public IReadOnlyCollection TrackedFiles { get; set; } - - public string IntermediateFolder { get; set; } - - public string ContentsFile { get; set; } - - public string OutputsFile { get; set; } - - public string BuiltOutputsFile { get; set; } - - public bool ResetAcls { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/LayoutCreator.cs b/src/WixToolset.Core/LayoutCreator.cs deleted file mode 100644 index 0c5aaf63..00000000 --- a/src/WixToolset.Core/LayoutCreator.cs +++ /dev/null @@ -1,223 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixToolset.Core.Bind; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Layout for the WiX toolset. - /// - internal class LayoutCreator : ILayoutCreator - { - internal LayoutCreator(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - } - - private IMessaging Messaging { get; } - - public void Layout(ILayoutContext context) - { - // Pre-layout. - // - foreach (var extension in context.Extensions) - { - extension.PreLayout(context); - } - - try - { - // Final step in binding that transfers (moves/copies) all files generated into the appropriate - // location in the source image. - if (context.FileTransfers?.Any() == true) - { - this.Messaging.Write(VerboseMessages.LayingOutMedia()); - - var command = new TransferFilesCommand(this.Messaging, context.Extensions, context.FileTransfers, context.ResetAcls); - command.Execute(); - } - - if (context.TrackedFiles != null) - { - this.CleanTempFiles(context.IntermediateFolder, context.TrackedFiles); - } - } - finally - { - if (context.TrackedFiles != null) - { - if (!String.IsNullOrEmpty(context.ContentsFile)) - { - this.CreateContentsFile(context.ContentsFile, context.TrackedFiles); - } - - if (!String.IsNullOrEmpty(context.OutputsFile)) - { - this.CreateOutputsFile(context.OutputsFile, context.TrackedFiles); - } - - if (!String.IsNullOrEmpty(context.BuiltOutputsFile)) - { - this.CreateBuiltOutputsFile(context.BuiltOutputsFile, context.TrackedFiles); - } - } - } - - // Post-layout. - foreach (var extension in context.Extensions) - { - extension.PostLayout(); - } - } - - /// - /// Writes the paths to the content files to a text file. - /// - /// Path to write file. - /// Collection of paths to content files that will be written to file. - private void CreateContentsFile(string path, IEnumerable trackedFiles) - { - var uniqueInputFilePaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Input).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); - - if (!uniqueInputFilePaths.Any()) - { - return; - } - - var directory = Path.GetDirectoryName(path); - Directory.CreateDirectory(directory); - - using (var contents = new StreamWriter(path, false)) - { - foreach (var inputPath in uniqueInputFilePaths) - { - contents.WriteLine(inputPath); - } - } - } - - /// - /// Writes the paths to the output files to a text file. - /// - /// Path to write file. - /// Collection of files that were transferred to the output directory. - private void CreateOutputsFile(string path, IEnumerable trackedFiles) - { - var uniqueOutputPaths = new SortedSet(trackedFiles.Where(t => t.Clean).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); - - if (!uniqueOutputPaths.Any()) - { - return; - } - - var directory = Path.GetDirectoryName(path); - Directory.CreateDirectory(directory); - - using (var outputs = new StreamWriter(path, false)) - { - //// Don't list files where the source is the same as the destination since - //// that might be the only place the file exists. The outputs file is often - //// used to delete stuff and losing the original source would be bad. - //var uniqueOutputPaths = new SortedSet(fileTransfers.Where(ft => !ft.Redundant).Select(ft => ft.Destination), StringComparer.OrdinalIgnoreCase); - - foreach (var outputPath in uniqueOutputPaths) - { - outputs.WriteLine(outputPath); - } - } - } - - /// - /// Writes the paths to the built output files to a text file. - /// - /// Path to write file. - /// Collection of files that were transferred to the output directory. - private void CreateBuiltOutputsFile(string path, IEnumerable trackedFiles) - { - var uniqueBuiltPaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Final).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); - - if (!uniqueBuiltPaths.Any()) - { - return; - } - - var directory = Path.GetDirectoryName(path); - Directory.CreateDirectory(directory); - - using (var outputs = new StreamWriter(path, false)) - { - foreach (var builtPath in uniqueBuiltPaths) - { - outputs.WriteLine(builtPath); - } - } - } - - private void CleanTempFiles(string intermediateFolder, IEnumerable trackedFiles) - { - var uniqueTempPaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Temporary).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); - - if (!uniqueTempPaths.Any()) - { - return; - } - - var uniqueFolders = new SortedSet(StringComparer.OrdinalIgnoreCase) - { - intermediateFolder - }; - - // Clean up temp files. - foreach (var tempPath in uniqueTempPaths) - { - try - { - this.SplitUniqueFolders(intermediateFolder, tempPath, uniqueFolders); - - File.Delete(tempPath); - } - catch // delete is best effort. - { - } - } - - // Clean up empty temp folders. - foreach (var folder in uniqueFolders.Reverse()) - { - try - { - Directory.Delete(folder); - } - catch // delete is best effort. - { - } - } - } - - private void SplitUniqueFolders(string intermediateFolder, string tempPath, SortedSet uniqueFolders) - { - if (tempPath.StartsWith(intermediateFolder, StringComparison.OrdinalIgnoreCase)) - { - var folder = Path.GetDirectoryName(tempPath).Substring(intermediateFolder.Length); - - var parts = folder.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); - - folder = intermediateFolder; - - foreach (var part in parts) - { - folder = Path.Combine(folder, part); - - uniqueFolders.Add(folder); - } - } - } - } -} diff --git a/src/WixToolset.Core/Librarian.cs b/src/WixToolset.Core/Librarian.cs deleted file mode 100644 index 1dd1b44d..00000000 --- a/src/WixToolset.Core/Librarian.cs +++ /dev/null @@ -1,135 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Core.Bind; - using WixToolset.Core.Link; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Core librarian tool. - /// - internal class Librarian : ILibrarian - { - internal Librarian(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = this.ServiceProvider.GetService(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - /// - /// Create a library by combining several intermediates (objects). - /// - /// Returns the new library. - public Intermediate Combine(ILibraryContext context) - { - if (String.IsNullOrEmpty(context.LibraryId)) - { - context.LibraryId = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('=').Replace('+', '.').Replace('/', '_'); - } - - foreach (var extension in context.Extensions) - { - extension.PreCombine(context); - } - - Intermediate library = null; - try - { - var sections = context.Intermediates.SelectMany(i => i.Sections).ToList(); - - var collate = new CollateLocalizationsCommand(this.Messaging, context.Localizations); - var localizationsByCulture = collate.Execute(); - - if (this.Messaging.EncounteredError) - { - return null; - } - - this.ResolveFilePathsToEmbed(context, sections); - - foreach (var section in sections) - { - section.AssignToLibrary(context.LibraryId); - } - - library = new Intermediate(context.LibraryId, IntermediateLevels.Compiled, sections, localizationsByCulture); - - library.UpdateLevel(IntermediateLevels.Combined); - - this.Validate(library); - } - finally - { - foreach (var extension in context.Extensions) - { - extension.PostCombine(library); - } - } - - return this.Messaging.EncounteredError ? null : library; - } - - private void ResolveFilePathsToEmbed(ILibraryContext context, IEnumerable sections) - { - // Resolve paths to files that are to be embedded in the library. - if (context.BindFiles) - { - var variableResolver = this.ServiceProvider.GetService(); - - var fileResolver = new FileResolver(context.BindPaths, context.Extensions); - - foreach (var symbol in sections.SelectMany(s => s.Symbols)) - { - foreach (var field in symbol.Fields.Where(f => f?.Type == IntermediateFieldType.Path)) - { - var pathField = field.AsPath(); - - if (pathField != null && !String.IsNullOrEmpty(pathField.Path)) - { - var resolution = variableResolver.ResolveVariables(symbol.SourceLineNumbers, pathField.Path); - - var file = fileResolver.Resolve(symbol.SourceLineNumbers, symbol.Definition, resolution.Value); - - if (!String.IsNullOrEmpty(file)) - { - // File was successfully resolved so track the embedded index as the embedded file index. - field.Set(new IntermediateFieldPathValue { Embed = true, Path = file }); - } - else - { - this.Messaging.Write(ErrorMessages.FileNotFound(symbol.SourceLineNumbers, pathField.Path, symbol.Definition.Name)); - } - } - } - } - } - } - - private void Validate(Intermediate library) - { - var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, library.Sections, OutputType.Library); - find.Execute(); - - // TODO: Consider bringing this sort of verification back. - // foreach (Section section in library.Sections) - // { - // ResolveReferencesCommand resolve = new ResolveReferencesCommand(find.EntrySection, find.Symbols); - // resolve.Execute(); - // - // ReportDuplicateResolvedSymbolErrorsCommand reportDupes = new ReportDuplicateResolvedSymbolErrorsCommand(find.SymbolsWithDuplicates, resolve.ResolvedSections); - // reportDupes.Execute(); - // } - } - } -} diff --git a/src/WixToolset.Core/LibraryContext.cs b/src/WixToolset.Core/LibraryContext.cs deleted file mode 100644 index e701cadf..00000000 --- a/src/WixToolset.Core/LibraryContext.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class LibraryContext : ILibraryContext - { - internal LibraryContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IMessaging Messaging { get; set; } - - public bool BindFiles { get; set; } - - public IReadOnlyCollection BindPaths { get; set; } - - public IReadOnlyCollection Extensions { get; set; } - - public string LibraryId { get; set; } - - public IReadOnlyCollection Localizations { get; set; } - - public IReadOnlyCollection Intermediates { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/Link/CollateLocalizationsCommand.cs b/src/WixToolset.Core/Link/CollateLocalizationsCommand.cs deleted file mode 100644 index d5c69838..00000000 --- a/src/WixToolset.Core/Link/CollateLocalizationsCommand.cs +++ /dev/null @@ -1,71 +0,0 @@ -// 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.Link -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - internal class CollateLocalizationsCommand - { - public CollateLocalizationsCommand(IMessaging messaging, IEnumerable localizations) - { - this.Messaging = messaging; - this.Localizations = localizations; - } - - private IMessaging Messaging { get; } - - private IEnumerable Localizations { get; } - - public Dictionary Execute() - { - var localizationsByCulture = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var localization in this.Localizations) - { - if (localizationsByCulture.TryGetValue(localization.Culture, out var existingCulture)) - { - var merged = this.Merge(existingCulture, localization); - localizationsByCulture[localization.Culture] = merged; - } - else - { - localizationsByCulture.Add(localization.Culture, localization); - } - } - - return localizationsByCulture; - } - - private Localization Merge(Localization existingLocalization, Localization localization) - { - var variables = existingLocalization.Variables.ToDictionary(v => v.Id); - var controls = existingLocalization.LocalizedControls.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - - foreach (var newVariable in localization.Variables) - { - if (!variables.TryGetValue(newVariable.Id, out var existingVariable) || (existingVariable.Overridable && !newVariable.Overridable)) - { - variables[newVariable.Id] = newVariable; - } - else if (!newVariable.Overridable) - { - this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(newVariable.SourceLineNumbers, newVariable.Id)); - } - } - - foreach (var localizedControl in localization.LocalizedControls) - { - if (!controls.ContainsKey(localizedControl.Key)) - { - controls.Add(localizedControl.Key, localizedControl.Value); - } - } - - return new Localization(existingLocalization.Codepage ?? localization.Codepage, existingLocalization.SummaryInformationCodepage ?? localization.SummaryInformationCodepage, existingLocalization.Culture, variables, controls); - } - } -} diff --git a/src/WixToolset.Core/Link/ConnectToFeature.cs b/src/WixToolset.Core/Link/ConnectToFeature.cs deleted file mode 100644 index e9a739a1..00000000 --- a/src/WixToolset.Core/Link/ConnectToFeature.cs +++ /dev/null @@ -1,59 +0,0 @@ -// 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.Link -{ - using System.Collections.Generic; - using WixToolset.Data; - - /// - /// Object that connects things (components/modules) to features. - /// - internal class ConnectToFeature - { - /// - /// Creates a new connect to feature. - /// - /// Section this connect belongs to. - /// Id of the child. - /// Sets the primary feature for the connection. - /// Sets if this is explicit primary. - public ConnectToFeature(IntermediateSection section, string childId, string primaryFeature, bool explicitPrimaryFeature) - { - this.Section = section; - this.ChildId = childId; - - this.PrimaryFeature = primaryFeature; - this.IsExplicitPrimaryFeature = explicitPrimaryFeature; - } - - /// - /// Gets the section. - /// - /// Section. - public IntermediateSection Section { get; } - - /// - /// Gets the child identifier. - /// - /// The child identifier. - public string ChildId { get; } - - /// - /// Gets or sets if the flag for if the primary feature was set explicitly. - /// - /// The flag for if the primary feature was set explicitly. - public bool IsExplicitPrimaryFeature { get; set; } - - /// - /// Gets or sets the primary feature. - /// - /// The primary feature. - public string PrimaryFeature { get; set; } - - /// - /// Gets the features connected to. - /// - /// Features connected to. - public List ConnectFeatures { get; } = new List(); - } -} diff --git a/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs b/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs deleted file mode 100644 index b7874527..00000000 --- a/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs +++ /dev/null @@ -1,92 +0,0 @@ -// 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.Link -{ - using System; - using System.Collections; - - /// - /// Hash collection of connect to feature objects. - /// - internal class ConnectToFeatureCollection : ICollection - { - private Hashtable collection; - - /// - /// Instantiate a new ConnectToFeatureCollection class. - /// - public ConnectToFeatureCollection() - { - this.collection = new Hashtable(); - } - - /// - /// Gets the number of items in the collection. - /// - /// Number of items in collection. - public int Count - { - get { return this.collection.Count; } - } - - /// - /// Gets if the collection has been synchronized. - /// - /// True if the collection has been synchronized. - public bool IsSynchronized - { - get { return this.collection.IsSynchronized; } - } - - /// - /// Gets the object used to synchronize the collection. - /// - /// Oject used the synchronize the collection. - public object SyncRoot - { - get { return this.collection.SyncRoot; } - } - - /// - /// Gets a feature connection by child id. - /// - /// Identifier of child to locate. - public ConnectToFeature this[string childId] - { - get { return (ConnectToFeature)this.collection[childId]; } - } - - /// - /// Adds a feature connection to the collection. - /// - /// Feature connection to add. - public void Add(ConnectToFeature connection) - { - if (null == connection) - { - throw new ArgumentNullException("connection"); - } - - this.collection.Add(connection.ChildId, connection); - } - - /// - /// Copies the collection into an array. - /// - /// Array to copy the collection into. - /// Index to start copying from. - public void CopyTo(System.Array array, int index) - { - this.collection.CopyTo(array, index); - } - - /// - /// Gets enumerator for the collection. - /// - /// Enumerator for the collection. - public IEnumerator GetEnumerator() - { - return this.collection.Values.GetEnumerator(); - } - } -} diff --git a/src/WixToolset.Core/Link/ConnectToModule.cs b/src/WixToolset.Core/Link/ConnectToModule.cs deleted file mode 100644 index 4380e12c..00000000 --- a/src/WixToolset.Core/Link/ConnectToModule.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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.Link -{ - /// - /// Object that connects things to modules. - /// - internal class ConnectToModule - { - private string childId; - private string module; - private string moduleLanguage; - - /// - /// Creates a new connect to module. - /// - /// Id of the child. - /// Id of the module. - /// Language of the module. - public ConnectToModule(string childId, string module, string moduleLanguage) - { - this.childId = childId; - this.module = module; - this.moduleLanguage = moduleLanguage; - } - - /// - /// Gets the id of the child. - /// - /// Child identifier. - public string ChildId - { - get { return this.childId; } - } - - /// - /// Gets the id of the module. - /// - /// The id of the module. - public string Module - { - get { return this.module; } - } - - /// - /// Gets the language of the module. - /// - /// The language of the module. - public string ModuleLanguage - { - get { return this.moduleLanguage; } - } - } -} diff --git a/src/WixToolset.Core/Link/ConnectToModuleCollection.cs b/src/WixToolset.Core/Link/ConnectToModuleCollection.cs deleted file mode 100644 index e0f96ffb..00000000 --- a/src/WixToolset.Core/Link/ConnectToModuleCollection.cs +++ /dev/null @@ -1,92 +0,0 @@ -// 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.Link -{ - using System; - using System.Collections; - - /// - /// Hash collection of connect to module objects. - /// - internal class ConnectToModuleCollection : ICollection - { - private Hashtable collection; - - /// - /// Instantiate a new ConnectToModuleCollection class. - /// - public ConnectToModuleCollection() - { - this.collection = new Hashtable(); - } - - /// - /// Gets the number of elements actually contained in the ConnectToModuleCollection. - /// - /// The number of elements actually contained in the ConnectToModuleCollection. - public int Count - { - get { return this.collection.Count; } - } - - /// - /// Gets a value indicating whether access to the ConnectToModuleCollection is synchronized (thread-safe). - /// - /// true if access to the ConnectToModuleCollection is synchronized (thread-safe); otherwise, false. The default is false. - public bool IsSynchronized - { - get { return this.collection.IsSynchronized; } - } - - /// - /// Gets an object that can be used to synchronize access to the ConnectToModuleCollection. - /// - /// An object that can be used to synchronize access to the ConnectToModuleCollection. - public object SyncRoot - { - get { return this.collection.SyncRoot; } - } - - /// - /// Gets a module connection by child id. - /// - /// Identifier of child to locate. - public ConnectToModule this[string childId] - { - get { return (ConnectToModule)this.collection[childId]; } - } - - /// - /// Adds a module connection to the collection. - /// - /// Module connection to add. - public void Add(ConnectToModule connection) - { - if (null == connection) - { - throw new ArgumentNullException("connection"); - } - - this.collection.Add(connection.ChildId, connection); - } - - /// - /// Copies the entire ConnectToModuleCollection to a compatible one-dimensional Array, starting at the specified index of the target array. - /// - /// The one-dimensional Array that is the destination of the elements copied from this ConnectToModuleCollection. The Array must have zero-based indexing. - /// The zero-based index in array at which copying begins. - public void CopyTo(System.Array array, int index) - { - this.collection.Keys.CopyTo(array, index); - } - - /// - /// Returns an enumerator for the entire ConnectToModuleCollection. - /// - /// An IEnumerator for the entire ConnectToModuleCollection. - public IEnumerator GetEnumerator() - { - return this.collection.Keys.GetEnumerator(); - } - } -} diff --git a/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs deleted file mode 100644 index 5d6cc831..00000000 --- a/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs +++ /dev/null @@ -1,119 +0,0 @@ -// 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.Link -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - internal class FindEntrySectionAndLoadSymbolsCommand - { - public FindEntrySectionAndLoadSymbolsCommand(IMessaging messaging, IEnumerable sections, OutputType expectedOutpuType) - { - this.Messaging = messaging; - this.Sections = sections; - this.ExpectedOutputType = expectedOutpuType; - } - - private IMessaging Messaging { get; } - - private IEnumerable Sections { get; } - - private OutputType ExpectedOutputType { get; } - - /// - /// Gets the located entry section after the command is executed. - /// - public IntermediateSection EntrySection { get; private set; } - - /// - /// Gets the collection of loaded symbols. - /// - public IDictionary SymbolsByName { get; private set; } - - /// - /// Gets the collection of possibly conflicting symbols. - /// - public IEnumerable PossibleConflicts { get; private set; } - - /// - /// Gets the collection of redundant symbols that should not be included - /// in the final output. - /// - public ISet RedundantSymbols { get; private set; } - - public void Execute() - { - var symbolsByName = new Dictionary(); - var possibleConflicts = new HashSet(); - var redundantSymbols = new HashSet(); - - if (!Enum.TryParse(this.ExpectedOutputType.ToString(), out SectionType expectedEntrySectionType)) - { - expectedEntrySectionType = SectionType.Unknown; - } - - foreach (var section in this.Sections) - { - // Try to find the one and only entry section. - if (SectionType.Product == section.Type || SectionType.Module == section.Type || SectionType.PatchCreation == section.Type || SectionType.Patch == section.Type || SectionType.Bundle == section.Type) - { - // TODO: remove this? - //if (SectionType.Unknown != expectedEntrySectionType && section.Type != expectedEntrySectionType) - //{ - // string outputExtension = Output.GetExtension(this.ExpectedOutputType); - // this.Messaging.Write(WixWarnings.UnexpectedEntrySection(section.SourceLineNumbers, section.Type.ToString(), expectedEntrySectionType.ToString(), outputExtension)); - //} - - if (null == this.EntrySection) - { - this.EntrySection = section; - } - else - { - this.Messaging.Write(ErrorMessages.MultipleEntrySections(this.EntrySection.Symbols.FirstOrDefault()?.SourceLineNumbers, this.EntrySection.Id, section.Id)); - this.Messaging.Write(ErrorMessages.MultipleEntrySections2(section.Symbols.FirstOrDefault()?.SourceLineNumbers)); - } - } - - // Load all the symbols from the section's tables that create symbols. - foreach (var symbol in section.Symbols.Where(t => t.Id != null)) - { - var symbolWithSection = new SymbolWithSection(section, symbol); - - if (!symbolsByName.TryGetValue(symbolWithSection.Name, out var existingSymbol)) - { - symbolsByName.Add(symbolWithSection.Name, symbolWithSection); - } - else // uh-oh, duplicate symbols. - { - // If the duplicate symbols are both private directories, there is a chance that they - // point to identical symbols. Identical directory symbols are redundant and will not cause - // conflicts. - if (AccessModifier.Section == existingSymbol.Access && AccessModifier.Section == symbolWithSection.Access && - SymbolDefinitionType.Directory == existingSymbol.Symbol.Definition.Type && existingSymbol.Symbol.IsIdentical(symbolWithSection.Symbol)) - { - // Ensure identical symbol's symbol is marked redundant to ensure (should the symbol be - // referenced into the final output) it will not add duplicate primary keys during - // the .IDT importing. - existingSymbol.AddRedundant(symbolWithSection); - redundantSymbols.Add(symbolWithSection.Symbol); - } - else - { - symbolWithSection.AddPossibleConflict(existingSymbol); - existingSymbol.AddPossibleConflict(symbolWithSection); - possibleConflicts.Add(symbolWithSection); - } - } - } - } - - this.SymbolsByName = symbolsByName; - this.PossibleConflicts = possibleConflicts; - this.RedundantSymbols = redundantSymbols; - } - } -} diff --git a/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs b/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs deleted file mode 100644 index 16593c7d..00000000 --- a/src/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs +++ /dev/null @@ -1,194 +0,0 @@ -// 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.Link -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - internal class FlattenAndProcessBundleTablesCommand - { - public FlattenAndProcessBundleTablesCommand(IntermediateSection entrySection, IMessaging messaging) - { - this.EntrySection = entrySection; - this.Messaging = messaging; - } - - private IntermediateSection EntrySection { get; } - - private IMessaging Messaging { get; } - - public void Execute() - { - this.FlattenBundleTables(); - - if (this.Messaging.EncounteredError) - { - return; - } - - this.ProcessBundleComplexReferences(); - } - - /// - /// Flattens the tables used in a Bundle. - /// - private void FlattenBundleTables() - { - // We need to flatten the nested PayloadGroups and PackageGroups under - // UX, Chain, and any Containers. When we're done, the WixGroups table - // will hold Payloads under UX, ChainPackages (references?) under Chain, - // and ContainerPackages/Payloads under any authored Containers. - var groups = new WixGroupingOrdering(this.EntrySection, this.Messaging); - - // Create UX payloads and Package payloads and Container packages - groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, - new[] { ComplexReferenceChildType.ContainerPackage, ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload }); - groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Package, false); - groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Container, false); - groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Layout, false); - - // Create Chain packages... - groups.UseTypes(new[] { ComplexReferenceParentType.PackageGroup }, new[] { ComplexReferenceChildType.Package, ComplexReferenceChildType.PackageGroup }); - groups.FlattenAndRewriteRows(ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, false); - - groups.RemoveUsedGroupRows(); - } - - private void ProcessBundleComplexReferences() - { - var containersById = this.EntrySection.Symbols.OfType().ToDictionary(c => c.Id.Id); - var groups = this.EntrySection.Symbols.OfType().ToList(); - var payloadsById = this.EntrySection.Symbols.OfType().ToDictionary(c => c.Id.Id); - - var containerByPackage = new Dictionary(); - var referencedPackages = new HashSet(); - var payloadsInBA = new HashSet(); - var payloadsInPackageOrLayout = new HashSet(); - - foreach (var groupSymbol in groups) - { - switch (groupSymbol.ChildType) - { - case ComplexReferenceChildType.ContainerPackage: - switch (groupSymbol.ParentType) - { - case ComplexReferenceParentType.Container: - if (containerByPackage.TryGetValue(groupSymbol.ChildId, out var collisionContainer)) - { - this.Messaging.Write(LinkerErrors.PackageInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, groupSymbol.ParentId, collisionContainer.Id.Id)); - } - else - { - containerByPackage.Add(groupSymbol.ChildId, containersById[groupSymbol.ParentId]); - } - break; - } - break; - case ComplexReferenceChildType.Package: - switch (groupSymbol.ParentType) - { - case ComplexReferenceParentType.PackageGroup: - if (groupSymbol.ParentId == BurnConstants.BundleChainPackageGroupId) - { - referencedPackages.Add(groupSymbol.ChildId); - } - break; - } - break; - case ComplexReferenceChildType.Payload: - switch (groupSymbol.ParentType) - { - case ComplexReferenceParentType.Container: - if (groupSymbol.ParentId == BurnConstants.BurnUXContainerName) - { - payloadsInBA.Add(groupSymbol.ChildId); - } - break; - case ComplexReferenceParentType.Layout: - payloadsById[groupSymbol.ChildId].LayoutOnly = true; - payloadsInPackageOrLayout.Add(groupSymbol.ChildId); - break; - case ComplexReferenceParentType.Package: - payloadsInPackageOrLayout.Add(groupSymbol.ChildId); - break; - } - break; - } - } - - foreach (var package in this.EntrySection.Symbols.OfType()) - { - if (!referencedPackages.Contains(package.Id.Id)) - { - this.Messaging.Write(LinkerErrors.UnscheduledChainPackage(package.SourceLineNumbers, package.Id.Id)); - } - } - - foreach (var rollbackBoundary in this.EntrySection.Symbols.OfType()) - { - if (!referencedPackages.Contains(rollbackBoundary.Id.Id)) - { - this.Messaging.Write(LinkerErrors.UnscheduledRollbackBoundary(rollbackBoundary.SourceLineNumbers, rollbackBoundary.Id.Id)); - } - } - - foreach (var payload in payloadsById.Values) - { - var payloadId = payload.Id.Id; - if (payloadsInBA.Contains(payloadId)) - { - if (payloadsInPackageOrLayout.Contains(payloadId)) - { - this.Messaging.Write(LinkerErrors.PayloadSharedWithBA(payload.SourceLineNumbers, payloadId)); - } - } - else if (!payloadsInPackageOrLayout.Contains(payloadId)) - { - this.Messaging.Write(LinkerErrors.OrphanedPayload(payload.SourceLineNumbers, payloadId)); - } - } - - if (this.Messaging.EncounteredError) - { - return; - } - - // Assign authored payloads to authored containers. - // Compressed Payloads not assigned to a container here will get assigned to the default attached container during binding. - foreach (var groupSymbol in groups) - { - if (groupSymbol.ChildType == ComplexReferenceChildType.Payload && groupSymbol.ParentType == ComplexReferenceParentType.Container) - { - var payloadSymbol = payloadsById[groupSymbol.ChildId]; - var containerId = groupSymbol.ParentId; - - if (String.IsNullOrEmpty(payloadSymbol.ContainerRef)) - { - if (payloadSymbol.Compressed == false) - { - this.Messaging.Write(LinkerWarnings.UncompressedPayloadInContainer(payloadSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); - } - - payloadSymbol.Compressed = true; - payloadSymbol.ContainerRef = containerId; - } - else - { - this.Messaging.Write(LinkerWarnings.PayloadInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId, payloadSymbol.ContainerRef)); - } - - if (payloadSymbol.LayoutOnly) - { - this.Messaging.Write(LinkerWarnings.LayoutPayloadInContainer(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); - } - } - } - - } - } -} diff --git a/src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs b/src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs deleted file mode 100644 index cbf48abe..00000000 --- a/src/WixToolset.Core/Link/IntermediateSymbolExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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.Link -{ - using WixToolset.Data; - - internal static class IntermediateSymbolExtensions - { - public static bool IsIdentical(this IntermediateSymbol first, IntermediateSymbol second) - { - var identical = (first.Definition.Type == second.Definition.Type && - (first.Definition.Type != SymbolDefinitionType.MustBeFromAnExtension || first.Definition.Name == second.Definition.Name) && - first.Definition.FieldDefinitions.Length == second.Definition.FieldDefinitions.Length); - - for (var i = 0; identical && i < first.Definition.FieldDefinitions.Length; ++i) - { - var firstField = first[i]; - var secondField = second[i]; - - identical = (firstField.AsString() == secondField.AsString()); - } - - return identical; - } - } -} diff --git a/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs b/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs deleted file mode 100644 index ace2e19d..00000000 --- a/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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.Link -{ - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - - internal class ReportConflictingSymbolsCommand - { - public ReportConflictingSymbolsCommand(IMessaging messaging, IEnumerable possibleConflicts, IEnumerable resolvedSections) - { - this.Messaging = messaging; - this.PossibleConflicts = possibleConflicts; - this.ResolvedSections = resolvedSections; - } - - private IMessaging Messaging { get; } - - private IEnumerable PossibleConflicts { get; } - - private IEnumerable ResolvedSections { get; } - - public void Execute() - { - // Do a quick check if there are any possibly conflicting symbols that don't come from tables that allow - // overriding. Hopefully the symbols with possible conflicts list is usually very short list (empty should - // be the most common). If we find any matches, we'll do a more costly check to see if the possible conflicting - // symbols are in sections we actually referenced. From the resulting set, show an error for each duplicate - // (aka: conflicting) symbol. - var illegalDuplicates = this.PossibleConflicts.Where(s => s.Symbol.Definition.Type != SymbolDefinitionType.WixAction && s.Symbol.Definition.Type != SymbolDefinitionType.WixVariable).ToList(); - if (0 < illegalDuplicates.Count) - { - var referencedSections = new HashSet(this.ResolvedSections); - - foreach (var referencedDuplicate in illegalDuplicates.Where(s => referencedSections.Contains(s.Section))) - { - var actuallyReferencedDuplicates = referencedDuplicate.PossiblyConflicts.Where(s => referencedSections.Contains(s.Section)).ToList(); - - if (actuallyReferencedDuplicates.Any()) - { - this.Messaging.Write(ErrorMessages.DuplicateSymbol(referencedDuplicate.Symbol.SourceLineNumbers, referencedDuplicate.Name)); - - foreach (var duplicate in actuallyReferencedDuplicates) - { - this.Messaging.Write(ErrorMessages.DuplicateSymbol2(duplicate.Symbol.SourceLineNumbers)); - } - } - } - } - } - } -} diff --git a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs deleted file mode 100644 index efb90bb8..00000000 --- a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs +++ /dev/null @@ -1,183 +0,0 @@ -// 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.Link -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - /// - /// Resolves all the simple references in a section. - /// - internal class ResolveReferencesCommand - { - private readonly IntermediateSection entrySection; - private readonly IDictionary symbolsWithSections; - private HashSet referencedSymbols; - private HashSet resolvedSections; - - public ResolveReferencesCommand(IMessaging messaging, IntermediateSection entrySection, IDictionary symbolsWithSections) - { - this.Messaging = messaging; - this.entrySection = entrySection; - this.symbolsWithSections = symbolsWithSections; - this.BuildingMergeModule = (SectionType.Module == entrySection.Type); - } - - public IEnumerable ReferencedSymbolWithSections => this.referencedSymbols; - - public IEnumerable ResolvedSections => this.resolvedSections; - - private bool BuildingMergeModule { get; } - - private IMessaging Messaging { get; } - - /// - /// Resolves all the simple references in a section. - /// - public void Execute() - { - this.resolvedSections = new HashSet(); - this.referencedSymbols = new HashSet(); - - this.RecursivelyResolveReferences(this.entrySection); - } - - /// - /// Recursive helper function to resolve all references of passed in section. - /// - /// Section with references to resolve. - /// Note: recursive function. - private void RecursivelyResolveReferences(IntermediateSection section) - { - // If we already resolved this section, move on to the next. - if (!this.resolvedSections.Add(section)) - { - return; - } - - // Process all of the references contained in this section using the collection of - // symbols provided. Then recursively call this method to process the - // located symbol's section. All in all this is a very simple depth-first - // search of the references per-section. - foreach (var wixSimpleReferenceRow in section.Symbols.OfType()) - { - // If we're building a Merge Module, ignore all references to the Media table - // because Merge Modules don't have Media tables. - if (this.BuildingMergeModule && wixSimpleReferenceRow.Table == "Media") - { - continue; - } - - // See if the symbol (and any of its duplicates) are appropriately accessible. - if (this.symbolsWithSections.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbolWithSection)) - { - var accessible = this.DetermineAccessibleSymbols(section, symbolWithSection); - if (accessible.Count == 1) - { - var accessibleSymbol = accessible[0]; - if (this.referencedSymbols.Add(accessibleSymbol) && null != accessibleSymbol.Section) - { - this.RecursivelyResolveReferences(accessibleSymbol.Section); - } - } - else if (accessible.Count == 0) - { - this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbolWithSection.Access)); - } - else // display errors for the duplicate symbols. - { - var accessibleSymbol = accessible[0]; - var referencingSourceLineNumber = wixSimpleReferenceRow.SourceLineNumbers?.ToString(); - - if (String.IsNullOrEmpty(referencingSourceLineNumber)) - { - this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name)); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name, referencingSourceLineNumber)); - } - - foreach (var accessibleDuplicate in accessible.Skip(1)) - { - this.Messaging.Write(ErrorMessages.DuplicateSymbol2(accessibleDuplicate.Symbol.SourceLineNumbers)); - } - } - } - else - { - this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName)); - } - } - } - - /// - /// Determine if the symbol and any of its duplicates are accessbile by referencing section. - /// - /// Section referencing the symbol. - /// Symbol being referenced. - /// List of symbols accessible by referencing section. - private List DetermineAccessibleSymbols(IntermediateSection referencingSection, SymbolWithSection symbolWithSection) - { - var accessibleSymbols = new List(); - - if (this.AccessibleSymbol(referencingSection, symbolWithSection)) - { - accessibleSymbols.Add(symbolWithSection); - } - - foreach (var dupe in symbolWithSection.PossiblyConflicts) - { - // don't count overridable WixActionSymbols - var symbolAction = symbolWithSection.Symbol as WixActionSymbol; - var dupeAction = dupe.Symbol as WixActionSymbol; - if (symbolAction?.Overridable != dupeAction?.Overridable) - { - continue; - } - - if (this.AccessibleSymbol(referencingSection, dupe)) - { - accessibleSymbols.Add(dupe); - } - } - - foreach (var dupe in symbolWithSection.Redundants) - { - if (this.AccessibleSymbol(referencingSection, dupe)) - { - accessibleSymbols.Add(dupe); - } - } - - return accessibleSymbols; - } - - /// - /// Determine if a single symbol is accessible by the referencing section. - /// - /// Section referencing the symbol. - /// Symbol being referenced. - /// True if symbol is accessible. - private bool AccessibleSymbol(IntermediateSection referencingSection, SymbolWithSection symbolWithSection) - { - switch (symbolWithSection.Access) - { - case AccessModifier.Global: - return true; - case AccessModifier.Library: - return symbolWithSection.Section.CompilationId == referencingSection.CompilationId || (null != symbolWithSection.Section.LibraryId && symbolWithSection.Section.LibraryId == referencingSection.LibraryId); - case AccessModifier.File: - return symbolWithSection.Section.CompilationId == referencingSection.CompilationId; - case AccessModifier.Section: - return referencingSection == symbolWithSection.Section; - default: - throw new ArgumentOutOfRangeException(nameof(symbolWithSection.Access)); - } - } - } -} diff --git a/src/WixToolset.Core/Link/SymbolWithSection.cs b/src/WixToolset.Core/Link/SymbolWithSection.cs deleted file mode 100644 index 08e01077..00000000 --- a/src/WixToolset.Core/Link/SymbolWithSection.cs +++ /dev/null @@ -1,92 +0,0 @@ -// 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.Link -{ - using System; - using System.Collections.Generic; - using System.Linq; - using WixToolset.Data; - - /// - /// Symbol with section representing a single unique symbol. - /// - internal class SymbolWithSection - { - private HashSet possibleConflicts; - private HashSet redundants; - - /// - /// Creates a symbol for a symbol. - /// - /// - /// Symbol for the symbol - public SymbolWithSection(IntermediateSection section, IntermediateSymbol symbol) - { - this.Symbol = symbol; - this.Section = section; - this.Name = String.Concat(this.Symbol.Definition.Name, ":", this.Symbol.Id.Id); - } - - /// - /// Gets the accessibility of the symbol which is a direct reflection of the accessibility of the row's accessibility. - /// - /// Accessbility of the symbol. - public AccessModifier Access => this.Symbol.Id.Access; - - /// - /// Gets the name of the symbol. - /// - /// Name of the symbol. - public string Name { get; } - - /// - /// Gets the symbol for this symbol. - /// - /// Symbol for this symbol. - public IntermediateSymbol Symbol { get; } - - /// - /// Gets the section for the symbol. - /// - /// Section for the symbol. - public IntermediateSection Section { get; } - - /// - /// Gets any duplicates of this symbol with sections that are possible conflicts. - /// - public IEnumerable PossiblyConflicts => this.possibleConflicts ?? Enumerable.Empty(); - - /// - /// Gets any duplicates of this symbol with sections that are redundant. - /// - public IEnumerable Redundants => this.redundants ?? Enumerable.Empty(); - - /// - /// Adds a duplicate symbol with sections that is a possible conflict. - /// - /// Symbol with section that is a possible conflict of this symbol. - public void AddPossibleConflict(SymbolWithSection symbolWithSection) - { - if (null == this.possibleConflicts) - { - this.possibleConflicts = new HashSet(); - } - - this.possibleConflicts.Add(symbolWithSection); - } - - /// - /// Adds a duplicate symbol that is redundant. - /// - /// Symbol with section that is redundant of this symbol. - public void AddRedundant(SymbolWithSection symbolWithSection) - { - if (null == this.redundants) - { - this.redundants = new HashSet(); - } - - this.redundants.Add(symbolWithSection); - } - } -} diff --git a/src/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs b/src/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs deleted file mode 100644 index 2b1925ad..00000000 --- a/src/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs +++ /dev/null @@ -1,75 +0,0 @@ -// 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.Link -{ - using System; - using WixToolset.Data.Symbols; - - internal static class WixComplexReferenceSymbolExtensions - { - /// - /// Creates a shallow copy of the ComplexReference. - /// - /// A shallow copy of the ComplexReference. - public static WixComplexReferenceSymbol Clone(this WixComplexReferenceSymbol source) - { - var clone = new WixComplexReferenceSymbol(source.SourceLineNumbers, source.Id); - clone.ParentType = source.ParentType; - clone.Parent = source.Parent; - clone.ParentLanguage = source.ParentLanguage; - clone.ChildType = source.ChildType; - clone.Child = source.Child; - clone.IsPrimary = source.IsPrimary; - - return clone; - } - - /// - /// Compares two complex references without considering the primary bit. - /// - /// this - /// Complex reference to compare to. - /// Zero if the objects are equivalent, negative number if the provided object is less, positive if greater. - public static int CompareToWithoutConsideringPrimary(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol other) - { - var comparison = symbol.ChildType - other.ChildType; - if (0 == comparison) - { - comparison = String.Compare(symbol.Child, other.Child, StringComparison.Ordinal); - if (0 == comparison) - { - comparison = symbol.ParentType - other.ParentType; - if (0 == comparison) - { - string thisParentLanguage = null == symbol.ParentLanguage ? String.Empty : symbol.ParentLanguage; - string otherParentLanguage = null == other.ParentLanguage ? String.Empty : other.ParentLanguage; - comparison = String.Compare(thisParentLanguage, otherParentLanguage, StringComparison.Ordinal); - if (0 == comparison) - { - comparison = String.Compare(symbol.Parent, other.Parent, StringComparison.Ordinal); - } - } - } - } - - return comparison; - } - - /// - /// Changes all of the parent references to point to the passed in parent reference. - /// - /// this - /// New parent complex reference. - public static void Reparent(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol parent) - { - symbol.Parent = parent.Parent; - symbol.ParentLanguage = parent.ParentLanguage; - symbol.ParentType = parent.ParentType; - - if (!symbol.IsPrimary) - { - symbol.IsPrimary = parent.IsPrimary; - } - } - } -} diff --git a/src/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/WixToolset.Core/Link/WixGroupingOrdering.cs deleted file mode 100644 index f9de82a9..00000000 --- a/src/WixToolset.Core/Link/WixGroupingOrdering.cs +++ /dev/null @@ -1,683 +0,0 @@ -// 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.Link -{ - using System; - using System.Collections.ObjectModel; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - using WixToolset.Data.Burn; - - /// - /// Grouping and Ordering class of the WiX toolset. - /// - internal class WixGroupingOrdering - { - private readonly IMessaging Messaging; - private List groupTypes; - private List itemTypes; - private ItemCollection items; - private readonly List symbolsUsed; - private bool loaded; - - /// - /// Creates a WixGroupingOrdering object. - /// - /// Output from which to read the group and order information. - /// Handler for any error messages. - public WixGroupingOrdering(IntermediateSection entrySections, IMessaging messageHandler) - { - this.EntrySection = entrySections; - this.Messaging = messageHandler; - - this.symbolsUsed = new List(); - this.loaded = false; - } - - private IntermediateSection EntrySection { get; } - - /// - /// Switches a WixGroupingOrdering object to operate on a new set of groups/items. - /// - /// Group types to include. - /// Item types to include. - public void UseTypes(IEnumerable groupTypes, IEnumerable itemTypes) - { - this.groupTypes = new List(groupTypes.Select(g => g.ToString())); - this.itemTypes = new List(itemTypes.Select(i => i.ToString())); - - this.items = new ItemCollection(); - this.loaded = false; - } - - /// - /// Finds all nested items under a parent group and creates new WixGroup data for them. - /// - /// The group type for the parent group to flatten. - /// The identifier of the parent group to flatten. - /// Whether to remove used group rows before returning. - public void FlattenAndRewriteRows(ComplexReferenceParentType parentType, string parentId, bool removeUsedRows) - { - var parentTypeString = parentType.ToString(); - Debug.Assert(this.groupTypes.Contains(parentTypeString)); - - this.CreateOrderedList(parentTypeString, parentId, out var orderedItems); - if (this.Messaging.EncounteredError) - { - return; - } - - this.CreateNewGroupRows(parentTypeString, parentId, orderedItems); - - if (removeUsedRows) - { - this.RemoveUsedGroupRows(); - } - } - - /// - /// Finds all items under a parent group type and creates new WixGroup data for them. - /// - /// The type of the parent group to flatten. - /// Whether to remove used group rows before returning. - public void FlattenAndRewriteGroups(ComplexReferenceParentType parentType, bool removeUsedRows) - { - var parentTypeString = parentType.ToString(); - Debug.Assert(this.groupTypes.Contains(parentTypeString)); - - this.LoadFlattenOrderGroups(); - if (this.Messaging.EncounteredError) - { - return; - } - - foreach (Item item in this.items) - { - if (parentTypeString == item.Type) - { - this.CreateOrderedList(item.Type, item.Id, out var orderedItems); - this.CreateNewGroupRows(item.Type, item.Id, orderedItems); - } - } - - if (removeUsedRows) - { - this.RemoveUsedGroupRows(); - } - } - - - /// - /// Creates a flattened and ordered list of items for the given parent group. - /// - /// The group type for the parent group to flatten. - /// The identifier of the parent group to flatten. - /// The returned list of ordered items. - private void CreateOrderedList(string parentType, string parentId, out List orderedItems) - { - orderedItems = null; - - this.LoadFlattenOrderGroups(); - if (this.Messaging.EncounteredError) - { - return; - } - - if (!this.items.TryGetValue(parentType, parentId, out var parentItem)) - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound(parentType, parentId)); - return; - } - - orderedItems = new List(parentItem.ChildItems); - orderedItems.Sort(new Item.AfterItemComparer()); - } - - /// - /// Removes rows from WixGroup that have been used by this object. - /// - public void RemoveUsedGroupRows() - { - foreach (var symbol in this.symbolsUsed) - { - this.EntrySection.RemoveSymbol(symbol); - } - } - - /// - /// Creates new WixGroup rows for a list of items. - /// - /// The group type for the parent group in the new rows. - /// The identifier of the parent group in the new rows. - /// The list of new items. - private void CreateNewGroupRows(string parentType, string parentId, List orderedItems) - { - // TODO: MSIs don't guarantee that rows stay in the same order, and technically, neither - // does WiX (although they do, currently). We probably want to "upgrade" this to a new - // table that includes a sequence number, and then change the code that uses ordered - // groups to read from that table instead. - foreach (var item in orderedItems) - { - this.EntrySection.AddSymbol(new WixGroupSymbol(item.Row.SourceLineNumbers) - { - ParentId = parentId, - ParentType = (ComplexReferenceParentType)Enum.Parse(typeof(ComplexReferenceParentType), parentType), - ChildId = item.Id, - ChildType = (ComplexReferenceChildType)Enum.Parse(typeof(ComplexReferenceChildType), item.Type), - }); - } - } - - // Group/Ordering Flattening Logic - // - // What follows is potentially convoluted logic. Two somewhat orthogonal concepts are in - // play: grouping (parent/child relationships) and ordering (before/after relationships). - // Dealing with just one or the other is straghtforward. Groups can be flattened - // recursively. Ordering can be propagated in either direction. When the ordering also - // participates in the grouping constructions, however, things get trickier. For the - // purposes of this discussion, we're dealing with "items" and "groups", and an instance - // of either of them can be marked as coming "after" some other instance. - // - // For simple item-to-item ordering, the "after" values simply propagate: if A is after B, - // and B is after C, then we can say that A is after *both* B and C. If a group is involved, - // it acts as a proxy for all of its included items and any sub-groups. - - /// - /// Internal workhorse for ensuring that group and ordering information has - /// been loaded and applied. - /// - private void LoadFlattenOrderGroups() - { - if (!this.loaded) - { - this.LoadGroups(); - this.LoadOrdering(); - - // It would be really nice to have a "find circular after dependencies" - // function, but it gets much more complicated because of the way that - // the dependencies are propagated across group boundaries. For now, we - // just live with the dependency loop detection as we flatten the - // dependencies. Group references, however, we can check directly. - this.FindCircularGroupReferences(); - - if (!this.Messaging.EncounteredError) - { - this.FlattenGroups(); - this.FlattenOrdering(); - } - - this.loaded = true; - } - } - - /// - /// Loads data from the WixGroup table. - /// - private void LoadGroups() - { - //Table wixGroupTable = this.output.Tables["WixGroup"]; - //if (null == wixGroupTable || 0 == wixGroupTable.Rows.Count) - //{ - // // TODO: Change message name to make it *not* Bundle specific? - // this.Write(WixErrors.MissingBundleInformation("WixGroup")); - //} - - // Collect all of the groups - foreach (var symbol in this.EntrySection.Symbols.OfType()) - { - var rowParentName = symbol.ParentId; - var rowParentType = symbol.ParentType.ToString(); - var rowChildName = symbol.ChildId; - var rowChildType = symbol.ChildType.ToString(); - - // If this row specifies a parent or child type that's not in our - // lists, we assume it's not a row that we're concerned about. - if (!this.groupTypes.Contains(rowParentType) || - !this.itemTypes.Contains(rowChildType)) - { - continue; - } - - this.symbolsUsed.Add(symbol); - - if (!this.items.TryGetValue(rowParentType, rowParentName, out var parentItem)) - { - parentItem = new Item(symbol, rowParentType, rowParentName); - this.items.Add(parentItem); - } - - if (!this.items.TryGetValue(rowChildType, rowChildName, out var childItem)) - { - childItem = new Item(symbol, rowChildType, rowChildName); - this.items.Add(childItem); - } - - parentItem.ChildItems.Add(childItem); - } - } - - /// - /// Flattens group/item information. - /// - private void FlattenGroups() - { - foreach (Item item in this.items) - { - item.FlattenChildItems(); - } - } - - /// - /// Finds and reports circular references in the group/item data. - /// - private void FindCircularGroupReferences() - { - ItemCollection itemsInKnownLoops = new ItemCollection(); - foreach (Item item in this.items) - { - if (itemsInKnownLoops.Contains(item)) - { - continue; - } - - ItemCollection itemsSeen = new ItemCollection(); - string circularReference; - if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference)) - { - itemsInKnownLoops.Add(itemsSeen); - this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference)); - } - } - } - - /// - /// Recursive worker to find and report circular references in group/item data. - /// - /// The sentinal item being checked. - /// The current item in the recursion. - /// A list of all items already visited (for performance). - /// A list of items in the current circular reference, if one was found; null otherwise. - /// True if a circular reference was found; false otherwise. - private bool FindCircularGroupReference(Item checkItem, Item currentItem, ItemCollection itemsSeen, out string circularReference) - { - circularReference = null; - foreach (Item subitem in currentItem.ChildItems) - { - if (checkItem == subitem) - { - // TODO: Even better would be to include the source lines for each reference! - circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3}", - currentItem.Type, currentItem.Id, subitem.Type, subitem.Id); - return true; - } - - if (!itemsSeen.Contains(subitem)) - { - itemsSeen.Add(subitem); - if (this.FindCircularGroupReference(checkItem, subitem, itemsSeen, out circularReference)) - { - // TODO: Even better would be to include the source lines for each reference! - circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}", - currentItem.Type, currentItem.Id, circularReference); - return true; - } - } - } - - return false; - } - - /// - /// Loads ordering dependency data from the WixOrdering table. - /// - private void LoadOrdering() - { - //Table wixOrderingTable = output.Tables["WixOrdering"]; - //if (null == wixOrderingTable || 0 == wixOrderingTable.Rows.Count) - //{ - // // TODO: Do we need a message here? - // return; - //} - - foreach (var row in this.EntrySection.Symbols.OfType()) - { - var rowItemType = row.ItemType.ToString(); - var rowItemName = row.ItemIdRef; - var rowDependsOnType = row.DependsOnType.ToString(); - var rowDependsOnName = row.DependsOnIdRef; - - // If this row specifies some other (unknown) type in either - // position, we assume it's not a row that we're concerned about. - // For ordering, we allow group and item in either position. - if (!(this.groupTypes.Contains(rowItemType) || this.itemTypes.Contains(rowItemType)) || - !(this.groupTypes.Contains(rowDependsOnType) || this.itemTypes.Contains(rowDependsOnType))) - { - continue; - } - - if (!this.items.TryGetValue(rowItemType, rowItemName, out var item)) - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowItemType, rowItemName)); - } - - if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out var dependsOn)) - { - this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowDependsOnType, rowDependsOnName)); - } - - if (null == item || null == dependsOn) - { - continue; - } - - item.AddAfter(dependsOn, this.Messaging); - } - } - - /// - /// Flattens the ordering dependencies in the groups/items. - /// - private void FlattenOrdering() - { - // Because items don't know about their parent groups (and can, in fact, be - // in more than one group at a time), we need to pre-propagate the 'afters' - // from each parent item to its children before we attempt to flatten the - // ordering. - foreach (Item item in this.items) - { - item.PropagateAfterToChildItems(this.Messaging); - } - - foreach (Item item in this.items) - { - item.FlattenAfters(this.Messaging); - } - } - - /// - /// A variant of KeyedCollection that doesn't throw when an item is re-added. - /// - /// Key type for the collection. - /// Item type for the colelction. - internal abstract class EnhancedKeyCollection : KeyedCollection - { - new public void Add(TItem item) - { - if (!this.Contains(item)) - { - base.Add(item); - } - } - - public void Add(Collection list) - { - foreach (TItem item in list) - { - this.Add(item); - } - } - - public void Remove(Collection list) - { - foreach (TItem item in list) - { - this.Remove(item); - } - } - - public bool TryGetValue(TKey key, out TItem item) - { - // KeyedCollection doesn't implement the TryGetValue() method, but it's - // a useful concept. We can't just always pass this to the enclosed - // Dictionary, however, because it doesn't always exist! If it does, we - // can delegate to it as one would expect. If it doesn't, we have to - // implement everything ourselves in terms of Contains(). - - if (null != this.Dictionary) - { - return this.Dictionary.TryGetValue(key, out item); - } - - if (this.Contains(key)) - { - item = this[key]; - return true; - } - - item = default(TItem); - return false; - } - -#if DEBUG - // This just makes debugging easier... - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - foreach (TItem item in this) - { - sb.AppendFormat("{0}, ", item); - } - sb.Length -= 2; - return sb.ToString(); - } -#endif // DEBUG - } - - /// - /// A specialized EnhancedKeyCollection, typed to Items. - /// - internal class ItemCollection : EnhancedKeyCollection - { - protected override string GetKeyForItem(Item item) - { - return item.Key; - } - - public bool TryGetValue(string type, string id, out Item item) - { - return this.TryGetValue(CreateKeyFromTypeId(type, id), out item); - } - - public static string CreateKeyFromTypeId(string type, string id) - { - return String.Format(CultureInfo.InvariantCulture, "{0}_{1}", type, id); - } - } - - /// - /// An item (or group) in the grouping/ordering engine. - /// - /// Encapsulates nested group membership and also before/after - /// ordering dependencies. - internal class Item - { - private readonly ItemCollection afterItems; - private readonly ItemCollection beforeItems; // for checking for circular references - private bool flattenedAfterItems; - - public Item(IntermediateSymbol row, string type, string id) - { - this.Row = row; - this.Type = type; - this.Id = id; - - this.Key = ItemCollection.CreateKeyFromTypeId(type, id); - - this.afterItems = new ItemCollection(); - this.beforeItems = new ItemCollection(); - this.flattenedAfterItems = false; - } - - public IntermediateSymbol Row { get; private set; } - public string Type { get; private set; } - public string Id { get; private set; } - public string Key { get; private set; } - -#if DEBUG - // Makes debugging easier... - public override string ToString() - { - return this.Key; - } -#endif // DEBUG - - public ItemCollection ChildItems { get; } = new ItemCollection(); - - /// - /// Removes any nested groups under this item and replaces - /// them with their child items. - /// - public void FlattenChildItems() - { - ItemCollection flattenedChildItems = new ItemCollection(); - - foreach (Item childItem in this.ChildItems) - { - if (0 == childItem.ChildItems.Count) - { - flattenedChildItems.Add(childItem); - } - else - { - childItem.FlattenChildItems(); - flattenedChildItems.Add(childItem.ChildItems); - } - } - - this.ChildItems.Clear(); - this.ChildItems.Add(flattenedChildItems); - } - - /// - /// Adds a list of items to the 'after' ordering collection. - /// - /// List of items to add. - /// Message handler in case a circular ordering reference is found. - public void AddAfter(ItemCollection items, IMessaging messageHandler) - { - foreach (Item item in items) - { - this.AddAfter(item, messageHandler); - } - } - - /// - /// Adds an item to the 'after' ordering collection. - /// - /// Item to add. - /// Message handler in case a circular ordering reference is found. - public void AddAfter(Item after, IMessaging messageHandler) - { - if (this.beforeItems.Contains(after)) - { - // We could try to chain this up (the way that group circular dependencies - // are reported), but since we're in the process of flattening, we may already - // have lost some distinction between authored and propagated ordering. - string circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3} -> {0}:{1}", - this.Type, this.Id, after.Type, after.Id); - messageHandler.Write(ErrorMessages.OrderingReferenceLoopDetected(after.Row.SourceLineNumbers, circularReference)); - return; - } - - this.afterItems.Add(after); - after.beforeItems.Add(this); - } - - /// - /// Propagates 'after' dependencies from an item to its child items. - /// - /// Message handler in case a circular ordering reference is found. - /// Because items don't know about their parent groups (and can, in fact, be in more - /// than one group at a time), we need to propagate the 'afters' from each parent item to its children - /// before we attempt to flatten the ordering. - public void PropagateAfterToChildItems(IMessaging messageHandler) - { - if (this.ShouldItemPropagateChildOrdering()) - { - foreach (Item childItem in this.ChildItems) - { - childItem.AddAfter(this.afterItems, messageHandler); - } - } - } - - /// - /// Flattens the ordering dependency for this item. - /// - /// Message handler in case a circular ordering reference is found. - public void FlattenAfters(IMessaging messageHandler) - { - if (this.flattenedAfterItems) - { - return; - } - - this.flattenedAfterItems = true; - - // Ensure that if we're after something (A), and *it's* after something (B), - // that we list ourselved as after both (A) *and* (B). - ItemCollection nestedAfterItems = new ItemCollection(); - - foreach (Item afterItem in this.afterItems) - { - afterItem.FlattenAfters(messageHandler); - nestedAfterItems.Add(afterItem.afterItems); - - if (afterItem.ShouldItemPropagateChildOrdering()) - { - // If we are after a group, it really means - // we are after all of the group's children. - foreach (Item childItem in afterItem.ChildItems) - { - childItem.FlattenAfters(messageHandler); - nestedAfterItems.Add(childItem.afterItems); - nestedAfterItems.Add(childItem); - } - } - } - - this.AddAfter(nestedAfterItems, messageHandler); - } - - // We *don't* propagate ordering information from Packages or - // Containers to their children, because ordering doesn't matter - // for them, and a Payload in two Packages (or Containers) can - // cause a circular reference to occur. - private bool ShouldItemPropagateChildOrdering() - { - if (String.Equals(nameof(ComplexReferenceParentType.Package), this.Type, StringComparison.Ordinal) || - String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal)) - { - return false; - } - return true; - } - - /// - /// Helper IComparer class to make ordering easier. - /// - internal class AfterItemComparer : IComparer - { - public int Compare(Item x, Item y) - { - if (x.afterItems.Contains(y)) - { - return 1; - } - else if (y.afterItems.Contains(x)) - { - return -1; - } - - return String.CompareOrdinal(x.Id, y.Id); - } - } - } - } -} diff --git a/src/WixToolset.Core/LinkContext.cs b/src/WixToolset.Core/LinkContext.cs deleted file mode 100644 index b99bb9c4..00000000 --- a/src/WixToolset.Core/LinkContext.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class LinkContext : ILinkContext - { - internal LinkContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IReadOnlyCollection Extensions { get; set; } - - public IReadOnlyCollection ExtensionData { get; set; } - - public OutputType ExpectedOutputType { get; set; } - - public IReadOnlyCollection Intermediates { get; set; } - - public ISymbolDefinitionCreator SymbolDefinitionCreator { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs deleted file mode 100644 index 47671f26..00000000 --- a/src/WixToolset.Core/Linker.cs +++ /dev/null @@ -1,942 +0,0 @@ -// 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 -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.Linq; - using WixToolset.Core.Link; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Linker core of the WiX toolset. - /// - internal class Linker : ILinker - { - private static readonly string EmptyGuid = Guid.Empty.ToString("B"); - - private readonly bool sectionIdOnRows; - - /// - /// Creates a linker. - /// - internal Linker(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.Messaging = this.ServiceProvider.GetService(); - this.sectionIdOnRows = true; // TODO: what is the correct value for this? - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - private ILinkContext Context { get; set; } - - /// - /// Gets or sets the path to output unreferenced symbols to. If null or empty, there is no output. - /// - /// The path to output the xml file. - public string UnreferencedSymbolsFile { get; set; } - - /// - /// Gets or sets the option to show pedantic messages. - /// - /// The option to show pedantic messages. - public bool ShowPedanticMessages { get; set; } - - /// - /// Links a collection of sections into an output. - /// - /// Output intermediate from the linking. - public Intermediate Link(ILinkContext context) - { - this.Context = context; - - if (this.Context.SymbolDefinitionCreator == null) - { - this.Context.SymbolDefinitionCreator = this.ServiceProvider.GetService(); - } - - foreach (var extension in this.Context.Extensions) - { - extension.PreLink(this.Context); - } - - var invalidIntermediates = this.Context.Intermediates.Where(i => !i.HasLevel(Data.IntermediateLevels.Compiled)); - if (invalidIntermediates.Any()) - { - this.Messaging.Write(ErrorMessages.IntermediatesMustBeCompiled(String.Join(", ", invalidIntermediates.Select(i => i.Id)))); - } - - Intermediate intermediate = null; - try - { - var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList(); - var localizations = this.Context.Intermediates.SelectMany(i => i.Localizations).ToList(); - - // Add sections from the extensions with data. - foreach (var data in this.Context.ExtensionData) - { - var library = data.GetLibrary(this.Context.SymbolDefinitionCreator); - - if (library != null) - { - sections.AddRange(library.Sections); - } - } - - //this.activeOutput = null; - - var multipleFeatureComponents = new Hashtable(); - - var wixVariables = new Dictionary(); - - // First find the entry section and while processing all sections load all the symbols from all of the sections. - var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, sections, this.Context.ExpectedOutputType); - find.Execute(); - - // Must have found the entry section by now. - if (null == find.EntrySection) - { - if (this.Context.ExpectedOutputType == OutputType.IntermediatePostLink || this.Context.ExpectedOutputType == OutputType.Unknown) - { - throw new WixException(ErrorMessages.MissingEntrySection()); - } - else - { - throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString())); - } - } - - // Add the missing standard action and directory symbols. - this.LoadStandardSymbols(find.SymbolsByName); - - // Resolve the symbol references to find the set of sections we care about for linking. - // Of course, we start with the entry section (that's how it got its name after all). - var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName); - resolve.Execute(); - - if (this.Messaging.EncounteredError) - { - return null; - } - - // Reset the sections to only those that were resolved then flatten the complex - // references that particpate in groups. - sections = resolve.ResolvedSections.ToList(); - - // TODO: consider filtering "localizations" down to only those localizations from - // intermediates in the sections. - - this.FlattenSectionsComplexReferences(sections); - - if (this.Messaging.EncounteredError) - { - return null; - } - - // The hard part in linking is processing the complex references. - var referencedComponents = new HashSet(); - var componentsToFeatures = new ConnectToFeatureCollection(); - var featuresToFeatures = new ConnectToFeatureCollection(); - var modulesToFeatures = new ConnectToFeatureCollection(); - this.ProcessComplexReferences(find.EntrySection, sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures); - - if (this.Messaging.EncounteredError) - { - return null; - } - - // Display an error message for Components that were not referenced by a Feature. - foreach (var symbolWithSection in resolve.ReferencedSymbolWithSections.Where(s => s.Symbol.Definition.Type == SymbolDefinitionType.Component)) - { - if (!referencedComponents.Contains(symbolWithSection.Name)) - { - this.Messaging.Write(ErrorMessages.OrphanedComponent(symbolWithSection.Symbol.SourceLineNumbers, symbolWithSection.Symbol.Id.Id)); - } - } - - // Report duplicates that would ultimately end up being primary key collisions. - { - var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections); - reportDupes.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return null; - } - - // resolve the feature to feature connects - this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.SymbolsByName); - - // Create a new section to hold the linked content. Start with the entry section's - // metadata. - var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type); - - var sectionCount = 0; - - foreach (var section in sections) - { - sectionCount++; - - var sectionId = section.Id; - if (null == sectionId && this.sectionIdOnRows) - { - sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture); - } - - foreach (var symbol in section.Symbols) - { - if (find.RedundantSymbols.Contains(symbol)) - { - continue; - } - - var copySymbol = true; // by default, copy symbols. - - // handle special tables - switch (symbol.Definition.Type) - { - case SymbolDefinitionType.Class: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)ClassSymbolFields.ComponentRef, (int)ClassSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.Extension: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)ExtensionSymbolFields.ComponentRef, (int)ExtensionSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.Assembly: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)AssemblySymbolFields.ComponentRef, (int)AssemblySymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.PublishComponent: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)PublishComponentSymbolFields.ComponentRef, (int)PublishComponentSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.Shortcut: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)ShortcutSymbolFields.ComponentRef, (int)ShortcutSymbolFields.Target, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.TypeLib: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, (int)TypeLibSymbolFields.ComponentRef, (int)TypeLibSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); - } - break; - - case SymbolDefinitionType.WixMerge: - if (SectionType.Product == resolvedSection.Type) - { - this.ResolveFeatures(symbol, -1, (int)WixMergeSymbolFields.FeatureRef, modulesToFeatures, null); - } - break; - - case SymbolDefinitionType.WixComplexReference: - copySymbol = false; - break; - - case SymbolDefinitionType.WixSimpleReference: - copySymbol = false; - break; - - case SymbolDefinitionType.WixVariable: - this.AddWixVariable(wixVariables, (WixVariableSymbol)symbol); - copySymbol = false; // Do not copy the symbol, it will be added later after all overriding has been handled. - break; - } - - if (copySymbol) - { - resolvedSection.AddSymbol(symbol); - } - } - } - - // Copy the module to feature connections into the output. - foreach (ConnectToFeature connectToFeature in modulesToFeatures) - { - foreach (var feature in connectToFeature.ConnectFeatures) - { - resolvedSection.AddSymbol(new WixFeatureModulesSymbol - { - FeatureRef = feature, - WixMergeRef = connectToFeature.ChildId - }); - } - } - - // Correct the section Id in FeatureComponents table. - if (this.sectionIdOnRows) - { -#if TODO_DO_SYMBOLS_NEED_SECTIONIDS - var componentSectionIds = resolvedSection.Symbols.OfType().ToDictionary(c => c.Id.Id, c => c.SectionId); - - foreach (var featureComponentSymbol in resolvedSection.Symbols.OfType()) - { - if (componentSectionIds.TryGetValue(featureComponentSymbol.ComponentRef, out var componentSectionId)) - { - featureComponentSymbol.SectionId = componentSectionId; - } - } -#endif - } - - // Copy the wix variable rows to the output now that all overriding has been accounted for. - foreach (var symbol in wixVariables.Values) - { - resolvedSection.AddSymbol(symbol); - } - - // Bundles have groups of data that must be flattened in a way different from other types. - if (resolvedSection.Type == SectionType.Bundle) - { - var command = new FlattenAndProcessBundleTablesCommand(resolvedSection, this.Messaging); - command.Execute(); - } - - if (this.Messaging.EncounteredError) - { - return null; - } - - var collate = new CollateLocalizationsCommand(this.Messaging, localizations); - var localizationsByCulture = collate.Execute(); - - intermediate = new Intermediate(resolvedSection.Id, Data.IntermediateLevels.Linked, new[] { resolvedSection }, localizationsByCulture); - } - finally - { - foreach (var extension in this.Context.Extensions) - { - extension.PostLink(intermediate); - } - } - - return this.Messaging.EncounteredError ? null : intermediate; - } - - /// - /// Check for colliding values and collect the wix variable rows. - /// - /// Collection of WixVariableSymbols by id. - /// WixVariableSymbol to add, if not overridden. - private void AddWixVariable(Dictionary wixVariables, WixVariableSymbol symbol) - { - var id = symbol.Id.Id; - - if (wixVariables.TryGetValue(id, out var collidingSymbol)) - { - if (collidingSymbol.Overridable && !symbol.Overridable) - { - wixVariables[id] = symbol; - } - else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable)) - { - this.Messaging.Write(ErrorMessages.WixVariableCollision(symbol.SourceLineNumbers, id)); - } - } - else - { - wixVariables.Add(id, symbol); - } - } - - /// - /// Load the standard action and directory symbols. - /// - /// Collection of symbols. - private void LoadStandardSymbols(IDictionary symbolsByName) - { - foreach (var actionSymbol in WindowsInstallerStandard.StandardActions()) - { - var symbolWithSection = new SymbolWithSection(null, actionSymbol); - - // If the action's symbol has not already been defined (i.e. overriden by the user), add it now. - if (!symbolsByName.ContainsKey(symbolWithSection.Name)) - { - symbolsByName.Add(symbolWithSection.Name, symbolWithSection); - } - } - - foreach (var directorySymbol in WindowsInstallerStandard.StandardDirectories()) - { - var symbolWithSection = new SymbolWithSection(null, directorySymbol); - - // If the directory's symbol has not already been defined (i.e. overriden by the user), add it now. - if (!symbolsByName.ContainsKey(symbolWithSection.Name)) - { - symbolsByName.Add(symbolWithSection.Name, symbolWithSection); - } - } - } - - /// - /// Process the complex references. - /// - /// Active section to add symbols to. - /// Sections that are referenced during the link process. - /// Collection of all components referenced by complex reference. - /// Component to feature complex references. - /// Feature to feature complex references. - /// Module to feature complex references. - private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnumerable sections, ISet referencedComponents, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures) - { - var componentsToModules = new Hashtable(); - - foreach (var section in sections) - { - // Need ToList since we might want to add symbols while processing. - foreach (var wixComplexReferenceRow in section.Symbols.OfType().ToList()) - { - ConnectToFeature connection; - switch (wixComplexReferenceRow.ParentType) - { - case ComplexReferenceParentType.Feature: - switch (wixComplexReferenceRow.ChildType) - { - case ComplexReferenceChildType.Component: - connection = componentsToFeatures[wixComplexReferenceRow.Child]; - if (null == connection) - { - componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); - } - else if (wixComplexReferenceRow.IsPrimary) - { - if (connection.IsExplicitPrimaryFeature) - { - this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), connection.PrimaryFeature ?? resolvedSection.Id)); - continue; - } - else - { - connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects - connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature - connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again - } - } - else - { - connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); - } - - // add a row to the FeatureComponents table - section.AddSymbol(new FeatureComponentsSymbol - { - FeatureRef = wixComplexReferenceRow.Parent, - ComponentRef = wixComplexReferenceRow.Child, - }); - - // index the component for finding orphaned records - var symbolName = String.Concat("Component:", wixComplexReferenceRow.Child); - referencedComponents.Add(symbolName); - - break; - - case ComplexReferenceChildType.Feature: - connection = featuresToFeatures[wixComplexReferenceRow.Child]; - if (null != connection) - { - this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); - continue; - } - - featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); - break; - - case ComplexReferenceChildType.Module: - connection = modulesToFeatures[wixComplexReferenceRow.Child]; - if (null == connection) - { - modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); - } - else if (wixComplexReferenceRow.IsPrimary) - { - if (connection.IsExplicitPrimaryFeature) - { - this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); - continue; - } - else - { - connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects - connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature - connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again - } - } - else - { - connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); - } - break; - - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); - } - break; - - case ComplexReferenceParentType.Module: - switch (wixComplexReferenceRow.ChildType) - { - case ComplexReferenceChildType.Component: - if (componentsToModules.ContainsKey(wixComplexReferenceRow.Child)) - { - this.Messaging.Write(ErrorMessages.ComponentReferencedTwice(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.Child)); - continue; - } - else - { - componentsToModules.Add(wixComplexReferenceRow.Child, wixComplexReferenceRow); // should always be new - - // add a row to the ModuleComponents table - section.AddSymbol(new ModuleComponentsSymbol - { - Component = wixComplexReferenceRow.Child, - ModuleID = wixComplexReferenceRow.Parent, - Language = Convert.ToInt32(wixComplexReferenceRow.ParentLanguage), - }); - } - - // index the component for finding orphaned records - var componentSymbolName = String.Concat("Component:", wixComplexReferenceRow.Child); - referencedComponents.Add(componentSymbolName); - - break; - - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); - } - break; - - case ComplexReferenceParentType.Patch: - switch (wixComplexReferenceRow.ChildType) - { - case ComplexReferenceChildType.PatchFamily: - case ComplexReferenceChildType.PatchFamilyGroup: - break; - - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); - } - break; - - case ComplexReferenceParentType.Product: - switch (wixComplexReferenceRow.ChildType) - { - case ComplexReferenceChildType.Feature: - connection = featuresToFeatures[wixComplexReferenceRow.Child]; - if (null != connection) - { - this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); - continue; - } - - featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary)); - break; - - default: - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); - } - break; - - default: - // Note: Groups have been processed before getting here so they are not handled by any case above. - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType))); - } - } - } - } - - /// - /// Flattens all complex references in all sections in the collection. - /// - /// Sections that are referenced during the link process. - private void FlattenSectionsComplexReferences(IEnumerable sections) - { - var parentGroups = new Dictionary>(); - var parentGroupsSections = new Dictionary(); - var parentGroupsNeedingProcessing = new Dictionary(); - - // DisplaySectionComplexReferences("--- section's complex references before flattening ---", sections); - - // Step 1: Gather all of the complex references that are going to participate - // in the flatting process. This means complex references that have "grouping - // parents" of Features, Modules, and, of course, Groups. These references - // that participate in a "grouping parent" will be removed from their section - // now and after processing added back in Step 3 below. - foreach (var section in sections) - { - var removeSymbols = new List(); - - foreach (var symbol in section.Symbols) - { - // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature, - // and Module. Non-grouping complex references are simple and - // resolved during normal complex reference resolutions. - if (symbol is WixComplexReferenceSymbol wixComplexReferenceRow && - (ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || - ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || - ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || - ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || - ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || - ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType)) - { - var parentTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent); - - // Group all complex references with a common parent - // together so we can find them quickly while processing in - // Step 2. - if (!parentGroups.TryGetValue(parentTypeAndId, out var childrenComplexRefs)) - { - childrenComplexRefs = new List(); - parentGroups.Add(parentTypeAndId, childrenComplexRefs); - } - - childrenComplexRefs.Add(wixComplexReferenceRow); - removeSymbols.Add(wixComplexReferenceRow); - - // Remember the mapping from set of complex references with a common - // parent to their section. We'll need this to add them back to the - // correct section in Step 3. - if (!parentGroupsSections.TryGetValue(parentTypeAndId, out var parentSection)) - { - parentGroupsSections.Add(parentTypeAndId, section); - } - - // If the child of the complex reference is another group, then in Step 2 - // we're going to have to process this complex reference again to copy - // the child group's references into the parent group. - if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) - { - if (!parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)) - { - parentGroupsNeedingProcessing.Add(parentTypeAndId, section); - } - } - } - } - - foreach (var removeSymbol in removeSymbols) - { - section.RemoveSymbol(removeSymbol); - } - } - - Debug.Assert(parentGroups.Count == parentGroupsSections.Count); - Debug.Assert(parentGroupsNeedingProcessing.Count <= parentGroups.Count); - - // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references middle of flattening ---", sections); - - // Step 2: Loop through the parent groups that have nested groups removing - // them from the hash table as they are processed. At the end of this the - // complex references should all be flattened. - var keys = parentGroupsNeedingProcessing.Keys.ToList(); - - foreach (var key in keys) - { - if (parentGroupsNeedingProcessing.ContainsKey(key)) - { - var loopDetector = new Stack(); - this.FlattenGroup(key, loopDetector, parentGroups, parentGroupsNeedingProcessing); - } - else - { - // the group must have allready been procesed and removed from the hash table - } - } - Debug.Assert(0 == parentGroupsNeedingProcessing.Count); - - // Step 3: Finally, ensure that all of the groups that were removed - // in Step 1 and flattened in Step 2 are added to their appropriate - // section. This is where we will toss out the final no-longer-needed - // groups. - foreach (var parentGroup in parentGroups.Keys) - { - var section = parentGroupsSections[parentGroup]; - - foreach (var wixComplexReferenceRow in parentGroups[parentGroup]) - { - if ((ComplexReferenceParentType.FeatureGroup != wixComplexReferenceRow.ParentType) && - (ComplexReferenceParentType.ComponentGroup != wixComplexReferenceRow.ParentType) && - (ComplexReferenceParentType.PatchFamilyGroup != wixComplexReferenceRow.ParentType)) - { - section.AddSymbol(wixComplexReferenceRow); - } - } - } - - // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references after flattening ---", sections); - } - - private string CombineTypeAndId(ComplexReferenceParentType type, string id) - { - return String.Concat(type.ToString(), ":", id); - } - - private string CombineTypeAndId(ComplexReferenceChildType type, string id) - { - return String.Concat(type.ToString(), ":", id); - } - - /// - /// Recursively processes the group. - /// - /// String combination type and id of group to process next. - /// Stack of groups processed thus far. Used to detect loops. - /// Hash table of complex references grouped by parent id. - /// Hash table of parent groups that still have nested groups that need to be flattened. - private void FlattenGroup(string parentTypeAndId, Stack loopDetector, Dictionary> parentGroups, Dictionary parentGroupsNeedingProcessing) - { - Debug.Assert(parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)); - loopDetector.Push(parentTypeAndId); // push this complex reference parent identfier into the stack for loop verifying - - var allNewChildComplexReferences = new List(); - - var referencesToParent = parentGroups[parentTypeAndId]; - foreach (var wixComplexReferenceRow in referencesToParent) - { - Debug.Assert(ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Patch == wixComplexReferenceRow.ParentType); - Debug.Assert(parentTypeAndId == this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent)); - - // We are only interested processing when the child is a group. - if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) - { - var childTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ChildType, wixComplexReferenceRow.Child); - if (loopDetector.Contains(childTypeAndId)) - { - // Create a comma delimited list of the references that participate in the - // loop for the error message. Start at the bottom of the stack and work the - // way up to present the loop as a directed graph. - var loop = String.Join(" -> ", loopDetector); - - this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(wixComplexReferenceRow?.SourceLineNumbers, loop)); - - // Cleanup the parentGroupsNeedingProcessing and the loopDetector just like the - // exit of this method does at the end because we are exiting early. - loopDetector.Pop(); - parentGroupsNeedingProcessing.Remove(parentTypeAndId); - - return; // bail - } - - // Check to see if the child group still needs to be processed. If so, - // go do that so that we'll get all of that children's (and children's - // children) complex references correctly merged into our parent group. - if (parentGroupsNeedingProcessing.ContainsKey(childTypeAndId)) - { - this.FlattenGroup(childTypeAndId, loopDetector, parentGroups, parentGroupsNeedingProcessing); - } - - // If the child is a parent to anything (i.e. the parent has grandchildren) - // clone each of the children's complex references, repoint them to the parent - // complex reference (because we're moving references up the tree), and finally - // add the cloned child's complex reference to the list of complex references - // that we'll eventually add to the parent group. - if (parentGroups.TryGetValue(childTypeAndId, out var referencesToChild)) - { - foreach (var crefChild in referencesToChild) - { - // Only merge up the non-group items since groups are purged - // after this part of the processing anyway (cloning them would - // be a complete waste of time). - if ((ComplexReferenceChildType.FeatureGroup != crefChild.ChildType) || - (ComplexReferenceChildType.ComponentGroup != crefChild.ChildType) || - (ComplexReferenceChildType.PatchFamilyGroup != crefChild.ChildType)) - { - var crefChildClone = crefChild.Clone(); - Debug.Assert(crefChildClone.Parent == wixComplexReferenceRow.Child); - - crefChildClone.Reparent(wixComplexReferenceRow); - allNewChildComplexReferences.Add(crefChildClone); - } - } - } - } - } - - // Add the children group's complex references to the parent - // group. Clean out any left over groups and quietly remove any - // duplicate complex references that occurred during the merge. - referencesToParent.AddRange(allNewChildComplexReferences); - referencesToParent.Sort(ComplexReferenceComparision); - for (var i = referencesToParent.Count - 1; i >= 0; --i) - { - var wixComplexReferenceRow = referencesToParent[i]; - - if ((ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || - (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) - { - referencesToParent.RemoveAt(i); - } - else if (i > 0) - { - // Since the list is already sorted, we can find duplicates by simply - // looking at the next sibling in the list and tossing out one if they - // match. - var crefCompare = referencesToParent[i - 1]; - if (0 == wixComplexReferenceRow.CompareToWithoutConsideringPrimary(crefCompare)) - { - referencesToParent.RemoveAt(i); - } - } - } - - int ComplexReferenceComparision(WixComplexReferenceSymbol x, WixComplexReferenceSymbol y) - { - var comparison = x.ChildType - y.ChildType; - if (0 == comparison) - { - comparison = String.Compare(x.Child, y.Child, StringComparison.Ordinal); - if (0 == comparison) - { - comparison = x.ParentType - y.ParentType; - if (0 == comparison) - { - comparison = String.Compare(x.ParentLanguage ?? String.Empty, y.ParentLanguage ?? String.Empty, StringComparison.Ordinal); - if (0 == comparison) - { - comparison = String.Compare(x.Parent, y.Parent, StringComparison.Ordinal); - } - } - } - } - - return comparison; - } - - loopDetector.Pop(); // pop this complex reference off the stack since we're done verify the loop here - parentGroupsNeedingProcessing.Remove(parentTypeAndId); // remove the newly processed complex reference - } - - /* - /// - /// Debugging method for displaying the section complex references. - /// - /// The header. - /// The sections to display. - private void DisplaySectionComplexReferences(string header, SectionCollection sections) - { - Console.WriteLine(header); - foreach (Section section in sections) - { - Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; - - foreach (WixComplexReferenceRow cref in wixComplexReferenceTable.Rows) - { - Console.WriteLine("Section: {0} Parent: {1} Type: {2} Child: {3} Primary: {4}", section.Id, cref.ParentId, cref.ParentType, cref.ChildId, cref.IsPrimary); - } - } - } - */ - - /// - /// Resolves the features connected to other features in the active output. - /// - /// Feature to feature complex references. - /// All symbols loaded from the sections. - private void ResolveFeatureToFeatureConnects(ConnectToFeatureCollection featuresToFeatures, IDictionary allSymbols) - { - foreach (ConnectToFeature connection in featuresToFeatures) - { - var wixSimpleReferenceRow = new WixSimpleReferenceSymbol - { - Table = "Feature", - PrimaryKeys = connection.ChildId - }; - - if (allSymbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbol)) - { - var featureSymbol = (FeatureSymbol)symbol.Symbol; - featureSymbol.ParentFeatureRef = connection.PrimaryFeature; - } - } - } - - /// - /// Resolve features for columns that have null guid placeholders. - /// - /// Symbol to resolve. - /// Number of the column containing the connection identifier. - /// Number of the column containing the feature. - /// Connect to feature complex references. - /// Hashtable of known components under multiple features. - private void ResolveFeatures(IntermediateSymbol symbol, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents) - { - var connectionId = connectionColumn < 0 ? symbol.Id.Id : symbol.AsString(connectionColumn); - var featureId = symbol.AsString(featureColumn); - - if (EmptyGuid == featureId) - { - var connection = connectToFeatures[connectionId]; - - if (null == connection) - { - // display an error for the component or merge module as appropriate - if (null != multipleFeatureComponents) - { - this.Messaging.Write(ErrorMessages.ComponentExpectedFeature(symbol.SourceLineNumbers, connectionId, symbol.Definition.Name, symbol.Id.Id)); - } - else - { - this.Messaging.Write(ErrorMessages.MergeModuleExpectedFeature(symbol.SourceLineNumbers, connectionId)); - } - } - else - { - // check for unique, implicit, primary feature parents with multiple possible parent features - if (this.ShowPedanticMessages && - !connection.IsExplicitPrimaryFeature && - 0 < connection.ConnectFeatures.Count) - { - // display a warning for the component or merge module as approrpriate - if (null != multipleFeatureComponents) - { - if (!multipleFeatureComponents.Contains(connectionId)) - { - this.Messaging.Write(WarningMessages.ImplicitComponentPrimaryFeature(connectionId)); - - // remember this component so only one warning is generated for it - multipleFeatureComponents[connectionId] = null; - } - } - else - { - this.Messaging.Write(WarningMessages.ImplicitMergeModulePrimaryFeature(connectionId)); - } - } - - // set the feature - symbol.Set(featureColumn, connection.PrimaryFeature); - } - } - } - } -} diff --git a/src/WixToolset.Core/LinkerErrors.cs b/src/WixToolset.Core/LinkerErrors.cs deleted file mode 100644 index 7ce8c00e..00000000 --- a/src/WixToolset.Core/LinkerErrors.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 -{ - using WixToolset.Data; - - internal static class LinkerErrors - { - public static Message OrphanedPayload(SourceLineNumber sourceLineNumbers, string payloadId) - { - return Message(sourceLineNumbers, Ids.OrphanedPayload, "Found orphaned Payload '{0}'. Make sure to reference it from a Package, the BootstrapperApplication, or the Bundle or move it into its own Fragment so it only gets linked in when actually used.", payloadId); - } - - public static Message PackageInMultipleContainers(SourceLineNumber sourceLineNumbers, string packageId, string containerId1, string containerId2) - { - return Message(sourceLineNumbers, Ids.PackageInMultipleContainers, "The Package '{0}' is referenced from multiple containers - Container '{1}' and Container '{2}'. This is not currently supported.", packageId, containerId1, containerId2); - } - - public static Message PayloadSharedWithBA(SourceLineNumber sourceLineNumbers, string payloadId) - { - return Message(sourceLineNumbers, Ids.PayloadSharedWithBA, "The Payload '{0}' is shared with the BootstrapperApplication. This is not currently supported.", payloadId); - } - - public static Message UnscheduledChainPackage(SourceLineNumber sourceLineNumbers, string packageId) - { - return Message(sourceLineNumbers, Ids.UnscheduledChainPackage, "Found orphaned Package '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", packageId); - } - - public static Message UnscheduledRollbackBoundary(SourceLineNumber sourceLineNumbers, string rollbackBoundaryId) - { - return Message(sourceLineNumbers, Ids.UnscheduledRollbackBoundary, "Found orphaned RollbackBoundary '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", rollbackBoundaryId); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); - } - - public enum Ids - { - OrphanedPayload = 7000, - PackageInMultipleContainers = 7001, - PayloadSharedWithBA = 7002, - UnscheduledChainPackage = 7003, - UnscheduledRollbackBoundary = 7004, - } // last available is 7099. 7100 is WindowsInstallerBackendWarnings. - } -} diff --git a/src/WixToolset.Core/LinkerWarnings.cs b/src/WixToolset.Core/LinkerWarnings.cs deleted file mode 100644 index 968fa4ea..00000000 --- a/src/WixToolset.Core/LinkerWarnings.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 -{ - using WixToolset.Data; - - internal static class LinkerWarnings - { - public static Message LayoutPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) - { - return Message(sourceLineNumbers, Ids.LayoutPayloadInContainer, "The layout-only Payload '{0}' is being added to Container '{1}'. It will not be extracted during layout.", payloadId, containerId); - } - - public static Message PayloadInMultipleContainers(SourceLineNumber sourceLineNumbers, string payloadId, string containerId1, string containerId2) - { - return Message(sourceLineNumbers, Ids.PayloadInMultipleContainers, "The Payload '{0}' can't be added to Container '{1}' because it was already added to Container '{2}'.", payloadId, containerId1, containerId2); - } - - public static Message UncompressedPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) - { - return Message(sourceLineNumbers, Ids.UncompressedPayloadInContainer, "The Payload '{0}' is being added to Container '{1}', overriding its Compressed value of 'no'.", payloadId, containerId); - } - - private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) - { - return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); - } - - public enum Ids - { - LayoutPayloadInContainer = 6900, - PayloadInMultipleContainers = 6901, - UncompressedPayloadInContainer = 6902, - } // last available is 6999. 7000 is LinkerErrors. - } -} diff --git a/src/WixToolset.Core/LocalizationParser.cs b/src/WixToolset.Core/LocalizationParser.cs deleted file mode 100644 index d6113fc6..00000000 --- a/src/WixToolset.Core/LocalizationParser.cs +++ /dev/null @@ -1,326 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Data.Bind; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - internal class LocalizationParser : ILocalizationParser - { - public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl"; - private const string XmlElementName = "WixLocalization"; - - internal LocalizationParser(IServiceProvider serviceProvider) - { - this.Messaging = serviceProvider.GetService(); - } - - private IMessaging Messaging { get; } - - public Localization ParseLocalization(string path) - { - var document = XDocument.Load(path); - return this.ParseLocalization(document); - } - - public Localization ParseLocalization(XDocument document) - { - var root = document.Root; - Localization localization = null; - - var sourceLineNumbers = SourceLineNumber.CreateFromXObject(root); - if (LocalizationParser.XmlElementName == root.Name.LocalName) - { - if (LocalizationParser.WxlNamespace == root.Name.Namespace) - { - localization = ParseWixLocalizationElement(this.Messaging, root); - } - else // invalid or missing namespace - { - if (null == root.Name.Namespace) - { - this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, LocalizationParser.WxlNamespace.NamespaceName)); - } - else - { - this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, root.Name.LocalName, LocalizationParser.WxlNamespace.NamespaceName)); - } - } - } - else - { - this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, root.Name.LocalName, "localization", LocalizationParser.XmlElementName)); - } - - return localization; - } - - /// - /// Adds a WixVariableRow to a dictionary while performing the expected override checks. - /// - /// - /// Dictionary of variable rows. - /// Row to add to the variables dictionary. - private static void AddWixVariable(IMessaging messaging, IDictionary variables, BindVariable wixVariableRow) - { - if (!variables.TryGetValue(wixVariableRow.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable)) - { - variables[wixVariableRow.Id] = wixVariableRow; - } - else if (!wixVariableRow.Overridable) - { - messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(wixVariableRow.SourceLineNumbers, wixVariableRow.Id)); - } - } - - /// - /// Parses the WixLocalization element. - /// - /// - /// Element to parse. - private static Localization ParseWixLocalizationElement(IMessaging messaging, XElement node) - { - var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); - int? codepage = null; - int? summaryInformationCodepage = null; - string culture = null; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Codepage": - codepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); - break; - case "SummaryInformationCodepage": - summaryInformationCodepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); - break; - case "Culture": - culture = attrib.Value; - break; - case "Language": - // do nothing; @Language is used for locutil which can't convert Culture to lcid - break; - default: - Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); - break; - } - } - else - { - Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); - } - } - - var variables = new Dictionary(); - var localizedControls = new Dictionary(); - - foreach (var child in node.Elements()) - { - if (LocalizationParser.WxlNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "String": - LocalizationParser.ParseString(messaging, child, variables); - break; - - case "UI": - LocalizationParser.ParseUI(messaging, child, localizedControls); - break; - - default: - messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); - break; - } - } - else - { - messaging.Write(ErrorMessages.UnsupportedExtensionElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); - } - } - - return messaging.EncounteredError ? null : new Localization(codepage, summaryInformationCodepage, culture, variables, localizedControls); - } - - /// - /// Parse a localization string into a WixVariableRow. - /// - /// - /// Element to parse. - /// - private static void ParseString(IMessaging messaging, XElement node, IDictionary variables) - { - string id = null; - var overridable = false; - var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); - break; - case "Overridable": - overridable = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - break; - case "Localizable": - ; // do nothing - break; - default: - messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); - break; - } - } - else - { - messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); - } - } - - var value = Common.GetInnerText(node); - - if (null == id) - { - messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "String", "Id")); - } - else if (0 == id.Length) - { - messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, "String", "Id", 0)); - } - - if (!messaging.EncounteredError) - { - var variable = new BindVariable - { - SourceLineNumbers = sourceLineNumbers, - Id = id, - Overridable = overridable, - Value = value, - }; - - LocalizationParser.AddWixVariable(messaging, variables, variable); - } - } - - /// - /// Parse a localized control. - /// - /// - /// Element to parse. - /// Dictionary of localized controls. - private static void ParseUI(IMessaging messaging, XElement node, IDictionary localizedControls) - { - string dialog = null; - string control = null; - var x = CompilerConstants.IntegerNotSet; - var y = CompilerConstants.IntegerNotSet; - var width = CompilerConstants.IntegerNotSet; - var height = CompilerConstants.IntegerNotSet; - var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); - var rightToLeft = false; - var rightAligned = false; - var leftScroll = false; - - foreach (var attrib in node.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Dialog": - dialog = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); - break; - case "Control": - control = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); - break; - case "X": - x = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Y": - y = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Width": - width = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "Height": - height = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); - break; - case "RightToLeft": - rightToLeft = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - break; - case "RightAligned": - rightAligned = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - break; - case "LeftScroll": - leftScroll = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); - break; - default: - Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); - break; - } - } - else - { - Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); - } - } - - var text = Common.GetInnerText(node); - - if (String.IsNullOrEmpty(control) && (rightToLeft || rightAligned || leftScroll)) - { - if (rightToLeft) - { - messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightToLeft", "Control")); - } - - if (rightAligned) - { - messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightAligned", "Control")); - } - - if (leftScroll) - { - messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "LeftScroll", "Control")); - } - } - - if (String.IsNullOrEmpty(control) && String.IsNullOrEmpty(dialog)) - { - messaging.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.ToString(), "Dialog", "Control")); - } - - if (!messaging.EncounteredError) - { - var localizedControl = new LocalizedControl(dialog, control, x, y, width, height, rightToLeft, rightAligned, leftScroll, text); - var key = localizedControl.GetKey(); - if (localizedControls.ContainsKey(key)) - { - if (String.IsNullOrEmpty(localizedControl.Control)) - { - messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog)); - } - else - { - messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog, localizedControl.Control)); - } - } - else - { - localizedControls.Add(key, localizedControl); - } - } - } - } -} diff --git a/src/WixToolset.Core/ParsedWixVariable.cs b/src/WixToolset.Core/ParsedWixVariable.cs deleted file mode 100644 index 9d308b77..00000000 --- a/src/WixToolset.Core/ParsedWixVariable.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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 -{ - internal class ParsedWixVariable - { - public int Index { get; set; } - - public int Length { get; set; } - - public string Namespace { get; set; } - - public string Name { get; set; } - - public string Scope { get; set; } - - public string DefaultValue { get; set; } - } -} diff --git a/src/WixToolset.Core/Preprocess/IfContext.cs b/src/WixToolset.Core/Preprocess/IfContext.cs deleted file mode 100644 index 91173c29..00000000 --- a/src/WixToolset.Core/Preprocess/IfContext.cs +++ /dev/null @@ -1,74 +0,0 @@ -// 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.Preprocess -{ - /// - /// Context for an if statement in the preprocessor. - /// - internal class IfContext - { - private bool keep; - - /// - /// Creates a default if context object, which are used for if's within an inactive preprocessor block - /// - public IfContext() - { - this.WasEverTrue = true; - this.IfState = IfState.If; - } - - /// - /// Creates an if context object. - /// - /// Flag if context is currently active. - /// Flag if context is currently true. - /// State of context to start in. - public IfContext(bool active, bool keep, IfState state) - { - this.Active = active; - this.keep = keep; - this.WasEverTrue = keep; - this.IfState = IfState.If; - } - - /// - /// Gets and sets if this if context is currently active. - /// - /// true if context is active. - public bool Active { get; set; } - - /// - /// Gets and sets if context is current true. - /// - /// true if context is currently true. - public bool IsTrue - { - get - { - return this.keep; - } - - set - { - this.keep = value; - if (this.keep) - { - this.WasEverTrue = true; - } - } - } - - /// - /// Gets if the context was ever true. - /// - /// True if context was ever true. - public bool WasEverTrue { get; private set; } - - /// - /// Gets the current state of the if context. - /// - /// Current state of context. - public IfState IfState { get; set; } - } -} diff --git a/src/WixToolset.Core/Preprocess/IfDefEventHandler.cs b/src/WixToolset.Core/Preprocess/IfDefEventHandler.cs deleted file mode 100644 index 6b56638a..00000000 --- a/src/WixToolset.Core/Preprocess/IfDefEventHandler.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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.Preprocess -{ - using System; - using WixToolset.Data; - - internal delegate void IfDefEventHandler(object sender, IfDefEventArgs e); - - internal class IfDefEventArgs : EventArgs - { - public IfDefEventArgs(SourceLineNumber sourceLineNumbers, bool isIfDef, bool isDefined, string variableName) - { - this.SourceLineNumbers = sourceLineNumbers; - this.IsIfDef = isIfDef; - this.IsDefined = isDefined; - this.VariableName = variableName; - } - - public SourceLineNumber SourceLineNumbers { get; } - - public bool IsDefined { get; } - - public bool IsIfDef { get; } - - public string VariableName { get; } - } -} diff --git a/src/WixToolset.Core/Preprocess/IfState.cs b/src/WixToolset.Core/Preprocess/IfState.cs deleted file mode 100644 index f5bb3e87..00000000 --- a/src/WixToolset.Core/Preprocess/IfState.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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.Preprocess -{ - /// - /// Current state of the if context. - /// - internal enum IfState - { - /// Context currently in unknown state. - Unknown, - - /// Context currently inside if statement. - If, - - /// Context currently inside elseif statement.. - ElseIf, - - /// Conext currently inside else statement. - Else, - } -} diff --git a/src/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs b/src/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs deleted file mode 100644 index 3c8ff2e8..00000000 --- a/src/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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.Preprocess -{ - using System; - using WixToolset.Data; - - /// - /// Included file event handler delegate. - /// - /// Sender of the message. - /// Arguments for the included file event. - internal delegate void IncludedFileEventHandler(object sender, IncludedFileEventArgs e); - - /// - /// Event args for included file event. - /// - internal class IncludedFileEventArgs : EventArgs - { - /// - /// Creates a new IncludedFileEventArgs. - /// - /// Source line numbers for the included file. - /// The full path of the included file. - public IncludedFileEventArgs(SourceLineNumber sourceLineNumbers, string fullName) - { - this.SourceLineNumbers = sourceLineNumbers; - this.FullName = fullName; - } - - /// - /// Gets the full path of the included file. - /// - /// The full path of the included file. - public string FullName { get; } - - /// - /// Gets the source line numbers. - /// - /// The source line numbers. - public SourceLineNumber SourceLineNumbers { get; } - } -} diff --git a/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs b/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs deleted file mode 100644 index 086a0f1a..00000000 --- a/src/WixToolset.Core/Preprocess/PreprocessorOperation.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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.Preprocess -{ - /// - /// Enumeration for preprocessor operations in if statements. - /// - internal enum PreprocessorOperation - { - /// The and operator. - And, - - /// The or operator. - Or, - - /// The not operator. - Not - } -} diff --git a/src/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs b/src/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs deleted file mode 100644 index 672b4b9f..00000000 --- a/src/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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.Preprocess -{ - using System; - using System.Xml.Linq; - - /// - /// Preprocessed output stream event handler delegate. - /// - /// Sender of the message. - /// Arguments for the preprocessed stream event. - internal delegate void ProcessedStreamEventHandler(object sender, ProcessedStreamEventArgs e); - - /// - /// Event args for preprocessed stream event. - /// - internal class ProcessedStreamEventArgs : EventArgs - { - /// - /// Creates a new ProcessedStreamEventArgs. - /// - /// Source file that is preprocessed. - /// Preprocessed output document. - public ProcessedStreamEventArgs(string sourceFile, XDocument document) - { - this.SourceFile = sourceFile; - this.Document = document; - } - - /// - /// Gets the full path of the source file. - /// - /// The full path of the source file. - public string SourceFile { get; } - - /// - /// Gets the preprocessed output stream. - /// - /// The the preprocessed output stream. - public XDocument Document { get; } - } -} diff --git a/src/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs b/src/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs deleted file mode 100644 index 6d159ad0..00000000 --- a/src/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.Preprocess -{ - using System; - using WixToolset.Data; - - internal delegate void ResolvedVariableEventHandler(object sender, ResolvedVariableEventArgs e); - - internal class ResolvedVariableEventArgs : EventArgs - { - public ResolvedVariableEventArgs(SourceLineNumber sourceLineNumbers, string variableName, string variableValue) - { - this.SourceLineNumbers = sourceLineNumbers; - this.VariableName = variableName; - this.VariableValue = variableValue; - } - - public SourceLineNumber SourceLineNumbers { get; } - - public string VariableName { get; } - - public string VariableValue { get; } - } -} diff --git a/src/WixToolset.Core/PreprocessContext.cs b/src/WixToolset.Core/PreprocessContext.cs deleted file mode 100644 index 986045ff..00000000 --- a/src/WixToolset.Core/PreprocessContext.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - - internal class PreprocessContext : IPreprocessContext - { - internal PreprocessContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IReadOnlyCollection Extensions { get; set; } - - public Platform Platform { get; set; } - - public IReadOnlyCollection IncludeSearchPaths { get; set; } - - public string SourcePath { get; set; } - - public IDictionary Variables { get; set; } - - public SourceLineNumber CurrentSourceLineNumber { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/PreprocessResult.cs b/src/WixToolset.Core/PreprocessResult.cs deleted file mode 100644 index 83b29a90..00000000 --- a/src/WixToolset.Core/PreprocessResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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 -{ - using System.Collections.Generic; - using System.Xml.Linq; - using WixToolset.Extensibility.Data; - - internal class PreprocessResult : IPreprocessResult - { - public XDocument Document { get; set; } - - public IReadOnlyCollection IncludedFiles { get; set; } - } -} diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs deleted file mode 100644 index 603c0e5b..00000000 --- a/src/WixToolset.Core/Preprocessor.cs +++ /dev/null @@ -1,1520 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Text; - using System.Text.RegularExpressions; - using System.Xml; - using System.Xml.Linq; - using WixToolset.Core.Preprocess; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Preprocessor object - /// - internal class Preprocessor : IPreprocessor - { - private static readonly Regex DefineRegex = new Regex(@"^\s*(?.+?)\s*(=\s*(?.+?)\s*)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); - private static readonly Regex PragmaRegex = new Regex(@"^\s*(?.+?)(?[\s\(].+?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); - - private static readonly XmlReaderSettings DocumentXmlReaderSettings = new XmlReaderSettings() - { - ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, - XmlResolver = null, - }; - - private static readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings() - { - ConformanceLevel = ConformanceLevel.Fragment, - ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, - XmlResolver = null, - }; - - internal Preprocessor(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = this.ServiceProvider.GetService(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - /// - /// Event for ifdef/ifndef directives. - /// - public event IfDefEventHandler IfDef; - - /// - /// Event for included files. - /// - public event IncludedFileEventHandler IncludedFile; - - /// - /// Event for preprocessed stream. - /// - public event ProcessedStreamEventHandler ProcessedStream; - - // - // Event for resolved variables. - // - // TOOD: Remove? - //public event ResolvedVariableEventHandler ResolvedVariable; - - /// - /// Get the source line information for the current element. The precompiler will insert - /// special source line number information for each element that it encounters. - /// - /// Element to get source line information for. - /// - /// The source line number used to author the element being processed or - /// null if the preprocessor did not process the element or the node is - /// not an element. - /// - public static SourceLineNumber GetSourceLineNumbers(XObject node) - { - return SourceLineNumber.GetFromXAnnotation(node); - } - - /// - /// Preprocesses a file. - /// - /// The preprocessing context. - /// XDocument with the postprocessed data. - public IPreprocessResult Preprocess(IPreprocessContext context) - { - var state = new ProcessingState(this.ServiceProvider, context); - - this.PreProcess(state); - - IPreprocessResult result; - using (var reader = XmlReader.Create(state.Context.SourcePath, DocumentXmlReaderSettings)) - { - result = this.Process(state, reader); - } - - this.PostProcess(state, result); - - return result; - } - - /// - /// Preprocesses a file. - /// - /// The preprocessing context. - /// XmlReader to processing the context. - /// XDocument with the postprocessed data. - public IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader) - { - if (String.IsNullOrEmpty(context.SourcePath) && !String.IsNullOrEmpty(reader.BaseURI)) - { - var uri = new Uri(reader.BaseURI); - context.SourcePath = uri.AbsolutePath; - } - - var state = new ProcessingState(this.ServiceProvider, context); - - this.PreProcess(state); - - var result = this.Process(state, reader); - - this.PostProcess(state, result); - - return result; - } - - /// - /// Preprocesses a file. - /// - /// The preprocessing context. - /// XmlReader to processing the context. - /// XDocument with the postprocessed data. - private IPreprocessResult Process(ProcessingState state, XmlReader reader) - { - state.CurrentFileStack.Push(state.Helper.GetVariableValue(state.Context, "sys", "SOURCEFILEDIR")); - - // Process the reader into the output. - IPreprocessResult result = null; - try - { - this.PreprocessReader(state, false, reader, state.Output, 0); - - // Fire event with post-processed document. - this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(state.Context.SourcePath, state.Output)); - - if (!this.Messaging.EncounteredError) - { - result = this.ServiceProvider.GetService(); - result.Document = state.Output; - result.IncludedFiles = state.IncludedFiles; - } - } - catch (XmlException e) - { - this.UpdateCurrentLineNumber(state, reader, 0); - throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message)); - } - - return result; - } - - /// - /// Determins if string is an operator. - /// - /// String to check. - /// true if string is an operator. - private static bool IsOperator(string operation) - { - if (operation == null) - { - return false; - } - - operation = operation.Trim(); - if (0 == operation.Length) - { - return false; - } - - if ("=" == operation || - "!=" == operation || - "<" == operation || - "<=" == operation || - ">" == operation || - ">=" == operation || - "~=" == operation) - { - return true; - } - return false; - } - - /// - /// Determines if expression is currently inside quotes. - /// - /// Expression to evaluate. - /// Index to start searching in expression. - /// true if expression is inside in quotes. - private static bool InsideQuotes(string expression, int index) - { - if (index == -1) - { - return false; - } - - var numQuotes = 0; - var tmpIndex = 0; - while (-1 != (tmpIndex = expression.IndexOf('\"', tmpIndex, index - tmpIndex))) - { - numQuotes++; - tmpIndex++; - } - - // found an even number of quotes before the index, so we're not inside - if (numQuotes % 2 == 0) - { - return false; - } - - // found an odd number of quotes, so we are inside - return true; - } - - /// - /// Tests expression to see if it starts with a keyword. - /// - /// Expression to test. - /// Operation to test for. - /// true if expression starts with a keyword. - private static bool StartsWithKeyword(string expression, PreprocessorOperation operation) - { - expression = expression.ToUpperInvariant(); - switch (operation) - { - case PreprocessorOperation.Not: - if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal)) - { - return true; - } - break; - case PreprocessorOperation.And: - if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal)) - { - return true; - } - break; - case PreprocessorOperation.Or: - if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal)) - { - return true; - } - break; - default: - break; - } - return false; - } - - /// - /// Processes an xml reader into an xml writer. - /// - /// - /// Specifies if reader is from an included file. - /// Reader for the source document. - /// Node where content should be added. - /// Original offset for the line numbers being processed. - private void PreprocessReader(ProcessingState state, bool include, XmlReader reader, XContainer container, int offset) - { - var currentContainer = container; - var containerStack = new Stack(); - - var ifContext = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code - var ifStack = new Stack(); - - // process the reader into the writer - while (reader.Read()) - { - // update information here in case an error occurs before the next read - this.UpdateCurrentLineNumber(state, reader, offset); - - var sourceLineNumbers = state.Context.CurrentSourceLineNumber; - - // check for changes in conditional processing - if (XmlNodeType.ProcessingInstruction == reader.NodeType) - { - var ignore = false; - string name = null; - - switch (reader.LocalName) - { - case "if": - ifStack.Push(ifContext); - if (ifContext.IsTrue) - { - ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(state, reader.Value), IfState.If); - } - else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true - { - ifContext = new IfContext(); - } - ignore = true; - break; - - case "ifdef": - ifStack.Push(ifContext); - name = reader.Value.Trim(); - if (ifContext.IsTrue) - { - ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); - } - else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true - { - ifContext = new IfContext(); - } - ignore = true; - this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); - break; - - case "ifndef": - ifStack.Push(ifContext); - name = reader.Value.Trim(); - if (ifContext.IsTrue) - { - ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); - } - else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true - { - ifContext = new IfContext(); - } - ignore = true; - this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); - break; - - case "elseif": - if (0 == ifStack.Count) - { - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); - } - - if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) - { - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); - } - - ifContext.IfState = IfState.ElseIf; // we're now in an elseif - if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test - { - ifContext.IsTrue = this.EvaluateExpression(state, reader.Value); - } - else if (ifContext.IsTrue) - { - ifContext.IsTrue = false; - } - ignore = true; - break; - - case "else": - if (0 == ifStack.Count) - { - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); - } - - if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) - { - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); - } - - ifContext.IfState = IfState.Else; // we're now in an else - ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now - ignore = true; - break; - - case "endif": - if (0 == ifStack.Count) - { - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif")); - } - - ifContext = ifStack.Pop(); - ignore = true; - break; - } - - if (ignore) // ignore this node since we just handled it above - { - continue; - } - } - - if (!ifContext.Active || !ifContext.IsTrue) // if our context is not true then skip the rest of the processing and just read the next thing - { - continue; - } - - switch (reader.NodeType) - { - case XmlNodeType.XmlDeclaration: - var document = currentContainer as XDocument; - if (null != document) - { - document.Declaration = new XDeclaration(null, null, null); - while (reader.MoveToNextAttribute()) - { - switch (reader.LocalName) - { - case "version": - document.Declaration.Version = reader.Value; - break; - - case "encoding": - document.Declaration.Encoding = reader.Value; - break; - - case "standalone": - document.Declaration.Standalone = reader.Value; - break; - } - } - - } - //else - //{ - // display an error? Can this happen? - //} - break; - - case XmlNodeType.ProcessingInstruction: - switch (reader.LocalName) - { - case "define": - this.PreprocessDefine(state, reader.Value); - break; - - case "error": - this.PreprocessError(state, reader.Value); - break; - - case "warning": - this.PreprocessWarning(state, reader.Value); - break; - - case "undef": - this.PreprocessUndef(state, reader.Value); - break; - - case "include": - this.UpdateCurrentLineNumber(state, reader, offset); - this.PreprocessInclude(state, reader.Value, currentContainer); - break; - - case "foreach": - this.PreprocessForeach(state, reader, currentContainer, offset); - break; - - case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error - throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach")); - - case "pragma": - this.PreprocessPragma(state, reader.Value, currentContainer); - break; - - default: - // unknown processing instructions are currently ignored - break; - } - break; - - case XmlNodeType.Element: - if (0 < state.IncludeNextStack.Count && state.IncludeNextStack.Peek()) - { - if ("Include" != reader.LocalName) - { - this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include")); - } - - state.IncludeNextStack.Pop(); - state.IncludeNextStack.Push(false); - break; - } - - var empty = reader.IsEmptyElement; - var ns = XNamespace.Get(reader.NamespaceURI); - var element = new XElement(ns + reader.LocalName); - currentContainer.Add(element); - - this.UpdateCurrentLineNumber(state, reader, offset); - element.AddAnnotation(sourceLineNumbers); - - while (reader.MoveToNextAttribute()) - { - var value = state.Helper.PreprocessString(state.Context, reader.Value); - - var attribNamespace = XNamespace.Get(reader.NamespaceURI); - attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; - - element.Add(new XAttribute(attribNamespace + reader.LocalName, value)); - } - - if (!empty) - { - containerStack.Push(currentContainer); - currentContainer = element; - } - break; - - case XmlNodeType.EndElement: - if (0 < reader.Depth || !include) - { - currentContainer = containerStack.Pop(); - } - break; - - case XmlNodeType.Text: - var postprocessedText = state.Helper.PreprocessString(state.Context, reader.Value); - currentContainer.Add(postprocessedText); - break; - - case XmlNodeType.CDATA: - var postprocessedValue = state.Helper.PreprocessString(state.Context, reader.Value); - currentContainer.Add(new XCData(postprocessedValue)); - break; - - default: - break; - } - } - - if (0 != ifStack.Count) - { - throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "if", "endif")); - } - - // TODO: can this actually happen? - if (0 != containerStack.Count) - { - throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "nodes", "nodes")); - } - } - - /// - /// Processes an error processing instruction. - /// - /// - /// Text from source. - private void PreprocessError(ProcessingState state, string errorMessage) - { - // Resolve other variables in the error message. - errorMessage = state.Helper.PreprocessString(state.Context, errorMessage); - - throw new WixException(ErrorMessages.PreprocessorError(state.Context.CurrentSourceLineNumber, errorMessage)); - } - - /// - /// Processes a warning processing instruction. - /// - /// - /// Text from source. - private void PreprocessWarning(ProcessingState state, string warningMessage) - { - // Resolve other variables in the warning message. - warningMessage = state.Helper.PreprocessString(state.Context, warningMessage); - - this.Messaging.Write(WarningMessages.PreprocessorWarning(state.Context.CurrentSourceLineNumber, warningMessage)); - } - - /// - /// Processes a define processing instruction and creates the appropriate parameter. - /// - /// - /// Text from source. - private void PreprocessDefine(ProcessingState state, string originalDefine) - { - var match = DefineRegex.Match(originalDefine); - - if (!match.Success) - { - throw new WixException(ErrorMessages.IllegalDefineStatement(state.Context.CurrentSourceLineNumber, originalDefine)); - } - - var defineName = match.Groups["varName"].Value; - var defineValue = match.Groups["varValue"].Value; - - // strip off the optional quotes - if (1 < defineValue.Length && - ((defineValue.StartsWith("\"", StringComparison.Ordinal) && defineValue.EndsWith("\"", StringComparison.Ordinal)) - || (defineValue.StartsWith("'", StringComparison.Ordinal) && defineValue.EndsWith("'", StringComparison.Ordinal)))) - { - defineValue = defineValue.Substring(1, defineValue.Length - 2); - } - - // resolve other variables in the variable value - defineValue = state.Helper.PreprocessString(state.Context, defineValue); - - if (defineName.StartsWith("var.", StringComparison.Ordinal)) - { - state.Helper.AddVariable(state.Context, defineName.Substring(4), defineValue); - } - else - { - state.Helper.AddVariable(state.Context, defineName, defineValue); - } - } - - /// - /// Processes an undef processing instruction and creates the appropriate parameter. - /// - /// - /// Text from source. - private void PreprocessUndef(ProcessingState state, string originalDefine) - { - var name = state.Helper.PreprocessString(state.Context, originalDefine.Trim()); - - if (name.StartsWith("var.", StringComparison.Ordinal)) - { - state.Helper.RemoveVariable(state.Context, name.Substring(4)); - } - else - { - state.Helper.RemoveVariable(state.Context, name); - } - } - - /// - /// Processes an included file. - /// - /// - /// Path to included file. - /// Parent container for included content. - private void PreprocessInclude(ProcessingState state, string includePath, XContainer parent) - { - var sourceLineNumbers = state.Context.CurrentSourceLineNumber; - - // Preprocess variables in the path. - includePath = state.Helper.PreprocessString(state.Context, includePath); - - var includeFile = this.GetIncludeFile(state, includePath); - - if (null == includeFile) - { - throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, includePath, "include")); - } - - using (var reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings)) - { - this.PushInclude(state, includeFile); - - // process the included reader into the writer - try - { - this.PreprocessReader(state, true, reader, parent, 0); - } - catch (XmlException e) - { - this.UpdateCurrentLineNumber(state, reader, 0); - throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "source", e.Message)); - } - - this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile)); - - var includedFile = this.ServiceProvider.GetService(); - includedFile.Path = includeFile; - includedFile.SourceLineNumbers = sourceLineNumbers; - - state.IncludedFiles.Add(includedFile); - - this.PopInclude(state); - } - } - - /// - /// Preprocess a foreach processing instruction. - /// - /// - /// The xml reader. - /// The container where to output processed data. - /// Offset for the line numbers. - private void PreprocessForeach(ProcessingState state, XmlReader reader, XContainer container, int offset) - { - // Find the "in" token. - var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); - if (0 > indexOfInToken) - { - throw new WixException(ErrorMessages.IllegalForeach(state.Context.CurrentSourceLineNumber, reader.Value)); - } - - // parse out the variable name - var varName = reader.Value.Substring(0, indexOfInToken).Trim(); - var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); - - // preprocess the variable values string because it might be a variable itself - varValuesString = state.Helper.PreprocessString(state.Context, varValuesString); - - var varValues = varValuesString.Split(';'); - - // go through all the empty strings - while (reader.Read() && XmlNodeType.Whitespace == reader.NodeType) - { - } - - // get the offset of this xml fragment (for some reason its always off by 1) - var lineInfoReader = reader as IXmlLineInfo; - if (null != lineInfoReader) - { - offset += lineInfoReader.LineNumber - 1; - } - - var textReader = reader as XmlTextReader; - // dump the xml to a string (maintaining whitespace if possible) - if (null != textReader) - { - textReader.WhitespaceHandling = WhitespaceHandling.All; - } - - var fragmentBuilder = new StringBuilder(); - var nestedForeachCount = 1; - while (nestedForeachCount != 0) - { - if (reader.NodeType == XmlNodeType.ProcessingInstruction) - { - switch (reader.LocalName) - { - case "foreach": - ++nestedForeachCount; - // Output the foreach statement - fragmentBuilder.AppendFormat("", reader.Value); - break; - - case "endforeach": - --nestedForeachCount; - if (0 != nestedForeachCount) - { - fragmentBuilder.Append(""); - } - break; - - default: - fragmentBuilder.AppendFormat("", reader.LocalName, reader.Value); - break; - } - } - else if (reader.NodeType == XmlNodeType.Element) - { - fragmentBuilder.Append(reader.ReadOuterXml()); - continue; - } - else if (reader.NodeType == XmlNodeType.Whitespace) - { - // Or output the whitespace - fragmentBuilder.Append(reader.Value); - } - else if (reader.NodeType == XmlNodeType.None) - { - throw new WixException(ErrorMessages.ExpectedEndforeach(state.Context.CurrentSourceLineNumber)); - } - - reader.Read(); - } - - using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) - { - // process each iteration, updating the variable's value each time - foreach (var varValue in varValues) - { - using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) - { - // Always overwrite foreach variables. - state.Helper.AddVariable(state.Context, varName, varValue, false); - - try - { - this.PreprocessReader(state, false, loopReader, container, offset); - } - catch (XmlException e) - { - this.UpdateCurrentLineNumber(state, loopReader, offset); - throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message)); - } - - fragmentStream.Position = 0; // seek back to the beginning for the next loop. - } - } - } - } - - /// - /// Processes a pragma processing instruction - /// - /// - /// Text from source. - /// - private void PreprocessPragma(ProcessingState state, string pragmaText, XContainer parent) - { - var match = PragmaRegex.Match(pragmaText); - - if (!match.Success) - { - throw new WixException(ErrorMessages.InvalidPreprocessorPragma(state.Context.CurrentSourceLineNumber, pragmaText)); - } - - // resolve other variables in the pragma argument(s) - var pragmaArgs = state.Helper.PreprocessString(state.Context, match.Groups["pragmaValue"].Value).Trim(); - - try - { - state.Helper.PreprocessPragma(state.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); - } - catch (Exception e) - { - throw new WixException(ErrorMessages.PreprocessorExtensionPragmaFailed(state.Context.CurrentSourceLineNumber, pragmaText, e.Message)); - } - } - - /// - /// Gets the next token in an expression. - /// - /// - /// Expression to parse. - /// Expression with token removed. - /// Flag if token is a string literal instead of a variable. - /// Next token. - private string GetNextToken(ProcessingState state, string originalExpression, ref string expression, out bool stringLiteral) - { - stringLiteral = false; - var token = String.Empty; - expression = expression.Trim(); - if (0 == expression.Length) - { - return String.Empty; - } - - if (expression.StartsWith("\"", StringComparison.Ordinal)) - { - stringLiteral = true; - var endingQuotes = expression.IndexOf('\"', 1); - if (-1 == endingQuotes) - { - throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - // cut the quotes off the string - token = state.Helper.PreprocessString(state.Context, expression.Substring(1, endingQuotes - 1)); - - // advance past this string - expression = expression.Substring(endingQuotes + 1).Trim(); - } - else if (expression.StartsWith("$(", StringComparison.Ordinal)) - { - // Find the ending paren of the expression - var endingParen = -1; - var openedCount = 1; - for (var i = 2; i < expression.Length; i++) - { - if ('(' == expression[i]) - { - openedCount++; - } - else if (')' == expression[i]) - { - openedCount--; - } - - if (openedCount == 0) - { - endingParen = i; - break; - } - } - - if (-1 == endingParen) - { - throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - token = expression.Substring(0, endingParen + 1); - - // Advance past this variable - expression = expression.Substring(endingParen + 1).Trim(); - } - else - { - // Cut the token off at the next equal, space, inequality operator, - // or end of string, whichever comes first - var space = expression.IndexOf(" ", StringComparison.Ordinal); - var equals = expression.IndexOf("=", StringComparison.Ordinal); - var lessThan = expression.IndexOf("<", StringComparison.Ordinal); - var lessThanEquals = expression.IndexOf("<=", StringComparison.Ordinal); - var greaterThan = expression.IndexOf(">", StringComparison.Ordinal); - var greaterThanEquals = expression.IndexOf(">=", StringComparison.Ordinal); - var notEquals = expression.IndexOf("!=", StringComparison.Ordinal); - var equalsNoCase = expression.IndexOf("~=", StringComparison.Ordinal); - int closingIndex; - - if (space == -1) - { - space = Int32.MaxValue; - } - - if (equals == -1) - { - equals = Int32.MaxValue; - } - - if (lessThan == -1) - { - lessThan = Int32.MaxValue; - } - - if (lessThanEquals == -1) - { - lessThanEquals = Int32.MaxValue; - } - - if (greaterThan == -1) - { - greaterThan = Int32.MaxValue; - } - - if (greaterThanEquals == -1) - { - greaterThanEquals = Int32.MaxValue; - } - - if (notEquals == -1) - { - notEquals = Int32.MaxValue; - } - - if (equalsNoCase == -1) - { - equalsNoCase = Int32.MaxValue; - } - - closingIndex = Math.Min(space, Math.Min(equals, Math.Min(lessThan, Math.Min(lessThanEquals, Math.Min(greaterThan, Math.Min(greaterThanEquals, Math.Min(equalsNoCase, notEquals))))))); - - if (Int32.MaxValue == closingIndex) - { - closingIndex = expression.Length; - } - - // If the index is 0, we hit an operator, so return it - if (0 == closingIndex) - { - // Length 2 operators - if (closingIndex == lessThanEquals || closingIndex == greaterThanEquals || closingIndex == notEquals || closingIndex == equalsNoCase) - { - closingIndex = 2; - } - else // Length 1 operators - { - closingIndex = 1; - } - } - - // Cut out the new token - token = expression.Substring(0, closingIndex).Trim(); - expression = expression.Substring(closingIndex).Trim(); - } - - return token; - } - - /// - /// Gets the value for a variable. - /// - /// - /// Original expression for error message. - /// Variable to evaluate. - /// Value of variable. - private string EvaluateVariable(ProcessingState state, string originalExpression, string variable) - { - // By default it's a literal and will only be evaluated if it - // matches the variable format - var varValue = variable; - - if (variable.StartsWith("$(", StringComparison.Ordinal)) - { - try - { - varValue = state.Helper.PreprocessString(state.Context, variable); - } - catch (ArgumentNullException) - { - // non-existent variables are expected - varValue = null; - } - } - else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1) - { - // make sure it doesn't contain parenthesis - throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1) - { - // shouldn't contain quotes - throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - return varValue; - } - - /// - /// Gets the left side value, operator, and right side value of an expression. - /// - /// - /// Original expression to evaluate. - /// Expression modified while processing. - /// Left side value from expression. - /// Operation in expression. - /// Right side value from expression. - private void GetNameValuePair(ProcessingState state, string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue) - { - leftValue = this.GetNextToken(state, originalExpression, ref expression, out var stringLiteral); - - // If it wasn't a string literal, evaluate it - if (!stringLiteral) - { - leftValue = this.EvaluateVariable(state, originalExpression, leftValue); - } - - // Get the operation - operation = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral); - if (IsOperator(operation)) - { - if (stringLiteral) - { - throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - rightValue = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral); - - // If it wasn't a string literal, evaluate it - if (!stringLiteral) - { - rightValue = this.EvaluateVariable(state, originalExpression, rightValue); - } - } - else - { - // Prepend the token back on the expression since it wasn't an operator - // and put the quotes back on the literal if necessary - - if (stringLiteral) - { - operation = "\"" + operation + "\""; - } - expression = (operation + " " + expression).Trim(); - - // If no operator, just check for existence - operation = ""; - rightValue = ""; - } - } - - /// - /// Evaluates an expression. - /// - /// - /// Original expression to evaluate. - /// Expression modified while processing. - /// true if expression evaluates to true. - private bool EvaluateAtomicExpression(ProcessingState state, string originalExpression, ref string expression) - { - // Quick test to see if the first token is a variable - var startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal); - this.GetNameValuePair(state, originalExpression, ref expression, out var leftValue, out var operation, out var rightValue); - - var expressionValue = false; - - // If the variables don't exist, they were evaluated to null - if (null == leftValue || null == rightValue) - { - if (operation.Length > 0) - { - throw new WixException(ErrorMessages.ExpectedVariable(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - // false expression - } - else if (operation.Length == 0) - { - // There is no right side of the equation. - // If the variable was evaluated, it exists, so the expression is true - if (startsWithVariable) - { - expressionValue = true; - } - else - { - throw new WixException(ErrorMessages.UnexpectedLiteral(state.Context.CurrentSourceLineNumber, originalExpression)); - } - } - else - { - leftValue = leftValue.Trim(); - rightValue = rightValue.Trim(); - if ("=" == operation) - { - if (leftValue == rightValue) - { - expressionValue = true; - } - } - else if ("!=" == operation) - { - if (leftValue != rightValue) - { - expressionValue = true; - } - } - else if ("~=" == operation) - { - if (String.Equals(leftValue, rightValue, StringComparison.OrdinalIgnoreCase)) - { - expressionValue = true; - } - } - else - { - // Convert the numbers from strings - int rightInt; - int leftInt; - try - { - rightInt = Int32.Parse(rightValue, CultureInfo.InvariantCulture); - leftInt = Int32.Parse(leftValue, CultureInfo.InvariantCulture); - } - catch (FormatException) - { - throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - catch (OverflowException) - { - throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - // Compare the numbers - if ("<" == operation && leftInt < rightInt || - "<=" == operation && leftInt <= rightInt || - ">" == operation && leftInt > rightInt || - ">=" == operation && leftInt >= rightInt) - { - expressionValue = true; - } - } - } - - return expressionValue; - } - - /// - /// Gets a sub-expression in parenthesis. - /// - /// - /// Original expression to evaluate. - /// Expression modified while processing. - /// Index of end of sub-expression. - /// Sub-expression in parenthesis. - private string GetParenthesisExpression(ProcessingState state, string originalExpression, string expression, out int endSubExpression) - { - endSubExpression = 0; - - // if the expression doesn't start with parenthesis, leave it alone - if (!expression.StartsWith("(", StringComparison.Ordinal)) - { - return expression; - } - - // search for the end of the expression with the matching paren - var openParenIndex = 0; - var closeParenIndex = 1; - while (openParenIndex != -1 && openParenIndex < closeParenIndex) - { - closeParenIndex = expression.IndexOf(')', closeParenIndex); - if (closeParenIndex == -1) - { - throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - if (InsideQuotes(expression, closeParenIndex)) - { - // ignore stuff inside quotes (it's a string literal) - } - else - { - // Look to see if there is another open paren before the close paren - // and skip over the open parens while they are in a string literal - do - { - openParenIndex++; - openParenIndex = expression.IndexOf('(', openParenIndex, closeParenIndex - openParenIndex); - } - while (InsideQuotes(expression, openParenIndex)); - } - - // Advance past the closing paren - closeParenIndex++; - } - - endSubExpression = closeParenIndex; - - // Return the expression minus the parenthesis - return expression.Substring(1, closeParenIndex - 2); - } - - /// - /// Updates expression based on operation. - /// - /// - /// State to update. - /// Operation to apply to current value. - /// Previous result. - private void UpdateExpressionValue(ProcessingState state, ref bool currentValue, PreprocessorOperation operation, bool prevResult) - { - switch (operation) - { - case PreprocessorOperation.And: - currentValue = currentValue && prevResult; - break; - case PreprocessorOperation.Or: - currentValue = currentValue || prevResult; - break; - case PreprocessorOperation.Not: - currentValue = !currentValue; - break; - default: - throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(state.Context.CurrentSourceLineNumber, operation.ToString())); - } - } - - /// - /// Evaluate an expression. - /// - /// - /// Expression to evaluate. - /// Boolean result of expression. - private bool EvaluateExpression(ProcessingState state, string expression) - { - var tmpExpression = expression; - return this.EvaluateExpressionRecurse(state, expression, ref tmpExpression, PreprocessorOperation.And, true); - } - - /// - /// Recurse through the expression to evaluate if it is true or false. - /// The expression is evaluated left to right. - /// The expression is case-sensitive (converted to upper case) with the - /// following exceptions: variable names and keywords (and, not, or). - /// Comparisons with = and != are string comparisons. - /// Comparisons with inequality operators must be done on valid integers. - /// - /// The operator precedence is: - /// "" - /// () - /// <, >, <=, >=, =, != - /// Not - /// And, Or - /// - /// Valid expressions include: - /// not $(var.B) or not $(var.C) - /// (($(var.A))and $(var.B) ="2")or Not((($(var.C))) and $(var.A)) - /// (($(var.A)) and $(var.B) = " 3 ") or $(var.C) - /// $(var.A) and $(var.C) = "3" or $(var.C) and $(var.D) = $(env.windir) - /// $(var.A) and $(var.B)>2 or $(var.B) <= 2 - /// $(var.A) != "2" - /// - /// - /// The original expression - /// The expression currently being evaluated - /// The operation to apply to this result - /// The previous result to apply to this result - /// Boolean to indicate if the expression is true or false - private bool EvaluateExpressionRecurse(ProcessingState state, string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult) - { - var expressionValue = false; - expression = expression.Trim(); - if (expression.Length == 0) - { - throw new WixException(ErrorMessages.UnexpectedEmptySubexpression(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - // If the expression starts with parenthesis, evaluate it - if (expression.IndexOf('(') == 0) - { - var subExpression = this.GetParenthesisExpression(state, originalExpression, expression, out var endSubExpressionIndex); - expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref subExpression, PreprocessorOperation.And, true); - - // Now get the rest of the expression that hasn't been evaluated - expression = expression.Substring(endSubExpressionIndex).Trim(); - } - else - { - // Check for NOT - if (StartsWithKeyword(expression, PreprocessorOperation.Not)) - { - expression = expression.Substring(3).Trim(); - if (expression.Length == 0) - { - throw new WixException(ErrorMessages.ExpectedExpressionAfterNot(state.Context.CurrentSourceLineNumber, originalExpression)); - } - - expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Not, true); - } - else // Expect a literal - { - expressionValue = this.EvaluateAtomicExpression(state, originalExpression, ref expression); - - // Expect the literal that was just evaluated to already be cut off - } - } - this.UpdateExpressionValue(state, ref expressionValue, prevResultOperation, prevResult); - - // If there's still an expression left, it must start with AND or OR. - if (expression.Trim().Length > 0) - { - if (StartsWithKeyword(expression, PreprocessorOperation.And)) - { - expression = expression.Substring(3); - return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.And, expressionValue); - } - else if (StartsWithKeyword(expression, PreprocessorOperation.Or)) - { - expression = expression.Substring(2); - return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Or, expressionValue); - } - else - { - throw new WixException(ErrorMessages.InvalidSubExpression(state.Context.CurrentSourceLineNumber, expression, originalExpression)); - } - } - - return expressionValue; - } - - /// - /// Update the current line number with the reader's current state. - /// - /// - /// The xml reader for the preprocessor. - /// This is the artificial offset of the line numbers from the reader. Used for the foreach processing. - private void UpdateCurrentLineNumber(ProcessingState state, XmlReader reader, int offset) - { - var lineInfoReader = reader as IXmlLineInfo; - if (null != lineInfoReader) - { - var newLine = lineInfoReader.LineNumber + offset; - - if (state.Context.CurrentSourceLineNumber.LineNumber != newLine) - { - state.Context.CurrentSourceLineNumber = new SourceLineNumber(state.Context.CurrentSourceLineNumber.FileName, state.Context.CurrentSourceLineNumber.Parent, newLine); - } - } - } - - /// - /// Pushes a file name on the stack of included files. - /// - /// - /// Name to push on to the stack of included files. - private void PushInclude(ProcessingState state, string fileName) - { - if (1023 < state.CurrentFileStack.Count) - { - throw new WixException(ErrorMessages.TooDeeplyIncluded(state.Context.CurrentSourceLineNumber, state.CurrentFileStack.Count)); - } - - var path = Path.GetFullPath(fileName); - - state.CurrentFileStack.Push(path); - state.SourceStack.Push(state.Context.CurrentSourceLineNumber); - state.Context.CurrentSourceLineNumber = new SourceLineNumber(path, state.Context.CurrentSourceLineNumber); - state.IncludeNextStack.Push(true); - } - - /// - /// Pops a file name from the stack of included files. - /// - private void PopInclude(ProcessingState state) - { - state.Context.CurrentSourceLineNumber = state.SourceStack.Pop(); - - state.CurrentFileStack.Pop(); - state.IncludeNextStack.Pop(); - } - - /// - /// Go through search paths, looking for a matching include file. - /// Start the search in the directory of the source file, then go - /// through the search paths in the order given on the command line - /// (leftmost first, ...). - /// - /// - /// User-specified path to the included file (usually just the file name). - /// Returns a FileInfo for the found include file, or null if the file cannot be found. - private string GetIncludeFile(ProcessingState state, string includePath) - { - string finalIncludePath = null; - - includePath = includePath.Trim(); - - // remove quotes (only if they match) - if ((includePath.StartsWith("\"", StringComparison.Ordinal) && includePath.EndsWith("\"", StringComparison.Ordinal)) || - (includePath.StartsWith("'", StringComparison.Ordinal) && includePath.EndsWith("'", StringComparison.Ordinal))) - { - includePath = includePath.Substring(1, includePath.Length - 2); - } - - // check if the include file is a full path - if (Path.IsPathRooted(includePath)) - { - if (File.Exists(includePath)) - { - finalIncludePath = includePath; - } - } - else // relative path - { - // build a string to test the directory containing the source file first - var currentFolder = state.CurrentFileStack.Peek(); - var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder), includePath); - - // test the source file directory - if (File.Exists(includeTestPath)) - { - finalIncludePath = includeTestPath; - } - else if (state.Context.IncludeSearchPaths != null) // test all search paths in the order specified on the command line - { - foreach (var includeSearchPath in state.Context.IncludeSearchPaths) - { - // if the path exists, we have found the final string - includeTestPath = Path.Combine(includeSearchPath, includePath); - if (File.Exists(includeTestPath)) - { - finalIncludePath = includeTestPath; - break; - } - } - } - } - - return finalIncludePath; - } - - private void PreProcess(ProcessingState state) - { - if (state.Context.Extensions == null) - { - return; - } - - foreach (var extension in state.Context.Extensions) - { - if (extension.Prefixes != null) - { - foreach (var prefix in extension.Prefixes) - { - if (!state.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension)) - { - state.ExtensionsByPrefix.Add(prefix, extension); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString())); - } - } - } - - extension.PrePreprocess(state.Context); - } - } - - private void PostProcess(ProcessingState state, IPreprocessResult result) - { - if (state.Context.Extensions == null) - { - return; - } - - foreach (var extension in state.Context.Extensions) - { - extension.PostPreprocess(result); - } - } - - private class ProcessingState - { - public ProcessingState(IServiceProvider serviceProvider, IPreprocessContext context) - { - var path = Path.GetFullPath(context.SourcePath); - - this.Context = context; - this.Context.CurrentSourceLineNumber = new SourceLineNumber(path); - this.Context.Variables = this.Context.Variables == null ? new Dictionary() : new Dictionary(this.Context.Variables); - - this.Helper = serviceProvider.GetService(); - } - - public IPreprocessContext Context { get; } - - public IPreprocessHelper Helper { get; } - - public List IncludedFiles { get; } = new List(); - - public XDocument Output { get; } = new XDocument(); - - public Stack CurrentFileStack { get; } = new Stack(); - - public Dictionary ExtensionsByPrefix { get; } = new Dictionary(); - - public Stack IncludeNextStack { get; } = new Stack(); - - public Stack SourceStack { get; } = new Stack(); - } - } -} diff --git a/src/WixToolset.Core/Properties/AssemblyInfo.cs b/src/WixToolset.Core/Properties/AssemblyInfo.cs deleted file mode 100644 index 81274e3f..00000000 --- a/src/WixToolset.Core/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -// 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. - -using System; -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyCulture("")] -[assembly: CLSCompliant(false)] -[assembly: ComVisible(false)] diff --git a/src/WixToolset.Core/ResolveContext.cs b/src/WixToolset.Core/ResolveContext.cs deleted file mode 100644 index 638c8079..00000000 --- a/src/WixToolset.Core/ResolveContext.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Threading; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ResolveContext : IResolveContext - { - internal ResolveContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public IReadOnlyCollection BindPaths { get; set; } - - public IReadOnlyCollection Extensions { get; set; } - - public IReadOnlyCollection ExtensionData { get; set; } - - public IReadOnlyCollection FilterCultures { get; set; } - - public string IntermediateFolder { get; set; } - - public Intermediate IntermediateRepresentation { get; set; } - - public IReadOnlyCollection Localizations { get; set; } - - public IVariableResolver VariableResolver { get; set; } - - public bool AllowUnresolvedVariables { get; set; } - - public CancellationToken CancellationToken { get; set; } - } -} diff --git a/src/WixToolset.Core/ResolveFileResult.cs b/src/WixToolset.Core/ResolveFileResult.cs deleted file mode 100644 index f6e201d4..00000000 --- a/src/WixToolset.Core/ResolveFileResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -// 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 -{ - using System.Collections.Generic; - using WixToolset.Extensibility.Data; - - internal class ResolveFileResult : IResolveFileResult - { - public string Path { get; set; } - - public IReadOnlyCollection CheckedPaths { get; set; } - } -} diff --git a/src/WixToolset.Core/ResolveResult.cs b/src/WixToolset.Core/ResolveResult.cs deleted file mode 100644 index fa8e09b7..00000000 --- a/src/WixToolset.Core/ResolveResult.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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 -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - - internal class ResolveResult : IResolveResult - { - public int? Codepage { get; set; } - - public int? SummaryInformationCodepage { get; set; } - - public int? PackageLcid { get; set; } - - public IReadOnlyCollection DelayedFields { get; set; } - - public IReadOnlyCollection ExpectedEmbeddedFiles { get; set; } - - public Intermediate IntermediateRepresentation { get; set; } - } -} diff --git a/src/WixToolset.Core/ResolvedCabinet.cs b/src/WixToolset.Core/ResolvedCabinet.cs deleted file mode 100644 index be04831f..00000000 --- a/src/WixToolset.Core/ResolvedCabinet.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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 -{ - using WixToolset.Extensibility.Data; - - /// - /// Data returned from build file manager ResolveCabinet callback. - /// - internal class ResolvedCabinet : IResolvedCabinet - { - /// - /// Gets or sets the build option for the resolved cabinet. - /// - public CabinetBuildOption BuildOption { get; set; } - - /// - /// Gets or sets the path for the resolved cabinet. - /// - public string Path { get; set; } - } -} diff --git a/src/WixToolset.Core/Resolver.cs b/src/WixToolset.Core/Resolver.cs deleted file mode 100644 index e93f8e1b..00000000 --- a/src/WixToolset.Core/Resolver.cs +++ /dev/null @@ -1,304 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using WixToolset.Core.Bind; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Resolver for the WiX toolset. - /// - internal class Resolver : IResolver - { - internal Resolver(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - this.Messaging = serviceProvider.GetService(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - public IResolveResult Resolve(IResolveContext context) - { - foreach (var extension in context.Extensions) - { - extension.PreResolve(context); - } - - ResolveResult resolveResult = null; - try - { - var filteredLocalizations = FilterLocalizations(context); - - var variableResolver = this.CreateVariableResolver(context, filteredLocalizations); - - this.LocalizeUI(variableResolver, context.IntermediateRepresentation); - - resolveResult = this.DoResolve(context, variableResolver); - - var primaryLocalization = filteredLocalizations.FirstOrDefault(); - - if (primaryLocalization != null) - { - this.TryGetCultureInfo(primaryLocalization.Culture, out var cultureInfo); - - resolveResult.Codepage = primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; - - resolveResult.SummaryInformationCodepage = primaryLocalization.SummaryInformationCodepage ?? primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; - - resolveResult.PackageLcid = cultureInfo?.LCID; - } - } - finally - { - foreach (var extension in context.Extensions) - { - extension.PostResolve(resolveResult); - } - } - - return resolveResult; - } - - private ResolveResult DoResolve(IResolveContext context, IVariableResolver variableResolver) - { - var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch); - - var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); - - IReadOnlyCollection delayedFields; - { - var command = new ResolveFieldsCommand(); - command.Messaging = this.Messaging; - command.BuildingPatch = buildingPatch; - command.VariableResolver = variableResolver; - command.BindPaths = context.BindPaths; - command.Extensions = context.Extensions; - command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - command.IntermediateFolder = context.IntermediateFolder; - command.Intermediate = context.IntermediateRepresentation; - command.SupportDelayedResolution = true; - command.AllowUnresolvedVariables = context.AllowUnresolvedVariables; - command.Execute(); - - delayedFields = command.DelayedFields; - } - -#if TODO_PATCHING - if (context.IntermediateRepresentation.SubStorages != null) - { - foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages) - { - var command = new ResolveFieldsCommand(); - command.BuildingPatch = buildingPatch; - command.BindVariableResolver = context.WixVariableResolver; - command.BindPaths = context.BindPaths; - command.Extensions = context.Extensions; - command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; - command.IntermediateFolder = context.IntermediateFolder; - command.Intermediate = context.IntermediateRepresentation; - command.SupportDelayedResolution = false; - command.Execute(); - } - } -#endif - - var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); - - context.IntermediateRepresentation.UpdateLevel(IntermediateLevels.Resolved); - - return new ResolveResult - { - ExpectedEmbeddedFiles = expectedEmbeddedFiles, - DelayedFields = delayedFields, - IntermediateRepresentation = context.IntermediateRepresentation - }; - } - - /// - /// Localize dialogs and controls. - /// - private void LocalizeUI(IVariableResolver variableResolver, Intermediate intermediate) - { - foreach (var section in intermediate.Sections) - { - foreach (var symbol in section.Symbols.OfType()) - { - if (variableResolver.TryGetLocalizedControl(symbol.Id.Id, null, out var localizedControl)) - { - if (CompilerConstants.IntegerNotSet != localizedControl.X) - { - symbol.HCentering = localizedControl.X; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Y) - { - symbol.VCentering = localizedControl.Y; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Width) - { - symbol.Width = localizedControl.Width; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Height) - { - symbol.Height = localizedControl.Height; - } - - symbol.RightAligned |= localizedControl.RightAligned; - symbol.RightToLeft |= localizedControl.RightToLeft; - symbol.LeftScroll |= localizedControl.LeftScroll; - - if (!String.IsNullOrEmpty(localizedControl.Text)) - { - symbol.Title = localizedControl.Text; - } - } - } - - foreach (var symbol in section.Symbols.OfType()) - { - if (variableResolver.TryGetLocalizedControl(symbol.DialogRef, symbol.Control, out var localizedControl)) - { - if (CompilerConstants.IntegerNotSet != localizedControl.X) - { - symbol.X = localizedControl.X; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Y) - { - symbol.Y = localizedControl.Y; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Width) - { - symbol.Width = localizedControl.Width; - } - - if (CompilerConstants.IntegerNotSet != localizedControl.Height) - { - symbol.Height = localizedControl.Height; - } - - symbol.RightAligned |= localizedControl.RightAligned; - symbol.RightToLeft |= localizedControl.RightToLeft; - symbol.LeftScroll |= localizedControl.LeftScroll; - - if (!String.IsNullOrEmpty(localizedControl.Text)) - { - symbol.Text = localizedControl.Text; - } - } - } - } - } - - private IVariableResolver CreateVariableResolver(IResolveContext context, IEnumerable filteredLocalizations) - { - var variableResolver = this.ServiceProvider.GetService(); - - foreach (var localization in filteredLocalizations) - { - variableResolver.AddLocalization(localization); - } - - // Gather all the wix variables. - var wixVariableSymbols = context.IntermediateRepresentation.Sections.SelectMany(s => s.Symbols).OfType(); - foreach (var symbol in wixVariableSymbols) - { - variableResolver.AddVariable(symbol.SourceLineNumbers, symbol.Id.Id, symbol.Value, symbol.Overridable); - } - - return variableResolver; - } - - private bool TryGetCultureInfo(string culture, out CultureInfo cultureInfo) - { - cultureInfo = null; - - if (!String.IsNullOrEmpty(culture)) - { - try - { - cultureInfo = new CultureInfo(culture, useUserOverride: false); - } - catch - { - this.Messaging.Write(""); - } - } - - return cultureInfo != null; - } - - private static IEnumerable FilterLocalizations(IResolveContext context) - { - var result = new List(); - var filter = CalculateCultureFilter(context); - - var localizations = context.Localizations.Concat(context.IntermediateRepresentation.Localizations).ToList(); - - AddFilteredLocalizations(result, filter, localizations); - - // Filter localizations provided by extensions with data. - var creator = context.ServiceProvider.GetService(); - - foreach (var data in context.ExtensionData) - { - var library = data.GetLibrary(creator); - - if (library?.Localizations != null && library.Localizations.Any()) - { - var extensionFilter = (!filter.Any() && data.DefaultCulture != null) ? new[] { data.DefaultCulture } : filter; - - AddFilteredLocalizations(result, extensionFilter, library.Localizations); - } - } - - return result; - } - - private static IEnumerable CalculateCultureFilter(IResolveContext context) - { - var filter = context.FilterCultures ?? Array.Empty(); - - // If no filter was specified, look for a language neutral localization file specified - // from the command-line (not embedded in the intermediate). If found, filter on language - // neutral. - if (!filter.Any() && context.Localizations.Any(l => String.IsNullOrEmpty(l.Culture))) - { - filter = new[] { String.Empty }; - } - - return filter; - } - - private static void AddFilteredLocalizations(List result, IEnumerable filter, IEnumerable localizations) - { - // If there is no filter, return all localizations. - if (!filter.Any()) - { - result.AddRange(localizations); - } - else // filter localizations in order specified by the filter - { - foreach (var culture in filter) - { - result.AddRange(localizations.Where(l => culture.Equals(l.Culture, StringComparison.OrdinalIgnoreCase) || String.IsNullOrEmpty(l.Culture))); - } - } - } - } -} diff --git a/src/WixToolset.Core/SourceFile.cs b/src/WixToolset.Core/SourceFile.cs deleted file mode 100644 index d7ea7a50..00000000 --- a/src/WixToolset.Core/SourceFile.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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 -{ - internal class SourceFile - { - public SourceFile(string sourcePath, string outputPath) - { - this.SourcePath = sourcePath; - this.OutputPath = outputPath; - } - - public string OutputPath { get; } - - public string SourcePath { get; } - } -} diff --git a/src/WixToolset.Core/UnbindContext.cs b/src/WixToolset.Core/UnbindContext.cs deleted file mode 100644 index c3817a08..00000000 --- a/src/WixToolset.Core/UnbindContext.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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 -{ - using System; - using WixToolset.Extensibility.Data; - - internal class UnbindContext : IUnbindContext - { - internal UnbindContext(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - public IServiceProvider ServiceProvider { get; } - - public string ExportBasePath { get; set; } - - public string InputFilePath { get; set; } - - public string IntermediateFolder { get; set; } - - public bool IsAdminImage { get; set; } - - public bool SuppressExtractCabinets { get; set; } - - public bool SuppressDemodularization { get; set; } - } -} diff --git a/src/WixToolset.Core/Unbinder.cs b/src/WixToolset.Core/Unbinder.cs deleted file mode 100644 index 3ef77083..00000000 --- a/src/WixToolset.Core/Unbinder.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.IO; - using WixToolset.Data; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - /// - /// Unbinder core of the WiX toolset. - /// - internal sealed class Unbinder : IUnbinder - { - public Unbinder(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - - var extensionManager = this.ServiceProvider.GetService(); - this.BackendFactories = extensionManager.GetServices(); - } - - public IServiceProvider ServiceProvider { get; } - - public IEnumerable BackendFactories { get; } - - /// - /// Gets or sets whether the input msi is an admin image. - /// - /// Set to true if the input msi is part of an admin image. - public bool IsAdminImage { get; set; } - - /// - /// Gets or sets the option to suppress demodularizing values. - /// - /// The option to suppress demodularizing values. - public bool SuppressDemodularization { get; set; } - - /// - /// Gets or sets the option to suppress extracting cabinets. - /// - /// The option to suppress extracting cabinets. - public bool SuppressExtractCabinets { get; set; } - - /// - /// Gets or sets the temporary path for the Binder. If left null, the binder - /// will use %TEMP% environment variable. - /// - /// Path to temp files. - public string TempFilesLocation => Path.GetTempPath(); - - /// - /// Unbind a Windows Installer file. - /// - /// The Windows Installer file. - /// The type of output to create. - /// The path where files should be exported. - /// The output representing the database. - public Intermediate Unbind(string file, OutputType outputType, string exportBasePath) - { - if (!File.Exists(file)) - { - if (OutputType.Transform == outputType) - { - throw new WixException(ErrorMessages.FileNotFound(null, file, "Transform")); - } - else - { - throw new WixException(ErrorMessages.FileNotFound(null, file, "Database")); - } - } - - // if we don't have the temporary files object yet, get one - Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there - - var context = new UnbindContext(this.ServiceProvider); - context.InputFilePath = file; - context.ExportBasePath = exportBasePath; - context.IntermediateFolder = this.TempFilesLocation; - context.IsAdminImage = this.IsAdminImage; - context.SuppressDemodularization = this.SuppressDemodularization; - context.SuppressExtractCabinets = this.SuppressExtractCabinets; - - foreach (var factory in this.BackendFactories) - { - if (factory.TryCreateBackend(outputType.ToString(), file, out var backend)) - { - return backend.Unbind(context); - } - } - - // TODO: Display message that could not find a unbinder for output type? - - return null; - } - } -} diff --git a/src/WixToolset.Core/VariableResolution.cs b/src/WixToolset.Core/VariableResolution.cs deleted file mode 100644 index 3b34e294..00000000 --- a/src/WixToolset.Core/VariableResolution.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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 -{ - using WixToolset.Extensibility.Services; - - internal class VariableResolution : IVariableResolution - { - /// - /// Indicates whether the variable should be delay resolved. - /// - public bool DelayedResolve { get; set; } - - /// - /// Indicates whether the value is the default value of the variable. - /// - public bool IsDefault { get; set; } - - /// - /// Indicates whether the value changed. - /// - public bool UpdatedValue { get; set; } - - /// - /// Resolved value. - /// - public string Value { get; set; } - } -} \ No newline at end of file diff --git a/src/WixToolset.Core/VariableResolver.cs b/src/WixToolset.Core/VariableResolver.cs deleted file mode 100644 index 437cabb7..00000000 --- a/src/WixToolset.Core/VariableResolver.cs +++ /dev/null @@ -1,197 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using System.Text; - using WixToolset.Data; - using WixToolset.Data.Bind; - using WixToolset.Extensibility.Services; - - /// - /// WiX variable resolver. - /// - internal class VariableResolver : IVariableResolver - { - private readonly Dictionary locVariables; - private readonly Dictionary wixVariables; - private readonly Dictionary localizedControls; - - /// - /// Instantiate a new VariableResolver. - /// - internal VariableResolver(IServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - this.Messaging = serviceProvider.GetService(); - - this.locVariables = new Dictionary(); - this.wixVariables = new Dictionary(); - this.localizedControls = new Dictionary(); - } - - private IServiceProvider ServiceProvider { get; } - - private IMessaging Messaging { get; } - - public int VariableCount => this.wixVariables.Count; - - public void AddLocalization(Localization localization) - { - foreach (var variable in localization.Variables) - { - if (!TryAddWixVariable(this.locVariables, variable)) - { - this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(variable.SourceLineNumbers, variable.Id)); - } - } - - foreach (KeyValuePair localizedControl in localization.LocalizedControls) - { - if (!this.localizedControls.ContainsKey(localizedControl.Key)) - { - this.localizedControls.Add(localizedControl.Key, localizedControl.Value); - } - } - } - - public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable) - { - var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber }; - - if (!TryAddWixVariable(this.wixVariables, bindVariable)) - { - this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name)); - } - } - - public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value) - { - return this.ResolveVariables(sourceLineNumbers, value, errorOnUnknown: true); - } - - public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl) - { - var key = LocalizedControl.GetKey(dialog, control); - return this.localizedControls.TryGetValue(key, out localizedControl); - } - - public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown) - { - var start = 0; - var defaulted = true; - var delayed = false; - var updated = false; - - while (Common.TryParseWixVariable(value, start, out var parsed)) - { - var variableNamespace = parsed.Namespace; - var variableId = parsed.Name; - var variableDefaultValue = parsed.DefaultValue; - - // check for an escape sequence of !! indicating the match is not a variable expression - if (0 < parsed.Index && '!' == value[parsed.Index - 1]) - { - var sb = new StringBuilder(value); - sb.Remove(parsed.Index - 1, 1); - value = sb.ToString(); - - updated = true; - start = parsed.Index + parsed.Length - 1; - - continue; - } - - string resolvedValue = null; - - if ("loc" == variableNamespace) - { - // localization variables do not support inline default values - if (variableDefaultValue != null) - { - this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue)); - continue; - } - - if (this.locVariables.TryGetValue(variableId, out var bindVariable)) - { - resolvedValue = bindVariable.Value; - } - } - else if ("wix" == variableNamespace) - { - if (this.wixVariables.TryGetValue(variableId, out var bindVariable)) - { - resolvedValue = bindVariable.Value ?? String.Empty; - defaulted = false; - } - else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified - { - resolvedValue = variableDefaultValue; - } - } - - if ("bind" == variableNamespace) - { - // Can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort. - delayed = true; - start = parsed.Index + parsed.Length - 1; - } - else - { - // insert the resolved value if it was found or display an error - if (null != resolvedValue) - { - if (parsed.Index == 0 && parsed.Length == value.Length) - { - value = resolvedValue; - } - else - { - var sb = new StringBuilder(value); - sb.Remove(parsed.Index, parsed.Length); - sb.Insert(parsed.Index, resolvedValue); - value = sb.ToString(); - } - - updated = true; - start = parsed.Index; - } - else - { - if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable - { - this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId)); - } - else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable - { - this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId)); - } - - start = parsed.Index + parsed.Length; - } - } - } - - return new VariableResolution - { - DelayedResolve = delayed, - IsDefault = defaulted, - UpdatedValue = updated, - Value = value, - }; - } - - private static bool TryAddWixVariable(IDictionary variables, BindVariable variable) - { - if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable)) - { - variables[variable.Id] = variable; - return true; - } - - return variable.Overridable; - } - } -} diff --git a/src/WixToolset.Core/WixToolset.Core.csproj b/src/WixToolset.Core/WixToolset.Core.csproj deleted file mode 100644 index 7242d500..00000000 --- a/src/WixToolset.Core/WixToolset.Core.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - netstandard2.0 - $(TargetFrameworks);net461;net472 - Core - WiX Toolset Core - embedded - true - true - true - - - - - <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd - - - - - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject b/src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject deleted file mode 100644 index c6001ebe..00000000 --- a/src/WixToolset.Core/WixToolset.Core.v3.ncrunchproject +++ /dev/null @@ -1,7 +0,0 @@ - - - - ..\..\version.json - - - \ No newline at end of file diff --git a/src/WixToolset.Core/WixToolsetServiceProvider.cs b/src/WixToolset.Core/WixToolsetServiceProvider.cs deleted file mode 100644 index 5d700ba0..00000000 --- a/src/WixToolset.Core/WixToolsetServiceProvider.cs +++ /dev/null @@ -1,117 +0,0 @@ -// 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 -{ - using System; - using System.Collections.Generic; - using WixToolset.Core.CommandLine; - using WixToolset.Core.ExtensibilityServices; - using WixToolset.Data; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class WixToolsetServiceProvider : IWixToolsetCoreServiceProvider - { - public WixToolsetServiceProvider() - { - this.CreationFunctions = new Dictionary, object>>(); - this.Singletons = new Dictionary(); - - // Singletons. - this.AddService((provider, singletons) => AddSingleton(singletons, new ExtensionManager(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new Messaging())); - this.AddService((provider, singletons) => AddSingleton(singletons, new SymbolDefinitionCreator(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new ParseHelper(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new PreprocessHelper(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new BackendHelper(provider))); - this.AddService((provider, singletons) => AddSingleton(singletons, new PathResolver())); - this.AddService((provider, singletons) => AddSingleton(singletons, new WixBranding())); - - // Transients. - this.AddService((provider, singletons) => new CommandLineArguments(provider)); - this.AddService((provider, singletons) => new CommandLineContext(provider)); - this.AddService((provider, singletons) => new CommandLine.CommandLine(provider)); - this.AddService((provider, singletons) => new PreprocessContext(provider)); - this.AddService((provider, singletons) => new CompileContext(provider)); - this.AddService((provider, singletons) => new LibraryContext(provider)); - this.AddService((provider, singletons) => new LinkContext(provider)); - this.AddService((provider, singletons) => new ResolveContext(provider)); - this.AddService((provider, singletons) => new BindContext(provider)); - this.AddService((provider, singletons) => new DecompileContext(provider)); - this.AddService((provider, singletons) => new LayoutContext(provider)); - this.AddService((provider, singletons) => new InscribeContext(provider)); - this.AddService((provider, singletons) => new UnbindContext(provider)); - - this.AddService((provider, singletons) => new BindFileWithPath()); - this.AddService((provider, singletons) => new BindPath()); - this.AddService((provider, singletons) => new BindResult()); - this.AddService((provider, singletons) => new ComponentKeyPath()); - this.AddService((provider, singletons) => new DecompileResult()); - this.AddService((provider, singletons) => new IncludedFile()); - this.AddService((provider, singletons) => new PreprocessResult()); - this.AddService((provider, singletons) => new ResolvedDirectory()); - this.AddService((provider, singletons) => new ResolveFileResult()); - this.AddService((provider, singletons) => new ResolveResult()); - this.AddService((provider, singletons) => new ResolvedCabinet()); - this.AddService((provider, singletons) => new VariableResolution()); - - this.AddService((provider, singletons) => new Binder(provider)); - this.AddService((provider, singletons) => new Compiler(provider)); - this.AddService((provider, singletons) => new Decompiler(provider)); - this.AddService((provider, singletons) => new LayoutCreator(provider)); - this.AddService((provider, singletons) => new Preprocessor(provider)); - this.AddService((provider, singletons) => new Librarian(provider)); - this.AddService((provider, singletons) => new Linker(provider)); - this.AddService((provider, singletons) => new Resolver(provider)); - this.AddService((provider, singletons) => new Unbinder(provider)); - - this.AddService((provider, singletons) => new LocalizationParser(provider)); - this.AddService((provider, singletons) => new VariableResolver(provider)); - } - - private Dictionary, object>> CreationFunctions { get; } - - private Dictionary Singletons { get; } - - public object GetService(Type serviceType) - { - if (serviceType == null) - { - throw new ArgumentNullException(nameof(serviceType)); - } - - if (!this.Singletons.TryGetValue(serviceType, out var service)) - { - if (this.CreationFunctions.TryGetValue(serviceType, out var creationFunction)) - { - service = creationFunction(this, this.Singletons); - -#if DEBUG - if (!serviceType.IsAssignableFrom(service?.GetType())) - { - throw new InvalidOperationException($"Creation function for service type: {serviceType.Name} created incompatible service with type: {service?.GetType()}"); - } -#endif - } - } - - return service; - } - - public void AddService(Type serviceType, Func, object> creationFunction) - { - this.CreationFunctions[serviceType] = creationFunction; - } - - public void AddService(Func, T> creationFunction) where T : class - { - this.AddService(typeof(T), creationFunction); - } - - private static T AddSingleton(Dictionary singletons, T service) where T : class - { - singletons.Add(typeof(T), service); - return service; - } - } -} diff --git a/src/WixToolset.Core/WixToolsetServiceProviderFactory.cs b/src/WixToolset.Core/WixToolsetServiceProviderFactory.cs deleted file mode 100644 index 8e07070b..00000000 --- a/src/WixToolset.Core/WixToolsetServiceProviderFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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 -{ - using WixToolset.Extensibility.Services; - - /// - /// Class for creating . - /// - public static class WixToolsetServiceProviderFactory - { - /// - /// Creates a new . - /// - /// The created - public static IWixToolsetCoreServiceProvider CreateServiceProvider() - { - return new WixToolsetServiceProvider(); - } - } -} diff --git a/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj b/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj deleted file mode 100644 index 88210bd4..00000000 --- a/src/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - netcoreapp3.1 - false - Exe - - - - - - - - - $(BaseOutputPath)TestData\$(Configuration)\example.wixlib - - - - - - - - - - diff --git a/src/test/CompileCoreTestExtensionWixlib/Program.cs b/src/test/CompileCoreTestExtensionWixlib/Program.cs deleted file mode 100644 index 323b5e5e..00000000 --- a/src/test/CompileCoreTestExtensionWixlib/Program.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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. - -using System.Collections.Generic; -using WixToolset.Core.TestPackage; - -namespace CompileCoreTestExtensionWixlib -{ - // We want to be able to test Core with extensions, but there's no easy way to build an extension without Tools. - // So we have this helper exe. - public class Program - { - public static void Main(string[] args) - { - var intermediateFolder = args[0]; - var wixlibPath = args[1]; - - var buildArgs = new List(); - buildArgs.Add("build"); - buildArgs.Add("-bindfiles"); - buildArgs.Add("-bindpath"); - buildArgs.Add("Data"); - buildArgs.Add("-intermediateFolder"); - buildArgs.Add(intermediateFolder); - buildArgs.Add("-o"); - buildArgs.Add(wixlibPath); - - foreach (var path in args[2].Split(';')) - { - buildArgs.Add(path); - } - - var result = WixRunner.Execute(buildArgs.ToArray()); - - result.AssertSuccess(); - } - } -} diff --git a/src/test/Example.Extension/Data/example.txt b/src/test/Example.Extension/Data/example.txt deleted file mode 100644 index 1b4ffe8a..00000000 --- a/src/test/Example.Extension/Data/example.txt +++ /dev/null @@ -1 +0,0 @@ -This is example.txt. \ No newline at end of file diff --git a/src/test/Example.Extension/Data/example.wxs b/src/test/Example.Extension/Data/example.wxs deleted file mode 100644 index af5d5086..00000000 --- a/src/test/Example.Extension/Data/example.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/Example.Extension/Example.Extension.csproj b/src/test/Example.Extension/Example.Extension.csproj deleted file mode 100644 index 9be10d35..00000000 --- a/src/test/Example.Extension/Example.Extension.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - netcoreapp3.1 - false - embedded - - - - - - - - - - - - - - - - diff --git a/src/test/Example.Extension/ExampleCompilerExtension.cs b/src/test/Example.Extension/ExampleCompilerExtension.cs deleted file mode 100644 index 5b8d4b3f..00000000 --- a/src/test/Example.Extension/ExampleCompilerExtension.cs +++ /dev/null @@ -1,195 +0,0 @@ -// 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 Example.Extension -{ - using System; - using System.Collections.Generic; - using System.Xml.Linq; - using WixToolset.Data; - using WixToolset.Extensibility; - - internal class ExampleCompilerExtension : BaseCompilerExtension - { - public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs"; - public string BundleExtensionId => "ExampleBundleExtension"; - - public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) - { - var processed = false; - - switch (parentElement.Name.LocalName) - { - case "Bundle": - case "Fragment": - switch (element.Name.LocalName) - { - case "ExampleEnsureTable": - this.ParseExampleEnsureTableElement(intermediate, section, element); - processed = true; - break; - case "ExampleSearch": - this.ParseExampleSearchElement(intermediate, section, element); - processed = true; - break; - case "ExampleSearchRef": - this.ParseExampleSearchRefElement(intermediate, section, element); - processed = true; - break; - } - break; - case "Component": - switch (element.Name.LocalName) - { - case "Example": - this.ParseExampleElement(intermediate, section, element); - processed = true; - break; - } - break; - } - - if (!processed) - { - base.ParseElement(intermediate, section, parentElement, element, context); - } - } - - private void ParseExampleElement(Intermediate intermediate, IntermediateSection section, XElement element) - { - var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); - Identifier id = null; - string value = null; - - foreach (var attrib in element.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - - case "Value": - value = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - - default: - this.ParseHelper.UnexpectedAttribute(element, attrib); - break; - } - } - else - { - this.ParseAttribute(intermediate, section, element, attrib, null); - } - } - - if (null == id) - { - //this.Messaging(WixErrors.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); - } - - if (!this.Messaging.EncounteredError) - { - var symbol = this.ParseHelper.CreateSymbol(section, sourceLineNumbers, "Example", id); - symbol.Set(0, value); - } - } - - private void ParseExampleEnsureTableElement(Intermediate intermediate, IntermediateSection section, XElement element) - { - var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); - this.ParseHelper.EnsureTable(section, sourceLineNumbers, ExampleTableDefinitions.NotInAll); - } - - private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element) - { - var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); - Identifier id = null; - string searchFor = null; - string variable = null; - string condition = null; - string after = null; - - foreach (var attrib in element.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Variable": - variable = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "Condition": - condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "After": - after = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "SearchFor": - searchFor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); - break; - - default: - this.ParseHelper.UnexpectedAttribute(element, attrib); - break; - } - } - else - { - this.ParseAttribute(intermediate, section, element, attrib, null); - } - } - - if (null == id) - { - this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); - } - - if (!this.Messaging.EncounteredError) - { - this.ParseHelper.CreateWixSearchSymbol(section, sourceLineNumbers, element.Name.LocalName, id, variable, condition, after, this.BundleExtensionId); - } - - if (!this.Messaging.EncounteredError) - { - var symbol = section.AddSymbol(new ExampleSearchSymbol(sourceLineNumbers, id) - { - SearchFor = searchFor, - }); - } - } - - private void ParseExampleSearchRefElement(Intermediate intermediate, IntermediateSection section, XElement element) - { - var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); - - foreach (var attrib in element.Attributes()) - { - if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) - { - switch (attrib.Name.LocalName) - { - case "Id": - var refId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); - this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, ExampleSymbolDefinitions.ExampleSearch, refId); - break; - default: - this.ParseHelper.UnexpectedAttribute(element, attrib); - break; - } - } - else - { - this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); - } - } - - this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); - } - } -} diff --git a/src/test/Example.Extension/ExampleExtensionData.cs b/src/test/Example.Extension/ExampleExtensionData.cs deleted file mode 100644 index 91d60eb9..00000000 --- a/src/test/Example.Extension/ExampleExtensionData.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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 Example.Extension -{ - using WixToolset.Data; - using WixToolset.Extensibility; - - internal class ExampleExtensionData : IExtensionData - { - public string DefaultCulture => null; - - public Intermediate GetLibrary(ISymbolDefinitionCreator symbolDefinitions) - { - return Intermediate.Load(typeof(ExampleExtensionData).Assembly, "Example.Extension.Example.wixlib", symbolDefinitions); - } - - public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition) - { - symbolDefinition = ExampleSymbolDefinitions.ByName(name); - return symbolDefinition != null; - } - } -} \ No newline at end of file diff --git a/src/test/Example.Extension/ExampleExtensionFactory.cs b/src/test/Example.Extension/ExampleExtensionFactory.cs deleted file mode 100644 index e54561ee..00000000 --- a/src/test/Example.Extension/ExampleExtensionFactory.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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 Example.Extension -{ - using System; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Services; - - public class ExampleExtensionFactory : IExtensionFactory - { - private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension; - - public ExampleExtensionFactory(IWixToolsetCoreServiceProvider serviceProvider) - { - this.ServiceProvider = serviceProvider; - } - - /// - /// This exists just to show it is possible to get a service provider to the extension factory. - /// - private IWixToolsetCoreServiceProvider ServiceProvider { get; } - - public bool TryCreateExtension(Type extensionType, out object extension) - { - if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension)) - { - if (this.preprocessorExtension == null) - { - this.preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine(); - } - - extension = this.preprocessorExtension; - } - else if (extensionType == typeof(ICompilerExtension)) - { - extension = new ExampleCompilerExtension(); - } - else if (extensionType == typeof(IExtensionData)) - { - extension = new ExampleExtensionData(); - } - else if (extensionType == typeof(IWindowsInstallerBackendBinderExtension)) - { - extension = new ExampleWindowsInstallerBackendExtension(); - } - else - { - extension = null; - } - - return extension != null; - } - } -} diff --git a/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs b/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs deleted file mode 100644 index 7244798a..00000000 --- a/src/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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 Example.Extension -{ - using System; - using System.Collections.Generic; - using WixToolset.Extensibility; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine - { - private string exampleValueFromCommandLine; - - public IReadOnlyCollection CommandLineSwitches => throw new NotImplementedException(); - - public ExamplePreprocessorExtensionAndCommandLine() - { - this.Prefixes = new[] { "ex" }; - } - - public void PreParse(ICommandLineContext context) - { - } - - public bool TryParseArgument(ICommandLineParser parser, string argument) - { - if (parser.IsSwitch(argument) && argument.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase)) - { - this.exampleValueFromCommandLine = parser.GetNextArgumentOrError(argument); - return true; - } - - return false; - } - - public bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) - { - command = null; - return false; - } - - public void PostParse() - { - } - - public override string GetVariableValue(string prefix, string name) - { - if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase)) - { - return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine; - } - - return null; - } - } -} diff --git a/src/test/Example.Extension/ExampleRow.cs b/src/test/Example.Extension/ExampleRow.cs deleted file mode 100644 index fc20c6c9..00000000 --- a/src/test/Example.Extension/ExampleRow.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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 Example.Extension -{ - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - - public class ExampleRow : Row - { - public ExampleRow(SourceLineNumber sourceLineNumbers, Table table) - : base(sourceLineNumbers, table) - { - } - - public ExampleRow(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) - : base(sourceLineNumbers, tableDefinition) - { - } - - public string Example - { - get { return (string)this.Fields[0].Data; } - set { this.Fields[0].Data = value; } - } - - public string Value - { - get { return (string)this.Fields[1].Data; } - set { this.Fields[1].Data = value; } - } - } -} diff --git a/src/test/Example.Extension/ExampleSearchSymbol.cs b/src/test/Example.Extension/ExampleSearchSymbol.cs deleted file mode 100644 index 40a39292..00000000 --- a/src/test/Example.Extension/ExampleSearchSymbol.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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 Example.Extension -{ - using WixToolset.Data; - - public enum ExampleSearchSymbolFields - { - SearchFor, - } - - public class ExampleSearchSymbol : IntermediateSymbol - { - public ExampleSearchSymbol() : base(ExampleSymbolDefinitions.ExampleSearch, null, null) - { - } - - public ExampleSearchSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.ExampleSearch, sourceLineNumber, id) - { - } - - public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index]; - - public string SearchFor - { - get => this.Fields[(int)ExampleSearchSymbolFields.SearchFor]?.AsString(); - set => this.Set((int)ExampleSearchSymbolFields.SearchFor, value); - } - } -} diff --git a/src/test/Example.Extension/ExampleSymbol.cs b/src/test/Example.Extension/ExampleSymbol.cs deleted file mode 100644 index 314087e9..00000000 --- a/src/test/Example.Extension/ExampleSymbol.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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 Example.Extension -{ - using WixToolset.Data; - - public enum ExampleSymbolFields - { - Value, - } - - public class ExampleSymbol : IntermediateSymbol - { - public ExampleSymbol() : base(ExampleSymbolDefinitions.Example, null, null) - { - } - - public ExampleSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.Example, sourceLineNumber, id) - { - } - - public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index]; - - public string Value - { - get => this.Fields[(int)ExampleSymbolFields.Value]?.AsString(); - set => this.Set((int)ExampleSymbolFields.Value, value); - } - } -} diff --git a/src/test/Example.Extension/ExampleSymbolDefinitions.cs b/src/test/Example.Extension/ExampleSymbolDefinitions.cs deleted file mode 100644 index f13d716d..00000000 --- a/src/test/Example.Extension/ExampleSymbolDefinitions.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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 Example.Extension -{ - using System; - using WixToolset.Data; - using WixToolset.Data.Burn; - - public enum ExampleSymbolDefinitionType - { - Example, - ExampleSearch, - } - - public static class ExampleSymbolDefinitions - { - public static readonly IntermediateSymbolDefinition Example = new IntermediateSymbolDefinition( - ExampleSymbolDefinitionType.Example.ToString(), - new[] - { - new IntermediateFieldDefinition(nameof(ExampleSymbolFields.Value), IntermediateFieldType.String), - }, - typeof(ExampleSymbol)); - - public static readonly IntermediateSymbolDefinition ExampleSearch = new IntermediateSymbolDefinition( - ExampleSymbolDefinitionType.ExampleSearch.ToString(), - new[] - { - new IntermediateFieldDefinition(nameof(ExampleSearchSymbolFields.SearchFor), IntermediateFieldType.String), - }, - typeof(ExampleSearchSymbol)); - - static ExampleSymbolDefinitions() - { - ExampleSearch.AddTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag); - } - - public static bool TryGetSymbolType(string name, out ExampleSymbolDefinitionType type) - { - return Enum.TryParse(name, out type); - } - - public static IntermediateSymbolDefinition ByName(string name) - { - if (!TryGetSymbolType(name, out var type)) - { - return null; - } - return ByType(type); - } - - public static IntermediateSymbolDefinition ByType(ExampleSymbolDefinitionType type) - { - switch (type) - { - case ExampleSymbolDefinitionType.Example: - return ExampleSymbolDefinitions.Example; - - case ExampleSymbolDefinitionType.ExampleSearch: - return ExampleSymbolDefinitions.ExampleSearch; - - default: - throw new ArgumentOutOfRangeException(nameof(type)); - } - } - } -} diff --git a/src/test/Example.Extension/ExampleTableDefinitions.cs b/src/test/Example.Extension/ExampleTableDefinitions.cs deleted file mode 100644 index a2b81698..00000000 --- a/src/test/Example.Extension/ExampleTableDefinitions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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 Example.Extension -{ - using WixToolset.Data.WindowsInstaller; - - public static class ExampleTableDefinitions - { - public static readonly TableDefinition ExampleTable = new TableDefinition( - "Wix4Example", - ExampleSymbolDefinitions.Example, - new[] - { - new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), - new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), - }, - strongRowType: typeof(ExampleRow), - symbolIdIsPrimaryKey: true - ); - - public static readonly TableDefinition NotInAll = new TableDefinition( - "TableDefinitionNotExposedByExtension", - null, - new[] - { - new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), - new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), - }, - symbolIdIsPrimaryKey: true - ); - - public static readonly TableDefinition[] All = new[] { ExampleTable }; - } -} diff --git a/src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs b/src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs deleted file mode 100644 index afccc56f..00000000 --- a/src/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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 Example.Extension -{ - using System.Collections.Generic; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; - - internal class ExampleWindowsInstallerBackendExtension : BaseWindowsInstallerBackendBinderExtension - { - public override IReadOnlyCollection TableDefinitions => ExampleTableDefinitions.All; - - public override bool TryProcessSymbol(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData output, TableDefinitionCollection tableDefinitions) - { - if (ExampleSymbolDefinitions.TryGetSymbolType(symbol.Definition.Name, out var symbolType)) - { - switch (symbolType) - { - case ExampleSymbolDefinitionType.Example: - { - var row = (ExampleRow)this.BackendHelper.CreateRow(section, symbol, output, ExampleTableDefinitions.ExampleTable); - row.Example = symbol.Id.Id; - row.Value = symbol[0].AsString(); - } - return true; - } - } - - return base.TryProcessSymbol(section, symbol, output, tableDefinitions); - } - } -} diff --git a/src/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs b/src/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs deleted file mode 100644 index a83da7f6..00000000 --- a/src/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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 WixToolsetTest.Core.Burn -{ - using System; - using WixToolset.Core.Burn.Bundles; - using Xunit; - - public class BurnReaderFixture - { - [Fact] - public void CanReadUInt16Max() - { - var bytes = new byte[] { 0xFF, 0xFF }; - var offset = 0u; - - var result = BurnCommon.ReadUInt16(bytes, offset); - - Assert.Equal(UInt16.MaxValue, result); - } - - [Fact] - public void CanReadUInt32Max() - { - var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; - var offset = 0u; - - var result = BurnCommon.ReadUInt32(bytes, offset); - - Assert.Equal(UInt32.MaxValue, result); - } - - [Fact] - public void CanReadUInt64Max() - { - var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - var offset = 0u; - - var result = BurnCommon.ReadUInt64(bytes, offset); - - Assert.Equal(UInt64.MaxValue, result); - } - } -} diff --git a/src/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj b/src/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj deleted file mode 100644 index 175ee1a9..00000000 --- a/src/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - netcoreapp3.1 - false - embedded - - - - NU1701 - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs deleted file mode 100644 index 47b47ef5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class ApprovedExeFixture - { - [Fact] - public void CanBuildWithApprovedExe() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleWithApprovedExe", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.NotEqual(0, result.ExitCode); - Assert.False(File.Exists(exePath)); - } - } - - [Fact] - public void CanBuildWithApprovedExe64() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleWithApprovedExe", "Bundle64.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.NotEqual(0, result.ExitCode); - Assert.False(File.Exists(exePath)); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs deleted file mode 100644 index 62ffe1eb..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs +++ /dev/null @@ -1,148 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class BadInputFixture - { - [Fact] - public void SwitchIsNotConsideredAnArgument() - { - var result = WixRunner.Execute(new[] - { - "build", - "-bindpath", "-thisisaswitchnotanarg", - }); - - Assert.Single(result.Messages, m => m.Id == (int)ErrorMessages.Ids.ExpectedArgument); - // TODO: when CantBuildSingleExeBundleWithInvalidArgument is fixed, uncomment: - //Assert.Equal((int)ErrorMessages.Ids.ExpectedArgument, result.ExitCode); - } - - [Fact] - public void HandleInvalidIds() - { - var folder = TestData.Get(@"TestData\BadInput"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "InvalidIds.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.Equal(330, result.ExitCode); - } - } - - [Fact] - public void CantBuildSingleExeBundleWithInvalidArgument() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - "-nonexistentswitch", "param", - }); - - Assert.NotEqual(0, result.ExitCode); - Assert.False(File.Exists(exePath)); - } - } - - [Fact] - public void RegistryKeyWithoutAttributesDoesntCrash() - { - var folder = TestData.Get(@"TestData\BadInput"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "RegistryKey.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.InRange(result.ExitCode, 2, Int32.MaxValue); - } - } - - [Fact] - public void BundleVariableWithBadTypeIsRejected() - { - var folder = TestData.Get(@"TestData\BadInput"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleVariable.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.Equal(21, result.ExitCode); - } - } - - [Fact] - public void BundleVariableWithHiddenPersistedIsRejected() - { - var folder = TestData.Get(@"TestData\BadInput"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "HiddenPersistedBundleVariable.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.Equal(193, result.ExitCode); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs deleted file mode 100644 index 39e6b4aa..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs +++ /dev/null @@ -1,96 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class BindVariablesFixture - { - [Fact] - public void CanBuildBundleWithPackageBindVariables() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleBindVariables", "CacheIdFromPackageDescription.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(exePath)); - } - } - - [Fact] - public void CanBuildWithDefaultValue() - { - var folder = TestData.Get(@"TestData", "BindVariables"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "DefaultedVariable.wxs"), - "-bf", - "-intermediateFolder", intermediateFolder, - "-bindpath", folder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - } - } - - [Fact] - public void CannotBuildWixlibWithBinariesFromMissingNamedBindPaths() - { - var folder = TestData.Get(@"TestData", "WixlibWithBinaries"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-bf", - "-bindpath", Path.Combine(folder, "data"), - // Use names that aren't excluded in default .gitignores. - "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", - "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", - "-bindpath", $"{Path.Combine(folder, "data", "alpha")}", - "-bindpath", $"{Path.Combine(folder, "data", "powerpc")}", - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.Equal(103, result.ExitCode); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs deleted file mode 100644 index 9bdc9496..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class BootstrapperApplicationFixture - { - [Fact] - public void CanSetBootstrapperApplicationDllDpiAwareness() - { - var folder = TestData.Get(@"TestData\BootstrapperApplication"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "DpiAwareness.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(wixlibPath); - var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); - var baDllSymbol = allSymbols.OfType() - .SingleOrDefault(); - Assert.NotNull(baDllSymbol); - - Assert.Equal(WixBootstrapperApplicationDpiAwarenessType.GdiScaled, baDllSymbol.DpiAwareness); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs deleted file mode 100644 index b33b8891..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs +++ /dev/null @@ -1,58 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Extensibility.Services; - using Xunit; - - public class BundleExtractionFixture - { - [Fact] - public void CanExtractBundleWithDetachedContainer() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - var baFolderPath = Path.Combine(extractFolderPath, "UX"); - var attachedContainerFolderPath = Path.Combine(extractFolderPath, "AttachedContainer"); - - // TODO: use WixRunner.Execute(string[]) to always go through the command line. - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleWithDetachedContainer", "Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }, serviceProvider, out var messages).Result; - - WixRunnerResult.AssertSuccess(result, messages); - Assert.Empty(messages.Where(m => m.Level == MessageLevel.Warning)); - - Assert.True(File.Exists(exePath)); - - var unbinder = serviceProvider.GetService(); - unbinder.Unbind(exePath, OutputType.Bundle, extractFolderPath); - - Assert.True(File.Exists(Path.Combine(baFolderPath, "manifest.xml"))); - Assert.False(Directory.Exists(attachedContainerFolderPath)); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs deleted file mode 100644 index ab644080..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BundleFixture.cs +++ /dev/null @@ -1,478 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using System.Xml; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.Burn; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Burn; - using WixToolset.Data.Symbols; - using WixToolset.Dtf.Resources; - using Xunit; - - public class BundleFixture - { - [Fact] - public void CanBuildMultiFileBundle() - { - var folder = TestData.Get(@"TestData\SimpleBundle"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), - Path.Combine(folder, "MultiFileBundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.exe") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildSimpleBundle() - { - var folder = TestData.Get(@"TestData\SimpleBundle"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Bundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - Assert.Empty(result.Messages.Where(m => m.Level == MessageLevel.Warning)); - - Assert.True(File.Exists(exePath)); - Assert.True(File.Exists(pdbPath)); - - using (var wixOutput = WixOutput.Read(pdbPath)) - { - - var intermediate = Intermediate.Load(wixOutput); - var section = intermediate.Sections.Single(); - - var bundleSymbol = section.Symbols.OfType().Single(); - Assert.Equal("1.0.0.0", bundleSymbol.Version); - - var previousVersion = bundleSymbol.Fields[(int)WixBundleSymbolFields.Version].PreviousValue; - Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString()); - - var msiSymbol = section.Symbols.OfType().Single(); - Assert.Equal("test.msi", msiSymbol.Id.Id); - - var extractResult = BundleExtractor.ExtractBAContainer(null, exePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var burnManifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); - var extractedBurnManifestData = File.ReadAllText(Path.Combine(baFolderPath, "manifest.xml"), Encoding.UTF8); - Assert.Equal(extractedBurnManifestData, burnManifestData); - - var baManifestData = wixOutput.GetData(BurnConstants.BootstrapperApplicationDataWixOutputStreamName); - var extractedBaManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BootstrapperApplicationData.xml"), Encoding.UTF8); - Assert.Equal(extractedBaManifestData, baManifestData); - - var bextManifestData = wixOutput.GetData(BurnConstants.BundleExtensionDataWixOutputStreamName); - var extractedBextManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BundleExtensionData.xml"), Encoding.UTF8); - Assert.Equal(extractedBextManifestData, bextManifestData); - - var logElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Log"); - var logElement = (XmlNode)Assert.Single(logElements); - Assert.Equal("", logElement.GetTestXml()); - - var registrationElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration"); - var registrationElement = (XmlNode)Assert.Single(registrationElements); - Assert.Equal($"" + - "" + - "", registrationElement.GetTestXml()); - - var msiPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='test.msi']"); - var msiPayload = (XmlNode)Assert.Single(msiPayloads); - Assert.Equal("", - msiPayload.GetTestXml(new Dictionary>() { { "Payload", new List { "FileSize", "Hash" } } })); - } - - var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); - manifestResource.Load(exePath); - var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data); - Assert.Equal("" + - "" + - "" + - "~TestBundle" + - "" + - "" + - "" + - "true/pmPerMonitorV2, PerMonitor" + - "", actualManifestData); - } - } - - [Fact] - public void CanBuildX64Bundle() - { - var folder = TestData.Get(@"TestData\SimpleBundle"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(false, new[] // TODO: go back to elevating warnings as errors. - { - "build", - "-arch", "x64", - Path.Combine(folder, "Bundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - var warning = Assert.Single(result.Messages.Where(m => m.Level == MessageLevel.Warning)); - Assert.Equal((int)WarningMessages.Ids.ExperimentalBundlePlatform, warning.Id); - - Assert.True(File.Exists(exePath)); - Assert.True(File.Exists(pdbPath)); - - var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); - manifestResource.Load(exePath); - var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data); - Assert.Equal("" + - "" + - "" + - "~TestBundle" + - "" + - "" + - "" + - "true/pmPerMonitorV2, PerMonitor" + - "", actualManifestData); - } - } - - [Fact] - public void CanBuildSimpleBundleUsingExtensionBA() - { - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - var folder = TestData.Get(@"TestData\SimpleBundle"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MultiFileBundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.exe") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildSingleExeBundle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(exePath)); - } - } - - [Fact] - public void CanBuildSingleExeRemotePayloadBundle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SingleExeBundle", "SingleExeRemotePayload.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(exePath)); - Assert.True(File.Exists(pdbPath)); - - using (var wixOutput = WixOutput.Read(pdbPath)) - { - var intermediate = Intermediate.Load(wixOutput); - var section = intermediate.Sections.Single(); - - var payloadSymbol = section.Symbols.OfType().Where(x => x.Id.Id == "NetFx462Web").Single(); - Assert.Equal(Int64.MaxValue, payloadSymbol.FileSize); - } - } - } - - [Fact] - public void CantBuildWithDuplicateCacheIds() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "DuplicateCacheIds.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal(8001, result.ExitCode); - } - } - - [Fact] - public void CantBuildWithDuplicatePayloadNames() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "DuplicatePayloadNames.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - var attachedContainerWarnings = result.Messages.Where(m => m.Id == (int)BurnBackendWarnings.Ids.AttachedContainerPayloadCollision) - .Select(m => m.ToString()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "The Payload 'Auto2' has a duplicate Name 'burn.exe' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", - }, attachedContainerWarnings); - - var baContainerErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.BAContainerPayloadCollision) - .Select(m => m.ToString()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "The Payload 'DuplicatePayloadNames.wxs' has a duplicate Name 'fakeba.dll' in the BA container. When extracting the container at runtime, the file will get overwritten.", - "The Payload 'uxTxMXPVMXwQrPTMIGa5WGt93w0Ns' has a duplicate Name 'BootstrapperApplicationData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", - "The Payload 'uxYRbgitOs0K878jn5L_z7LdJ21KI' has a duplicate Name 'BundleExtensionData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", - }, baContainerErrors); - - var externalErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.ExternalPayloadCollision) - .Select(m => m.ToString()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "The external Payload 'HiddenPersistedBundleVariable.wxs' has a duplicate Name 'PayloadCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", - "The external Container 'MsiPackagesContainer' has a duplicate Name 'ContainerCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", - }, externalErrors); - - var packageCacheErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.PackageCachePayloadCollision) - .Select(m => m.ToString()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "The Payload 'test.msi' has a duplicate Name 'test.msi' in package 'test.msi'. When caching the package, the file will get overwritten.", - }, packageCacheErrors); - - Assert.Equal(14, result.Messages.Length); - } - } - - [Fact] - public void CantBuildWithOrphanPayload() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "OrphanPayload.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal((int)LinkerErrors.Ids.OrphanedPayload, result.ExitCode); - } - } - - [Fact] - public void CantBuildWithPackageInMultipleContainers() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "PackageInMultipleContainers.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal((int)LinkerErrors.Ids.PackageInMultipleContainers, result.ExitCode); - } - } - - [Fact] - public void CantBuildWithUnscheduledPackage() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "UnscheduledPackage.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal((int)LinkerErrors.Ids.UnscheduledChainPackage, result.ExitCode); - } - } - - [Fact] - public void CantBuildWithUnscheduledRollbackBoundary() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadInput", "UnscheduledRollbackBoundary.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal((int)LinkerErrors.Ids.UnscheduledRollbackBoundary, result.ExitCode); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs deleted file mode 100644 index 6d769bd6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ /dev/null @@ -1,365 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.IO; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class BundleManifestFixture - { - [Fact] - public void PopulatesBAManifestWithBootstrapperApplicationBundleCustomData() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTableBA"); - Assert.Equal(3, customElements.Count); - Assert.Equal("", customElements[0].GetTestXml()); - Assert.Equal("", customElements[1].GetTestXml()); - Assert.Equal("", customElements[2].GetTestXml()); - } - } - - [Fact] - public void PopulatesBAManifestWithPackageInformation() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "CustomPackageDescription", "CustomPackageDescription.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, ".Data"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var packageElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPackageProperties"); - var ignoreAttributesByElementName = new Dictionary> - { - { "WixPackageProperties", new List { "DownloadSize", "PackageSize", "InstalledSize", "Version" } }, - }; - Assert.Equal(3, packageElements.Count); - Assert.Equal("", packageElements[0].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", packageElements[1].GetTestXml()); - Assert.Equal("", packageElements[2].GetTestXml(ignoreAttributesByElementName)); - } - } - - [Fact] - public void PopulatesBAManifestWithPayloadInformation() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, ".Data"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var payloadElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties"); - var ignoreAttributesByElementName = new Dictionary> - { - { "WixPayloadProperties", new List { "Size" } }, - }; - Assert.Equal(4, payloadElements.Count); - Assert.Equal("", payloadElements[0].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", payloadElements[1].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", payloadElements[2].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", payloadElements[3].GetTestXml(ignoreAttributesByElementName)); - } - } - - [Fact] - public void PopulatesBEManifestWithBundleExtensionBundleCustomData() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var customElements = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='CustomTableExtension']/be:BundleCustomTableBE"); - Assert.Equal(3, customElements.Count); - Assert.Equal("", customElements[0].GetTestXml()); - Assert.Equal("", customElements[1].GetTestXml()); - Assert.Equal("", customElements[2].GetTestXml()); - } - } - - [Fact] - public void PopulatesManifestWithBundleExtension() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleExtension", "BundleExtension.wxs"), - Path.Combine(folder, "BundleExtension", "SimpleBundleExtension.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); - Assert.Equal(1, bundleExtensions.Count); - Assert.Equal("", bundleExtensions[0].GetTestXml()); - - var bundleExtensionPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload[@Id='ExampleBext']"); - Assert.Equal(1, bundleExtensionPayloads.Count); - var ignored = new Dictionary> - { - { "Payload", new List { "FileSize", "Hash", "SourcePath" } }, - }; - Assert.Equal("", bundleExtensionPayloads[0].GetTestXml(ignored)); - } - } - - [Fact] - public void PopulatesManifestWithBundleExtensionSearches() - { - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BundleExtension", "BundleExtensionSearches.wxs"), - Path.Combine(folder, "BundleExtension", "BundleWithSearches.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); - Assert.Equal(1, bundleExtensions.Count); - Assert.Equal("", bundleExtensions[0].GetTestXml()); - - var extensionSearches = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:ExtensionSearch"); - Assert.Equal(2, extensionSearches.Count); - Assert.Equal("", extensionSearches[0].GetTestXml()); - Assert.Equal("", extensionSearches[1].GetTestXml()); - - var bundleExtensionDatas = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']"); - Assert.Equal(1, bundleExtensionDatas.Count); - Assert.Equal("" + - "" + - "" + - "", bundleExtensionDatas[0].GetTestXml()); - - var exampleSearches = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']/be:ExampleSearch"); - Assert.Equal(2, exampleSearches.Count); - } - } - - [Fact] - public void PopulatesManifestWithExePackages() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, ".Data"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); - var ignoreAttributesByElementName = new Dictionary> - { - { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, - }; - Assert.Equal(2, exePackageElements.Count); - Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); - Assert.Equal("", exePackageElements[1].GetTestXml(ignoreAttributesByElementName)); - } - } - - [Fact] - public void PopulatesManifestWithSetVariables() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SetVariable", "Simple.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var setVariables = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:SetVariable"); - Assert.Equal(6, setVariables.Count); - Assert.Equal("", setVariables[0].GetTestXml()); - Assert.Equal("", setVariables[1].GetTestXml()); - Assert.Equal("", setVariables[2].GetTestXml()); - Assert.Equal("", setVariables[3].GetTestXml()); - Assert.Equal("", setVariables[4].GetTestXml()); - Assert.Equal("", setVariables[5].GetTestXml()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs deleted file mode 100644 index ad62dea6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs +++ /dev/null @@ -1,107 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class CabFixture - { - [Fact] - public void CabinetFilesSequencedCorrectly() - { - var folder = TestData.Get(@"TestData\MultiFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - Assert.True(File.Exists(cabPath)); - - var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); - var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); - - Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray()); - Assert.Equal(new[] { "Notepad.exe", "test.txt" }, fileRows.Select(f => f.Name).ToArray()); - - var files = Query.GetCabinetFiles(cabPath); - Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); - } - } - - [Fact(Skip = "Sequence number of file from merge module is 0 but should be 1.")] - public void CabinetFilesSequencedCorrectlyUsingMergeModule() - { - var folder = TestData.Get(@"TestData\SimpleMerge"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, ".data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - Assert.True(File.Exists(cabPath)); - - var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); - var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); - - Assert.Equal(new[] { 1 }, fileRows.Select(f => f.Sequence).ToArray()); - Assert.Equal(new[] { "test.txt" }, fileRows.Select(f => f.Name).ToArray()); - - var files = Query.GetCabinetFiles(cabPath); - Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); - } - } - - private class FileRow - { - public FileRow(string row) - { - row = row.Substring("File:".Length); - - var split = row.Split('\t'); - this.Id = split[0]; - this.Name = split[2]; - this.Sequence = Convert.ToInt32(split[7]); - } - - public string Id { get; set; } - - public string Name { get; set; } - - public int Sequence { get; set; } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs deleted file mode 100644 index d24ba08c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs +++ /dev/null @@ -1,45 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class ComponentFixture - { - [Fact] - public void CanDetectDuplicateComponentGuids() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Component", "GuidCollision.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - var errors = result.Messages.Where(m => m.Level == MessageLevel.Error); - Array.Equals(new[] - { - 369, - 369 - }, errors.Select(e => e.Id).ToArray()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs deleted file mode 100644 index dd381dfe..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs +++ /dev/null @@ -1,385 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Xml; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.Burn; - using WixToolset.Core.TestPackage; - using Xunit; - - public class ContainerFixture - { - [Fact(Skip = "Test demonstrates failure")] - public void CanBuildWithCustomAttachedContainer() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder, buildToSubfolder: true); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Container", "HarvestIntoAttachedContainer.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); - Assert.Equal(4, payloads.Count); - var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; - Assert.Equal(@"", payloads[0].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[1].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[2].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[3].GetTestXml(ignoreAttributes)); - } - } - - [Fact] - public void HarvestedPayloadsArePutInCorrectContainer() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); - Assert.Equal(4, payloads.Count); - var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; - Assert.Equal(@"", payloads[0].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[1].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[2].GetTestXml(ignoreAttributes)); - Assert.Equal(@"", payloads[3].GetTestXml(ignoreAttributes)); - } - } - - [Fact] - public void HarvestedPayloadsArePutInCorrectPackage() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributes = new Dictionary> - { - { "MsiPackage", new List { "CacheId", "InstallSize", "Size", "ProductCode" } }, - { "Provides", new List { "Key" } }, - }; - var msiPackages = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributes)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "", - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "", - }, msiPackages); - } - } - - [Fact] - public void LayoutPayloadIsPutInContainer() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - WixAssert.CompareLineByLine(new string[] - { - "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; - var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributes)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - }, payloads); - } - } - - [Fact] - public void MultipleAttachedContainersAreNotCurrentlySupported() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Container", "MultipleAttachedContainers.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - Assert.Equal((int)BurnBackendErrors.Ids.MultipleAttachedContainersUnsupported, result.ExitCode); - } - } - - [Fact] - public void PayloadIsNotPutInMultipleContainers() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "Container", "PayloadInMultipleContainers.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - WixAssert.CompareLineByLine(new string[] - { - "The Payload 'SharedPayload' can't be added to Container 'FirstX64' because it was already added to Container 'FirstX86'.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; - var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributes)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - }, payloads); - } - } - - [Fact] - public void PopulatesBAManifestWithLayoutOnlyPayloads() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - this.BuildMsis(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath - }); - - WixAssert.CompareLineByLine(new string[] - { - "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributesByElementName = new Dictionary> - { - { "WixPayloadProperties", new List { "Size" } }, - }; - var payloads = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributesByElementName)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - "", - "", - "", - }, payloads); - } - } - - private void BuildMsis(string folder, string intermediateFolder, string binFolder, bool buildToSubfolder = false) - { - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX86" : ".", "FirstX86.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX64" : ".", "FirstX64.msi"), - }); - - result.AssertSuccess(); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs deleted file mode 100644 index c6fa602b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class CopyFileFixture - { - [Fact] - public void CanBuildCopyFile() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CopyFile", "CopyFile.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - var copyFileSymbol = section.Symbols.OfType().Single(); - Assert.Equal("MoveText", copyFileSymbol.Id.Id); - Assert.True(copyFileSymbol.Delete); - Assert.Equal("OtherFolder", copyFileSymbol.DestFolder); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs deleted file mode 100644 index 636b86a6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs +++ /dev/null @@ -1,169 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class CustomActionFixture - { - [Fact] - public void CanDetectCustomActionCycle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomAction", "CustomActionCycle.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - Assert.Equal(176, result.ExitCode); - Assert.Equal("The InstallExecuteSequence table contains an action 'Action1' that is scheduled to come before or after action 'Action3', which is also scheduled to come before or after action 'Action1'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", result.Messages[0].ToString()); - } - } - - [Fact] - public void CanDetectCustomActionCycleWithTail() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomAction", "CustomActionCycleWithTail.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - Assert.Equal(176, result.ExitCode); - Assert.Equal("The InstallExecuteSequence table contains an action 'Action2' that is scheduled to come before or after action 'Action4', which is also scheduled to come before or after action 'Action2'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", result.Messages[0].ToString()); - } - } - - [Fact] - public void PopulatesCustomActionTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomAction", "UnscheduledCustomAction.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { - "ActionText", - "AdminExecuteSequence", - "AdminUISequence", - "AdvtExecuteSequence", - "Binary", - "CustomAction", - "InstallExecuteSequence", - "InstallUISequence", - "Property", - }).Where(x => !x.StartsWith("Property:") || x.StartsWith("Property:MsiHiddenProperties\t")).ToArray(); - Assert.Equal(new[] - { - "ActionText:CustomAction2\tProgess2Text\t", - "AdminExecuteSequence:CostFinalize\t\t1000", - "AdminExecuteSequence:CostInitialize\t\t800", - "AdminExecuteSequence:CustomAction2\t\t801", - "AdminExecuteSequence:FileCost\t\t900", - "AdminExecuteSequence:InstallAdminPackage\t\t3900", - "AdminExecuteSequence:InstallFiles\t\t4000", - "AdminExecuteSequence:InstallFinalize\t\t6600", - "AdminExecuteSequence:InstallInitialize\t\t1500", - "AdminExecuteSequence:InstallValidate\t\t1400", - "AdminUISequence:CostFinalize\t\t1000", - "AdminUISequence:CostInitialize\t\t800", - "AdminUISequence:CustomAction2\t\t801", - "AdminUISequence:ExecuteAction\t\t1300", - "AdminUISequence:FileCost\t\t900", - "AdvtExecuteSequence:CostFinalize\t\t1000", - "AdvtExecuteSequence:CostInitialize\t\t800", - "AdvtExecuteSequence:CustomAction2\t\t801", - "AdvtExecuteSequence:InstallFinalize\t\t6600", - "AdvtExecuteSequence:InstallInitialize\t\t1500", - "AdvtExecuteSequence:InstallValidate\t\t1400", - "AdvtExecuteSequence:PublishFeatures\t\t6300", - "AdvtExecuteSequence:PublishProduct\t\t6400", - "Binary:Binary1\t[Binary data]", - "CustomAction:CustomAction1\t1\tBinary1\tInvalidEntryPoint\t", - "CustomAction:CustomAction2\t51\tTestAdvtExecuteSequenceProperty\t1\t", - "CustomAction:CustomActionWithHiddenTarget\t9217\tBinary1\tInvalidEntryPoint\t", - "CustomAction:DiscardOptimismAllBeingsWhoProceed\t19\t\tAbandon hope all ye who enter here.\t", - "InstallExecuteSequence:CostFinalize\t\t1000", - "InstallExecuteSequence:CostInitialize\t\t800", - "InstallExecuteSequence:CreateFolders\t\t3700", - "InstallExecuteSequence:CustomAction2\t\t801", - "InstallExecuteSequence:FileCost\t\t900", - "InstallExecuteSequence:FindRelatedProducts\t\t25", - "InstallExecuteSequence:InstallFiles\t\t4000", - "InstallExecuteSequence:InstallFinalize\t\t6600", - "InstallExecuteSequence:InstallInitialize\t\t1500", - "InstallExecuteSequence:InstallValidate\t\t1400", - "InstallExecuteSequence:LaunchConditions\t\t100", - "InstallExecuteSequence:MigrateFeatureStates\t\t1200", - "InstallExecuteSequence:ProcessComponents\t\t1600", - "InstallExecuteSequence:PublishFeatures\t\t6300", - "InstallExecuteSequence:PublishProduct\t\t6400", - "InstallExecuteSequence:RegisterProduct\t\t6100", - "InstallExecuteSequence:RegisterUser\t\t6000", - "InstallExecuteSequence:RemoveExistingProducts\t\t1401", - "InstallExecuteSequence:RemoveFiles\t\t3500", - "InstallExecuteSequence:RemoveFolders\t\t3600", - "InstallExecuteSequence:UnpublishFeatures\t\t1800", - "InstallExecuteSequence:ValidateProductID\t\t700", - "InstallUISequence:CostFinalize\t\t1000", - "InstallUISequence:CostInitialize\t\t800", - "InstallUISequence:CustomAction2\t\t801", - "InstallUISequence:ExecuteAction\t\t1300", - "InstallUISequence:FileCost\t\t900", - "InstallUISequence:FindRelatedProducts\t\t25", - "InstallUISequence:LaunchConditions\t\t100", - "InstallUISequence:MigrateFeatureStates\t\t1200", - "InstallUISequence:ValidateProductID\t\t700", - "Property:MsiHiddenProperties\tCustomActionWithHiddenTarget", - }, results); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs deleted file mode 100644 index ee93b03a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs +++ /dev/null @@ -1,234 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Xml.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class CustomTableFixture - { - [Fact] - public void PopulatesCustomTable1() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTable1" }); - Assert.Equal(new[] - { - "CustomTable1:Row1\ttest.txt", - "CustomTable1:Row2\ttest.txt", - }, results); - } - } - - [Fact] - public void PopulatesCustomTableWithLocalization() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" }); - Assert.Equal(new[] - { - "CustomTableLocalized:Row1\tThis is row one", - "CustomTableLocalized:Row2\tThis is row two", - }, results); - } - } - - [Fact] - public void PopulatesCustomTableWithFilePath() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "CustomTable", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); - Assert.Equal(new[] - { - "CustomTableWithFile:Row1\t[Binary data]", - "CustomTableWithFile:Row2\t[Binary data]", - }, results); - } - } - - [Fact] - public void PopulatesCustomTableWithFilePathSerialized() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), - "-bindpath", Path.Combine(folder, "CustomTable", "data"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-lib", wixlibPath, - "-bindpath", Path.Combine(folder, "CustomTable", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); - Assert.Equal(new[] - { - "CustomTableWithFile:Row1\t[Binary data]", - "CustomTableWithFile:Row2\t[Binary data]", - }, results); - } - } - - [Fact] - public void UnrealCustomTableIsNotPresentInMsi() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "CustomTable", "CustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" }); - Assert.Empty(results); - } - } - - [Fact] - public void CanCompileAndDecompile() - { - var folder = TestData.Get(@"TestData"); - var expectedFile = Path.Combine(folder, "CustomTable", "CustomTable-Expected.wxs"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - var decompiledWxsPath = Path.Combine(baseFolder, @"decompiled.wxs"); - - var result = WixRunner.Execute(new[] - { - "build", - "-d", "ProductCode=83f9c623-26fe-42ab-951e-170022117f54", - Path.Combine(folder, "CustomTable", "CustomTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - Assert.True(File.Exists(msiPath)); - - result = WixRunner.Execute(new[] - { - "decompile", msiPath, - "-sw1060", - "-intermediateFolder", intermediateFolder, - "-o", decompiledWxsPath - }); - - result.AssertSuccess(); - - WixAssert.CompareXml(expectedFile, decompiledWxsPath); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs deleted file mode 100644 index ab04da15..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs +++ /dev/null @@ -1,86 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class DecompileFixture - { - private static void DecompileAndCompare(string sourceFolder, string msiName, string expectedWxsName) - { - var folder = TestData.Get(sourceFolder); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); - - var result = WixRunner.Execute(new[] - { - "decompile", - Path.Combine(folder, msiName), - "-intermediateFolder", intermediateFolder, - "-o", outputPath - }); - - result.AssertSuccess(); - - WixAssert.CompareXml(Path.Combine(folder, expectedWxsName), outputPath); - } - } - - [Fact] - public void CanDecompileSingleFileCompressed() - { - DecompileAndCompare(@"TestData\DecompileSingleFileCompressed", "example.msi", "Expected.wxs"); - } - - [Fact] - public void CanDecompile64BitSingleFileCompressed() - { - DecompileAndCompare(@"TestData\DecompileSingleFileCompressed64", "example.msi", "Expected.wxs"); - } - - [Fact] - public void CanDecompileNestedDirSearchUnderRegSearch() - { - DecompileAndCompare(@"TestData\AppSearch", "NestedDirSearchUnderRegSearch.msi", "DecompiledNestedDirSearchUnderRegSearch.wxs"); - } - - [Fact] - public void CanDecompileOldClassTableDefinition() - { - // The input MSI was not created using standard methods, it is an example of a real world database that needs to be decompiled. - // The Class/@Feature_ column has length of 32, the File/@Attributes has length of 2, - // and numerous foreign key relationships are missing. - DecompileAndCompare(@"TestData\Class", "OldClassTableDef.msi", "DecompiledOldClassTableDef.wxs"); - } - - [Fact] - public void CanDecompileSequenceTables() - { - DecompileAndCompare(@"TestData\SequenceTables", "SequenceTables.msi", "DecompiledSequenceTables.wxs"); - } - - [Fact] - public void CanDecompileShortcuts() - { - DecompileAndCompare(@"TestData\Shortcut", "shortcuts.msi", "DecompiledShortcuts.wxs"); - } - - [Fact] - public void CanDecompileNullComponent() - { - DecompileAndCompare(@"TestData\DecompileNullComponent", "example.msi", "Expected.wxs"); - } - - [Fact] - public void CanDecompileMergeModuleWithTargetDirComponent() - { - DecompileAndCompare(@"TestData\DecompileTargetDirMergeModule", "MergeModule1.msm", "Expected.wxs"); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs deleted file mode 100644 index 840b411e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs +++ /dev/null @@ -1,180 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Xml; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class DependencyExtensionFixture - { - [Fact] - public void CanBuildBundleUsingExePackageWithProvides() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Dependency", "ExePackageProvidesBundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, ".Data"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage/burn:Provides") - .Cast() - .Select(e => e.GetTestXml()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - }, provides); - } - } - - [Fact] - public void CanBuildBundleUsingMsiWithProvides() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "UsingProvides", "Package.wxs"), - Path.Combine(folder, "UsingProvides", "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "UsingProvides", "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "UsingProvides"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "UsingProvides.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Dependency", "UsingProvidesBundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage/burn:Provides") - .Cast() - .Select(e => e.GetTestXml()) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - "", - }, provides); - } - } - - [Fact] - public void CanBuildBundleWithCustomProviderKey() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var bundlePath = Path.Combine(binFolder, "test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Dependency", "CustomProviderKeyBundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributesByElementName = new Dictionary> - { - { "Registration", new List { "Id" } }, - }; - var registration = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributesByElementName)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - }, registration); - } - } - - [Fact] - public void CanBuildPackageUsingProvides() - { - var folder = TestData.Get(@"TestData\UsingProvides"); - var build = new Builder(folder, null, new[] { folder }); - - var results = build.BuildAndQuery(Build, "WixDependencyProvider"); - Assert.Equal(new[] - { - "WixDependencyProvider:dep74OfIcniaqxA7EprRGBw4Oyy3r8\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tUsingProvides\t\t\t", - }, results); - } - - private static void Build(string[] args) - { - var result = WixRunner.Execute(args) - .AssertSuccess(); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs deleted file mode 100644 index a61bdff3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs +++ /dev/null @@ -1,271 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using Xunit; - - public class DirectoryFixture - { - [Fact] - public void CanGet32bitProgramFiles6432Folder() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Directory", "Empty.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - Assert.Equal(new[] - { - "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", - "ProgramFiles6432Folder:ProgramFilesFolder:.", - "ProgramFilesFolder:TARGETDIR:PFiles", - "TARGETDIR::SourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); - } - } - - [Fact] - public void CanGet64bitProgramFiles6432Folder() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - "-arch", "x64", - Path.Combine(folder, "Directory", "Empty.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - Assert.Equal(new[] - { - "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", - "ProgramFiles6432Folder:ProgramFiles64Folder:.", - "ProgramFiles64Folder:TARGETDIR:PFiles64", - "TARGETDIR::SourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); - } - } - - [Fact] - public void CanGetDefaultName() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Directory", "DefaultName.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - WixAssert.CompareLineByLine(new[] - { - "BinFolder\tCompanyFolder\t.", - "CompanyFolder\tProgramFilesFolder\tExample Corporation", - "ProgramFilesFolder\tTARGETDIR\tPFiles", - "TARGETDIR\t\tSourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); - - var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var directoryRows = data.Tables["Directory"].Rows; - WixAssert.CompareLineByLine(new[] - { - "BinFolder\tCompanyFolder\t.", - "CompanyFolder\tProgramFilesFolder\tu7-b4gch|Example Corporation", - "ProgramFilesFolder\tTARGETDIR\tPFiles", - "TARGETDIR\t\tSourceDir" - }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); - } - } - - [Fact] - public void CanGetDuplicateDir() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - "-arch", "x64", - Path.Combine(folder, "DuplicateDir", "DuplicateDir.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - Assert.Equal(new[] - { - "dZsSsu81KcG46xXTwc4mTSZO5Zx4:INSTALLFOLDER:dupe", - "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", - "ProgramFiles6432Folder:ProgramFiles64Folder:.", - "ProgramFiles64Folder:TARGETDIR:PFiles64", - "TARGETDIR::SourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); - } - } - - [Fact] - public void CanGetWithMultiNestedSubdirectory() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - "-arch", "x64", - Path.Combine(folder, "Directory", "Nested.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - Assert.Equal(new[] - { - "BinFolder:ProgramFilesFolder:Example Corporation\\Test Product\\bin", - "ProgramFilesFolder:TARGETDIR:PFiles", - "TARGETDIR::SourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); - - var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var directoryRows = data.Tables["Directory"].Rows; - Assert.Equal(new[] - { - "d4EceYatXTyy8HXPt5B6DT9Rj.wE:ProgramFilesFolder:u7-b4gch|Example Corporation", - "dSJ1pgiASlW7kJTu0wqsGBklJsS0:d4EceYatXTyy8HXPt5B6DT9Rj.wE:vjj-gxay|Test Product", - "BinFolder:dSJ1pgiASlW7kJTu0wqsGBklJsS0:bin", - "ProgramFilesFolder:TARGETDIR:PFiles", - "TARGETDIR::SourceDir" - }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).ToArray()); - } - } - - [Fact] - public void CanGetDuplicateTargetSourceName() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - "-arch", "x64", - Path.Combine(folder, "Directory", "DuplicateTargetSourceName.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().ToList(); - Assert.Equal(new[] - { - "BinFolder\tProgramFilesFolder\tbin", - "ProgramFilesFolder\tTARGETDIR\tPFiles", - "TARGETDIR\t\tSourceDir" - }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); - - var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var directoryRows = data.Tables["Directory"].Rows; - Assert.Equal(new[] - { - "BinFolder\tProgramFilesFolder\tbin", - "ProgramFilesFolder\tTARGETDIR\tPFiles", - "TARGETDIR\t\tSourceDir" - }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs deleted file mode 100644 index e2306dcd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs +++ /dev/null @@ -1,52 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class ExePackageFixture - { - [Fact] - public void ErrorWhenMissingDetectCondition() - { - var folder = TestData.Get(@"TestData", "ExePackage"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MissingDetectCondition.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(1153, result.ExitCode); - } - } - - [Fact] - public void ErrorWhenRequireDetectCondition() - { - var folder = TestData.Get(@"TestData", "ExePackage"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "RequireDetectCondition.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(401, result.ExitCode); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs deleted file mode 100644 index 089658e6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs +++ /dev/null @@ -1,153 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class ExtensionFixture - { - [Fact] - public void CanBuildAndQuery() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - var build = new Builder(folder, typeof(ExampleExtensionFactory), new[] { Path.Combine(folder, "data") }); - - var results = build.BuildAndQuery(Build, "Wix4Example"); - Assert.Equal(new[] - { - "Wix4Example:Foo\tBar" - }, results); - } - - [Fact] - public void CanBuildWithExampleExtension() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\example.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - - var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single(); - Assert.Equal("Foo", example.Id?.Id); - Assert.Equal("Bar", example[0].AsString()); - } - } - - [Fact] - public void CanParseCommandLineWithExtension() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-example", "test", - "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); - var section = intermediate.Sections.Single(); - - var property = section.Symbols.OfType().Where(p => p.Id.Id == "ExampleProperty").Single(); - Assert.Equal("ExampleProperty", property.Id.Id); - Assert.Equal("test", property.Value); - } - } - - [Fact] - public void CannotBuildWithMissingExtension() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var exception = Assert.Throws(() => - WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-ext", "ExampleExtension.DoesNotExist" - })); - - Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist' could not be found. Checked paths: ", exception.Message); - } - } - - [Fact] - public void CannotBuildWithMissingVersionedExtension() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var exception = Assert.Throws(() => - WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-ext", "ExampleExtension.DoesNotExist/1.0.0" - })); - - Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist/1.0.0' could not be found. Checked paths: ", exception.Message); - } - } - - private static void Build(string[] args) - { - var result = WixRunner.Execute(args) - .AssertSuccess(); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs b/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs deleted file mode 100644 index db9708a7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs +++ /dev/null @@ -1,174 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using Xunit; - - public class LanguageFixture - { - [Fact] - public void CanBuildWithDefaultProductLanguage() - { - var folder = TestData.Get(@"TestData", "Language"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var directorySymbols = section.Symbols.OfType(); - WixAssert.CompareLineByLine(new[] - { - "INSTALLFOLDER:Example Corporation\\MsiPackage", - "ProgramFilesFolder:PFiles", - "TARGETDIR:SourceDir" - }, directorySymbols.OrderBy(s => s.Id.Id).Select(s => s.Id.Id + ":" + s.Name).ToArray()); - - var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); - Assert.Equal("0", propertySymbol.Value); - - var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); - Assert.Equal("Intel;0", summaryPlatform.Value); - - var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); - Assert.Equal("1252", summaryCodepage.Value); - - var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var directoryRows = data.Tables["Directory"].Rows; - WixAssert.CompareLineByLine(new[] - { - "d4EceYatXTyy8HXPt5B6DT9Rj.wE:u7-b4gch|Example Corporation", - "INSTALLFOLDER:oekcr5lq|MsiPackage", - "ProgramFilesFolder:PFiles", - "TARGETDIR:SourceDir" - }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(2)).ToArray()); - } - } - - [Fact] - public void CanBuildEnuWxl() - { - var folder = TestData.Get(@"TestData", "Language"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); - Assert.Equal("1033", propertySymbol.Value); - - var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); - Assert.Equal("Intel;1033", summaryPlatform.Value); - - var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); - Assert.Equal("1252", summaryCodepage.Value); - } - } - - [Fact] - public void CanBuildJpnWxl() - { - var folder = TestData.Get(@"TestData", "Language"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.ja-jp.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); - Assert.Equal("1041", propertySymbol.Value); - - var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); - Assert.Equal("Intel;1041", summaryPlatform.Value); - - var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); - Assert.Equal("932", summaryCodepage.Value); - } - } - - [Fact] - public void CanBuildJpnWxlWithEnuSummaryInfo() - { - var folder = TestData.Get(@"TestData", "Language"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "PackageWithEnSummaryInfo.ja-jp.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); - Assert.Equal("1041", propertySymbol.Value); - - var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); - Assert.Equal("Intel;1041", summaryPlatform.Value); - - var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); - Assert.Equal("1252", summaryCodepage.Value); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs b/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs deleted file mode 100644 index cfe4d3f1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs +++ /dev/null @@ -1,174 +0,0 @@ - -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - using Xunit; - - public class LinkerFixture - { - [Fact] - public void MustCompileBeforeLinking() - { - var intermediate1 = new Intermediate("TestIntermediate1", new[] { new IntermediateSection("test1", SectionType.Product) }, null); - var intermediate2 = new Intermediate("TestIntermediate2", new[] { new IntermediateSection("test2", SectionType.Fragment) }, null); - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - - var listener = new TestMessageListener(); - var messaging = serviceProvider.GetService(); - messaging.SetListener(listener); - - var creator = serviceProvider.GetService(); - var context = serviceProvider.GetService(); - context.Extensions = Array.Empty(); - context.ExtensionData = Array.Empty(); - context.Intermediates = new[] { intermediate1, intermediate2 }; - context.SymbolDefinitionCreator = creator; - - var linker = serviceProvider.GetService(); - linker.Link(context); - - Assert.Equal((int)ErrorMessages.Ids.IntermediatesMustBeCompiled, messaging.LastErrorNumber); - Assert.Single(listener.Messages); - Assert.EndsWith("TestIntermediate1, TestIntermediate2", listener.Messages[0].ToString()); - } - - [Fact] - public void CanBuildWithOverridableActions() - { - var folder = TestData.Get(@"TestData\OverridableActions"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1008", // this is expected for this test - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var actions = section.Symbols.OfType().Where(wat => wat.Action.StartsWith("Set")).ToList(); - Assert.Equal(2, actions.Count); - //Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileSymbolFields.Source].AsPath().Path); - //Assert.Equal(@"test.txt", wixFile[WixFileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void MissingEntrySectionDetectedProduct() - { - var folder = TestData.Get(@"TestData\OverridableActions"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - try - { - WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - } - catch (WixException we) - { - Assert.Equal("Could not find entry section in provided list of intermediates. Expected section of type 'Product'.", we.Message); - return; - } - - Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); - } - } - - [Fact] - public void MissingEntrySectionDetectedWixipl() - { - var folder = TestData.Get(@"TestData\OverridableActions"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - try - { - WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.wixipl") - }); - } - catch (WixException we) - { - Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message); - return; - } - - Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); - } - } - - [Fact] - public void MissingEntrySectionDetectedUnknown() - { - var folder = TestData.Get(@"TestData\OverridableActions"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - try - { - WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.bob") - }); - } - catch (WixException we) - { - Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message); - return; - } - - Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs deleted file mode 100644 index de18e30c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class MediaFixture - { - [Fact] - public void CanBuildMultiMedia() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Media", "MultiMedia.wxs"), - "-bindpath", Path.Combine(folder, "Media", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var mediaSymbols = section.Symbols.OfType().OrderBy(m => m.DiskId).ToList(); - var fileSymbols = section.Symbols.OfType().OrderBy(f => f.Sequence).ToList(); - Assert.Equal(1, mediaSymbols[0].DiskId); - Assert.Equal(2, mediaSymbols[0].LastSequence); - Assert.Equal(2, mediaSymbols[1].DiskId); - Assert.Equal(4, mediaSymbols[1].LastSequence); - Assert.Equal(new[] - { - "a1.txt", - "a2.txt", - "b1.txt", - "b2.txt", - }, fileSymbols.Select(f => f.Name).ToArray()); - Assert.Equal(new[] - { - 1, - 2, - 3, - 4, - }, fileSymbols.Select(f => f.Sequence).ToArray()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs deleted file mode 100644 index 17e91692..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs +++ /dev/null @@ -1,113 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using Xunit; - - public class ModuleFixture - { - [Fact] - public void CanBuildSimpleModule() - { - var folder = TestData.Get(@"TestData\SimpleModule"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Module.wxs"), - "-loc", Path.Combine(folder, "Module.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msm") - }); - - result.AssertSuccess(); - - var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); - Assert.True(File.Exists(msmPath)); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var dirSymbols = section.Symbols.OfType().OrderBy(d => d.Id.Id).ToList(); - WixAssert.CompareLineByLine(new[] - { - "MergeRedirectFolder\tTARGETDIR\t.", - "NotTheMergeRedirectFolder\tTARGETDIR\t.", - "TARGETDIR\t\tSourceDir" - }, dirSymbols.Select(d => String.Join("\t", d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); - - var fileSymbols = section.Symbols.OfType().OrderBy(d => d.Id.Id).ToList(); - WixAssert.CompareLineByLine(new[] - { - $"File1\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", - $"File2\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", - }, fileSymbols.Select(fileSymbol => String.Join("\t", fileSymbol.Id.Id, fileSymbol[FileSymbolFields.Source].AsPath().Path, fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path)).ToArray()); - - var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var fileRows = data.Tables["File"].Rows; - Assert.Equal(new[] - { - "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", - "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", - }, fileRows.Select(r => r.FieldAsString(0)).ToArray()); - - var cabPath = Path.Combine(intermediateFolder, "msm-test.cab"); - Query.ExtractStream(msmPath, "MergeModule.CABinet", cabPath); - var files = Query.GetCabinetFiles(cabPath); - Assert.Equal(new[] - { - "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", - "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", - }, files.Select(f => Path.Combine(f.Path, f.Name)).ToArray()); - } - } - - [Fact] - public void CanSuppressModularization() - { - var folder = TestData.Get(@"TestData\SuppressModularization"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Module.wxs"), - "-loc", Path.Combine(folder, "Module.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-sw1079", - "-sw1086", - "-o", Path.Combine(intermediateFolder, @"bin\test.msm") - }); - - result.AssertSuccess(); - - var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); - - var rows = Query.QueryDatabase(msmPath, new[] { "CustomAction", "Property" }); - WixAssert.CompareLineByLine(new[] - { - "CustomAction:Test\t11265\tFakeCA.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tTestEntry\t", - "Property:MsiHiddenProperties\tTest" - }, rows); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs deleted file mode 100644 index 3bdfa0ef..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/MsiFixture.cs +++ /dev/null @@ -1,838 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using Xunit; - - public class MsiFixture - { - [Fact] - public void CanBuildSingleFile() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - - Assert.False(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Compiled)); - Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Linked)); - Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Resolved)); - Assert.True(intermediate.HasLevel(WixToolset.Data.WindowsInstaller.IntermediateLevels.FullyBound)); - - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().First(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CanBuildSingleFileCompressed() - { - var folder = TestData.Get(@"TestData\SingleFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CanBuildSingleFileCompressedWithMediaTemplate() - { - var folder = TestData.Get(@"TestData\SingleFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression() - { - var folder = TestData.Get(@"TestData\SingleFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel=low", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildMultipleFilesCompressed() - { - var folder = TestData.Get(@"TestData\MultiFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1079", // TODO: why does this test need to create a second cab which is empty? - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example1.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example2.cab"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanFailBuildMissingFile() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "does-not-exist"), - "-bindpath", Path.Combine(folder, "also-does-not-exist"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }, out var messages); - Assert.Equal(103, result); - - var error = messages.Single(m => m.Level == MessageLevel.Error); - var errorMessage = error.ToString(); - var checkedPaths = errorMessage.Substring(errorMessage.IndexOf(':') + 1).Split(new[] { ',' }).Select(s => s.Trim()).ToArray(); - Assert.Equal(new[] - { - "test.txt", - Path.Combine(folder, "does-not-exist", "test.txt"), - Path.Combine(folder, "also-does-not-exist", "test.txt"), - }, checkedPaths); - } - } - - [Fact] - public void CanBuildWithErrorTable() - { - var folder = TestData.Get(@"TestData\ErrorsInUI"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var errors = section.Symbols.OfType().ToDictionary(t => t.Id.Id); - Assert.Equal("Category 55 Emergency Doomsday Crisis", errors["1234"].Message.Trim()); - Assert.Equal(" ", errors["5678"].Message); - - var customAction1 = section.Symbols.OfType().Where(t => t.Id.Id == "CanWeReferenceAnError_YesWeCan").Single(); - Assert.Equal("1234", customAction1.Target); - - var customAction2 = section.Symbols.OfType().Where(t => t.Id.Id == "TextErrorsWorkOKToo").Single(); - Assert.Equal("If you see this, something went wrong.", customAction2.Target); - } - } - - [Fact] - public void CanLoadPdbGeneratedByBuild() - { - var folder = TestData.Get(@"TestData\MultiFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); - - var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); - Assert.True(File.Exists(pdbPath)); - - var output = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: true); - Assert.NotNull(output); - } - } - - [Fact] - public void CanLoadPdbGeneratedByBuildViaWixOutput() - { - var folder = TestData.Get(@"TestData\MultiFileCompressed"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-d", "MediaTemplateCompressionLevel", - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); - - var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); - Assert.True(File.Exists(pdbPath)); - - var wixOutput = WixOutput.Read(pdbPath); - var output = WindowsInstallerData.Load(wixOutput, suppressVersionCheck: true); - Assert.NotNull(output); - } - } - - [Fact] - public void CanBuildManualUpgrade() - { - var folder = TestData.Get(@"TestData\ManualUpgrade"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }, out var messages); - - Assert.Equal(0, result); - - var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); - Assert.True(File.Exists(pdbPath)); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(pdbPath); - var section = intermediate.Sections.Single(); - - var upgradeSymbol = section.Symbols.OfType().Single(); - Assert.False(upgradeSymbol.ExcludeLanguages); - Assert.True(upgradeSymbol.IgnoreRemoveFailures); - Assert.False(upgradeSymbol.VersionMaxInclusive); - Assert.True(upgradeSymbol.VersionMinInclusive); - Assert.Equal("13.0.0", upgradeSymbol.VersionMax); - Assert.Equal("12.0.0", upgradeSymbol.VersionMin); - Assert.False(upgradeSymbol.OnlyDetect); - Assert.Equal("BLAHBLAHBLAH", upgradeSymbol.ActionProperty); - - var pdb = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: false); - var secureProperties = pdb.Tables["Property"].Rows.Where(row => row.GetKey() == "SecureCustomProperties").Single(); - Assert.Contains("BLAHBLAHBLAH", secureProperties.FieldAsString(1)); - } - } - - [Fact] - public void CanBuildWixipl() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.wixipl") - }, out var messages); - - Assert.Equal(0, result); - - var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin")); - - Assert.Equal(new[]{ - "test.wixipl" - }, builtFiles.Select(Path.GetFileName).ToArray()); - } - } - - [Fact] - public void CanBuildWixlib() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.wixlib") - }, out var messages); - - Assert.Equal(0, result); - - var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin")); - - Assert.Equal(new[]{ - "test.wixlib" - }, builtFiles.Select(Path.GetFileName).ToArray()); - } - } - - [Fact] - public void CanBuildBinaryWixlib() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute( - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-bindfiles", - "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); - - result.AssertSuccess(); - - using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) - { - Assert.NotNull(wixout.GetDataStream("wix-ir.json")); - - var text = wixout.GetData("wix-ir/test.txt"); - Assert.Equal("This is test.txt.", text); - } - } - } - - [Fact] - public void CanBuildBinaryWixlibWithCollidingFilenames() - { - var folder = TestData.Get(@"TestData\SameFileFolders"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute( - "build", - Path.Combine(folder, "TestComponents.wxs"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-bindfiles", - "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); - - result.AssertSuccess(); - - using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) - { - Assert.NotNull(wixout.GetDataStream("wix-ir.json")); - - var text = wixout.GetData("wix-ir/test.txt"); - Assert.Equal(@"This is a\test.txt.", text); - - var text2 = wixout.GetData("wix-ir/test.txt-1"); - Assert.Equal(@"This is b\test.txt.", text2); - - var text3 = wixout.GetData("wix-ir/test.txt-2"); - Assert.Equal(@"This is c\test.txt.", text3); - } - } - } - - [Fact] - public void CanBuildWithIncludePath() - { - var folder = TestData.Get(@"TestData\IncludePath"); - var bindpath = Path.Combine(folder, "data"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute( - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", bindpath, - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi"), - "-i", bindpath); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CanBuildWithAssembly() - { - var folder = TestData.Get(@"TestData\Assembly"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\AssemblyMsiPackage\candle.exe"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\candle.exe"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"candle.exe", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - - var msiAssemblyNameSymbols = section.Symbols.OfType(); - Assert.Equal(new[] - { - "culture", - "fileVersion", - "name", - "processorArchitecture", - "publicKeyToken", - "version" - }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Name).ToArray()); - - Assert.Equal(new[] - { - "neutral", - "3.11.11810.0", - "candle", - "x86", - "256B3414DFA97718", - "3.0.0.0" - }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Value).ToArray()); - } - } - - [Fact] - public void CanBuild64bit() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-arch", "x64", - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var platformSummary = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); - Assert.Equal("x64;1033", platformSummary.Value); - } - } - - [Fact] - public void CanBuildSharedComponent() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-arch", "x64", - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - // Only one component is shared. - var sharedComponentSymbols = section.Symbols.OfType(); - Assert.Equal(1, sharedComponentSymbols.Sum(t => t.Shared ? 1 : 0)); - - // And it is this one. - var sharedComponentSymbol = sharedComponentSymbols.Single(t => t.Id.Id == "Shared.dll"); - Assert.True(sharedComponentSymbol.Shared); - } - } - - [Fact] - public void CanBuildSetProperty() - { - var folder = TestData.Get(@"TestData\SetProperty"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var output = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"), false); - var caRows = output.Tables["CustomAction"].Rows.Single(); - Assert.Equal("SetINSTALLLOCATION", caRows.FieldAsString(0)); - Assert.Equal("51", caRows.FieldAsString(1)); - Assert.Equal("INSTALLLOCATION", caRows.FieldAsString(2)); - Assert.Equal("[INSTALLFOLDER]", caRows.FieldAsString(3)); - } - } - - [Fact] - public void CanBuildVersionIndependentProgId() - { - var folder = TestData.Get(@"TestData\ProgId"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\Foo.exe"))); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var progids = section.Symbols.OfType().OrderBy(symbol => symbol.ProgId).ToList(); - Assert.Equal(new[] - { - "Foo.File.hol", - "Foo.File.hol.15" - }, progids.Select(p => p.ProgId).ToArray()); - - Assert.Equal(new[] - { - "Foo.File.hol.15", - null - }, progids.Select(p => p.ParentProgIdRef).ToArray()); - } - } - - [Fact] - public void CanBuildInstanceTransform() - { - var folder = TestData.Get(@"TestData\InstanceTransform"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var output = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"), false); - var substorage = output.SubStorages.Single(); - Assert.Equal("I1", substorage.Name); - - var data = substorage.Data; - Assert.Equal(new[] - { - "_SummaryInformation", - "Property", - "Upgrade" - }, data.Tables.Select(t => t.Name).ToArray()); - - Assert.Equal(new[] - { - "INSTANCEPROPERTY\tI1", - "ProductName\tMsiPackage (Instance 1)", - }, JoinRows(data.Tables["Property"])); - - Assert.Equal(new[] - { - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t0\t0", - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", - "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t0\t0" - }, JoinRows(data.Tables["Upgrade"])); - } - } - - [Fact(Skip = "Test demonstrates failure")] - public void FailsBuildAtLinkTimeForMissingEnsureTable() - { - var folder = TestData.Get(@"TestData"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "BadEnsureTable", "BadEnsureTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - Assert.Collection(result.Messages, - first => - { - Assert.Equal(MessageLevel.Error, first.Level); - Assert.Equal("The identifier 'WixCustomTable:TableDefinitionNotExposedByExtension' could not be found. Ensure you have typed the reference correctly and that all the necessary inputs are provided to the linker.", first.ToString()); - }); - - Assert.False(File.Exists(msiPath)); - } - } - - private static string[] JoinRows(Table table) - { - return table.Rows.Select(r => JoinFields(r.Fields)).ToArray(); - - string JoinFields(Field[] fields) - { - return String.Join('\t', fields.Select(f => f.ToString())); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs deleted file mode 100644 index 71edddc6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs +++ /dev/null @@ -1,1040 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Data.WindowsInstaller; - using Xunit; - - public class MsiQueryFixture - { - [Fact] - public void PopulatesAppIdTableWhenAdvertised() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppId", "Advertised.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppId" }); - WixAssert.CompareLineByLine(new[] - { - "AppId:{D6040299-B15C-4C94-AE26-0C9B60D14C35}\t\t\t\t\t\t", - }, results); - } - } - - [Fact] - public void PopulatesAppSearchTablesFromComponentSearch() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppSearch", "ComponentSearch.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "CompLocator" }); - WixAssert.CompareLineByLine(new[] - { - "AppSearch:SAMPLECOMPFOUND\tSampleCompSearch", - "CompLocator:SampleCompSearch\t{4D9A0D20-D0CC-40DE-B580-EAD38B985217}\t1", - }, results); - } - } - - [Fact] - public void PopulatesAppSearchTablesFromDirectorySearch() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppSearch", "DirectorySearch.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator" }); - WixAssert.CompareLineByLine(new[] - { - "AppSearch:SAMPLEDIRFOUND\tSampleDirSearch", - "DrLocator:SampleDirSearch\t\tC:\\SampleDir\t", - }, results); - } - } - - [Fact] - public void PopulatesAppSearchTablesFromFileSearch() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppSearch", "FileSearch.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator", "IniLocator" }); - WixAssert.CompareLineByLine(new[] - { - "AppSearch:SAMPLEFILEFOUND\tSampleFileSearch", - "DrLocator:SampleFileSearch\tSampleIniFileSearch\t\t", - "IniLocator:SampleFileSearch\tsample.fil\tMySection\tMyKey\t\t1", - }, results); - } - } - - [Fact] - public void PopulatesAppSearchTablesFromRegistrySearch() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppSearch", "RegistrySearch.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); - WixAssert.CompareLineByLine(new[] - { - "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", - "RegLocator:SampleRegSearch\t2\tSampleReg\t\t2", - }, results); - } - } - - [Fact] - public void PopulatesAppSearchTablesFromRegistrySearch64() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AppSearch", "RegistrySearch64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); - WixAssert.CompareLineByLine(new[] - { - "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", - "RegLocator:SampleRegSearch\t2\tSampleReg\t\t18", - }, results); - } - } - - [Fact] - public void PopulatesClassTablesWhenIconIndexIsZero() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Class", "IconIndex0.wxs"), - Path.Combine(folder, "Icon", "SampleIcon.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, ".Data"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Class" }); - WixAssert.CompareLineByLine(new[] - { - "Class:{3FAED4CC-C473-4B8A-BE8B-303871377A4A}\tLocalServer32\tClassComp\t\tFakeClass3FAE\t\t\tSampleIcon\t0\t\t\tProductFeature\t", - }, results); - } - } - - [Fact] - public void PopulatesClassTablesWhenProgIdIsNestedUnderAdvertisedClass() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ProgId", "NestedUnderClass.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Class", "ProgId", "Registry" }); - WixAssert.CompareLineByLine(new[] - { - "Class:{F12A6F69-117F-471F-AE73-F8E74218F498}\tLocalServer32\tProgIdComp\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tFakeClassF12A\t\t\t\t\t\t\tProductFeature\t", - "ProgId:73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\t\t{F12A6F69-117F-471F-AE73-F8E74218F498}\tFakeClassF12A\t\t", - "Registry:regUIIK326nDZpkWHuexeF58EikQvA\t0\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tNoOpen\tNoOpen73E7\tProgIdComp", - "Registry:regvrhMurMp98anbQJkpgA8yJCefdM\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\Version\t\t0.0.0.1\tProgIdComp", - "Registry:regY1F4E2lvu_Up6gV6c3jeN5ukn8s\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\LocalServer32\tThreadingModel\tApartment\tProgIdComp", - }, results); - } - } - - [Fact] - public void PopulatesControlTables() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "DialogsInInstallUISequence", "PackageComponents.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - - var results = Query.QueryDatabase(msiPath, new[] { "CheckBox", "Control", "ControlCondition", "InstallUISequence" }); - WixAssert.CompareLineByLine(new[] - { - "CheckBox:WIXUI_EXITDIALOGOPTIONALCHECKBOX\t1", - "Control:FirstDialog\tHeader\tText\t0\t13\t90\t13\t3\t\tFirstDialogHeader\tTitle\t", - "Control:FirstDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tFirstDialogTitle\tHeader\t", - "Control:SecondDialog\tOptionalCheckBox\tCheckBox\t0\t13\t100\t40\t2\tWIXUI_EXITDIALOGOPTIONALCHECKBOX\t[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]\tTitle\tOptional checkbox|Check this box for fun", - "Control:SecondDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tSecondDialogTitle\tOptionalCheckBox\t", - "ControlCondition:FirstDialog\tHeader\tDisable\tInstalled", - "ControlCondition:FirstDialog\tHeader\tHide\tInstalled", - "ControlCondition:SecondDialog\tOptionalCheckBox\tShow\tWIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT AND NOT Installed", - "InstallUISequence:CostFinalize\t\t1000", - "InstallUISequence:CostInitialize\t\t800", - "InstallUISequence:ExecuteAction\t\t1300", - "InstallUISequence:FileCost\t\t900", - "InstallUISequence:FindRelatedProducts\t\t25", - "InstallUISequence:FirstDialog\tInstalled AND PATCH\t1298", - "InstallUISequence:LaunchConditions\t\t100", - "InstallUISequence:MigrateFeatureStates\t\t1200", - "InstallUISequence:SecondDialog\tNOT Installed\t1299", - "InstallUISequence:ValidateProductID\t\t700", - }, results); - } - } - - [Fact] - public void PopulatesCreateFolderTableForNullKeypathComponents() - { - var folder = TestData.Get(@"TestData\Components"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "CreateFolder" }); - WixAssert.CompareLineByLine(new[] - { - "CreateFolder:INSTALLFOLDER\tNullKeypathComponent", - }, results); - } - } - - [Fact] - public void PopulatesDirectoryTableWithValidDefaultDir() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1031", // this is expected for this test - Path.Combine(folder, "DefaultDir", "DefaultDir.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Directory" }); - WixAssert.CompareLineByLine(new[] - { - "Directory:DUPLICATENAMEANDSHORTNAME\tINSTALLFOLDER\tduplicat", - "Directory:Folder1\tINSTALLFOLDER\tFolder.1", - "Directory:Folder12\tINSTALLFOLDER\tFolder.12", - "Directory:Folder123\tINSTALLFOLDER\tFolder.123", - "Directory:Folder1234\tINSTALLFOLDER\tyakwclwy|Folder.1234", - "Directory:INSTALLFOLDER\tProgramFiles6432Folder\t1egc1laj|MsiPackage", - "Directory:NAMEANDSHORTNAME\tINSTALLFOLDER\tSHORTNAM|NameAndShortName", - "Directory:NAMEANDSHORTSOURCENAME\tINSTALLFOLDER\tNAMEASSN|NameAndShortSourceName", - "Directory:NAMEWITHSHORTVALUE\tINSTALLFOLDER\tSHORTVAL", - "Directory:ProgramFiles6432Folder\tProgramFilesFolder\t.", - "Directory:ProgramFilesFolder\tTARGETDIR\tPFiles", - "Directory:SHORTNAMEANDLONGSOURCENAME\tINSTALLFOLDER\tSHNALSNM:6ukthv5q|ShortNameAndLongSourceName", - "Directory:SHORTNAMEONLY\tINSTALLFOLDER\tSHORTONL", - "Directory:SOURCENAME\tINSTALLFOLDER\ts2s5bq-i|NameAndSourceName:dhnqygng|SourceNameWithName", - "Directory:SOURCENAMESONLY\tINSTALLFOLDER\t.:SRCNAMON|SourceNameOnly", - "Directory:SOURCENAMEWITHSHORTVALUE\tINSTALLFOLDER\t.:SRTSRCVL", - "Directory:TARGETDIR\t\tSourceDir", - }, results); - } - } - - [Fact] - public void PopulatesEnvironmentTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Environment", "Environment.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Environment" }); - WixAssert.CompareLineByLine(new[] - { - "Environment:PATH\t=-*PATH\t[INSTALLFOLDER]; ;[~]\tWixEnvironmentTest", - "Environment:WixEnvironmentTest1\t=-WixEnvTest1\t\tWixEnvironmentTest", - "Environment:WixEnvironmentTest2\t+-WixEnvTest1\t\tWixEnvironmentTest", - "Environment:WixEnvironmentTest3\t!-WixEnvTest1\t\tWixEnvironmentTest", - "Environment:WixEnvironmentTest4\t=-*WIX\t[INSTALLFOLDER]\tWixEnvironmentTest", - }, results); - } - } - - [Fact(Skip = "Test demonstrates failure")] - public void PopulatesExampleTableBecauseOfEnsureTable() - { - var folder = TestData.Get(@"TestData"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "EnsureTable", "EnsureTable.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabaseByTable(msiPath, new[] { "Wix4Example" }); - Assert.Empty(results["Wix4Example"]); - } - } - - [Fact] - public void PopulatesFeatureTableWithParent() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "FeatureGroup", "FeatureGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Feature" }); - WixAssert.CompareLineByLine(new[] - { - "Feature:ChildFeature\tParentFeature\tChildFeatureTitle\t\t2\t1\t\t0", - "Feature:ParentFeature\t\tParentFeatureTitle\t\t2\t1\t\t0", - "Feature:ProductFeature\t\tMsiPackageTitle\t\t2\t1\t\t0", - }, results); - } - } - - [Fact] - public void PopulatesFontTableFromFontTitle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Font", "FontTitle.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Font" }); - WixAssert.CompareLineByLine(new[] - { - "Font:test.txt\tFakeFont", - }, results); - } - } - - [Fact] - public void PopulatesFontTableFromTrueType() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Font", "TrueType.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Font" }); - WixAssert.CompareLineByLine(new[] - { - "Font:TrueTypeFontFile\t", - }, results); - } - } - - [Fact] - public void PopulatesInstallExecuteSequenceTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "InstallExecuteSequence" }); - WixAssert.CompareLineByLine(new[] - { - "InstallExecuteSequence:CostFinalize\t\t1000", - "InstallExecuteSequence:CostInitialize\t\t800", - "InstallExecuteSequence:CreateFolders\t\t3700", - "InstallExecuteSequence:FileCost\t\t900", - "InstallExecuteSequence:FindRelatedProducts\t\t25", - "InstallExecuteSequence:InstallFiles\t\t4000", - "InstallExecuteSequence:InstallFinalize\t\t6600", - "InstallExecuteSequence:InstallInitialize\t\t1500", - "InstallExecuteSequence:InstallValidate\t\t1400", - "InstallExecuteSequence:LaunchConditions\t\t100", - "InstallExecuteSequence:MigrateFeatureStates\t\t1200", - "InstallExecuteSequence:ProcessComponents\t\t1600", - "InstallExecuteSequence:PublishFeatures\t\t6300", - "InstallExecuteSequence:PublishProduct\t\t6400", - "InstallExecuteSequence:RegisterProduct\t\t6100", - "InstallExecuteSequence:RegisterUser\t\t6000", - "InstallExecuteSequence:RemoveExistingProducts\t\t1401", - "InstallExecuteSequence:RemoveFiles\t\t3500", - "InstallExecuteSequence:RemoveFolders\t\t3600", - "InstallExecuteSequence:UnpublishFeatures\t\t1800", - "InstallExecuteSequence:ValidateProductID\t\t700", - }, results); - } - } - - [Fact] - public void PopulatesLockPermissionsTableWithEmptyPermissions() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "LockPermissions", "EmptyPermissions.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "LockPermissions" }); - WixAssert.CompareLineByLine(new[] - { - "LockPermissions:INSTALLFOLDER\tCreateFolder\t\tAdministrator\t0", - }, results); - } - } - - [Fact] - public void PopulatesMsiAssemblyTables() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Assembly", "Win32Assembly.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "Assembly", "data"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "MsiAssembly", "MsiAssemblyName" }); - WixAssert.CompareLineByLine(new[] - { - "MsiAssembly:test.txt\tProductFeature\ttest.dll.manifest\t\t1", - "MsiAssemblyName:test.txt\tname\tMyApplication.app", - "MsiAssemblyName:test.txt\tversion\t1.0.0.0", - }, results); - } - } - - [Fact] - public void PopulatesReserveCostTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ReserveCost", "ReserveCost.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "ReserveCost" }); - WixAssert.CompareLineByLine(new[] - { - "ReserveCost:TestCost\tReserveCostComp\tINSTALLFOLDER\t100\t200", - }, results); - } - } - - [Fact] - public void PopulatesServiceTables() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ServiceInstall", "OwnProcess.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "ServiceInstall", "ServiceControl" }); - WixAssert.CompareLineByLine(new[] - { - "ServiceControl:SampleService\tSampleService\t161\t\t1\ttest.txt", - "ServiceInstall:SampleService\tSampleService\t\t16\t4\t0\t\t\t\t\t\ttest.txt\t", - }, results); - } - } - - [Fact] - public void PopulatesTextStyleTableWhenColorIsNull() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "TextStyle", "ColorNull.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); - WixAssert.CompareLineByLine(new[] - { - "TextStyle:FirstTextStyle\tArial\t2\t\t", - }, results); - } - } - - [Fact] - public void PopulatesTextStyleTableWhenSizeIsLocalized() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "TextStyle", "SizeLocalized.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-loc", Path.Combine(folder, "TextStyle", "SizeLocalized.en-us.wxl"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); - WixAssert.CompareLineByLine(new[] - { - "TextStyle:CustomFont\tTahoma\t8\t\t", - }, results); - } - } - - [Fact] - public void PopulatesTypeLibTableWhenLanguageIsZero() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "TypeLib", "Language0.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "TypeLib" }); - WixAssert.CompareLineByLine(new[] - { - "TypeLib:{765BE8EE-BD7F-491E-90D2-C5A972462B50}\t0\tTypeLibComp\t\t\t\tProductFeature\t", - }, results); - } - } - - [Fact] - public void PopulatesUpgradeTableFromManualUpgrade() - { - var folder = TestData.Get(@"TestData\ManualUpgrade"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }, out var messages); - - Assert.Equal(0, result); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); - WixAssert.CompareLineByLine(new[] - { - "Upgrade:{01120000-00E0-0000-0000-0000000FF1CE}\t12.0.0\t13.0.0\t\t260\t\tBLAHBLAHBLAH", - }, results); - } - } - - [Fact] - public void PopulatesUpgradeTableFromDetectOnlyUpgrade() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); - WixAssert.CompareLineByLine(new[] - { - "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", - "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", - "Upgrade:{B05772EA-82B8-4DE0-B7EB-45B5F0CCFE6D}\t1.0.0\t\t\t256\t\tRELPRODFOUND", - }, results); - - var prefix = "Property:SecureCustomProperties\t"; - var secureProperties = Query.QueryDatabase(msiPath, new[] { "Property" }).Where(p => p.StartsWith(prefix)).Single(); - WixAssert.CompareLineByLine(new[] - { - "RELPRODFOUND", - "WIX_DOWNGRADE_DETECTED", - "WIX_UPGRADE_DETECTED", - }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p).ToArray()); - } - } - - [Fact] - public void CanMergeModule() - { - var folder = TestData.Get(@"TestData\SimpleMerge"); - - using (var fs = new DisposableFileSystem()) - { - var intermediateFolder = fs.GetFolder(); - var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); - var cabPath = Path.Combine(intermediateFolder, @"bin\cab1.cab"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, ".data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - Assert.Empty(section.Symbols.OfType()); - - var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - Assert.Empty(data.Tables["File"].Rows); - - var results = Query.QueryDatabase(msiPath, new[] { "File" }); - WixAssert.CompareLineByLine(new[] - { - "File:filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent.243FB739_4D05_472F_9CFB_EF6B1017B6DE\ttest.txt\t17\t\t\t512\t0" - }, results); - - var files = Query.GetCabinetFiles(cabPath); - WixAssert.CompareLineByLine(new[] - { - "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" - }, files.Select(f => f.Name).ToArray()); - } - } - - [Fact] - public void CanPublishComponentWithMultipleFeatureComponents() - { - var folder = TestData.Get(@"TestData\PublishComponent"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "PublishComponent" }); - WixAssert.CompareLineByLine(new[] - { - "PublishComponent:{0A82C8F6-9CE9-4336-B8BE-91A39B5F7081} Qualifier2 Component2 AppData2 ProductFeature2", - "PublishComponent:{BD245B5A-EC33-46ED-98FF-E9D3D416AD04} Qualifier1 Component1 AppData1 ProductFeature1", - }, results); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs deleted file mode 100644 index a566b490..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs +++ /dev/null @@ -1,131 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class MsiTransactionFixture - { - [Fact] - public void CantBuildX64AfterX86Bundle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var exePath = Path.Combine(binFolder, "test.exe"); - - BuildMsiPackages(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1151", // this is expected for this test - Path.Combine(folder, "MsiTransaction", "X64AfterX86Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - Assert.Equal(390, result.ExitCode); - } - } - - [Fact] - public void CanBuildX86AfterX64Bundle() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var binFolder = Path.Combine(baseFolder, "bin"); - var exePath = Path.Combine(binFolder, "test.exe"); - - BuildMsiPackages(folder, intermediateFolder, binFolder); - - var result = WixRunner.Execute(new[] - { - "build", - "-sw1151", // this is expected for this test - Path.Combine(folder, "MsiTransaction", "X86AfterX64Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", binFolder, - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(exePath)); - } - } - - private static void BuildMsiPackages(string folder, string intermediateFolder, string binFolder) - { - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "FirstX86", "FirstX86.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "SecondX86.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(binFolder, "SecondX86", "SecondX86.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-arch", "x64", - "-o", Path.Combine(binFolder, "FirstX64", "FirstX64.msi"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MsiTransaction", "SecondX64.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-arch", "x64", - "-o", Path.Combine(binFolder, "SecondX64", "SecondX64.msi"), - }); - - result.AssertSuccess(); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs deleted file mode 100644 index 475afcf0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class MsuPackageFixture - { - [Fact] - public void CanBuildBundleWithMsuPackage() - { - var folder = TestData.Get(@"TestData", "MsuPackage"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, "bin", "test.exe") - }); - - result.AssertSuccess(); - Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.exe"))); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs deleted file mode 100644 index 6b2d8bfa..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs +++ /dev/null @@ -1,211 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.Collections.Generic; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class PackagePayloadFixture - { - [Fact] - public void CanSpecifyPackagePayloadInPayloadGroup() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackagePayload", "PackagePayloadInPayloadGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); - var ignoreAttributesByElementName = new Dictionary> - { - { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, - }; - Assert.Equal(1, exePackageElements.Count); - Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); - - var payloadElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='burn.exe']"); - Assert.Equal(1, payloadElements.Count); - Assert.Equal("", payloadElements[0].GetTestXml()); - } - } - - [Fact] - public void ErrorWhenMissingSourceFileAndHash() - { - var folder = TestData.Get(@"TestData", "PackagePayload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "MissingSourceFileAndHash.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(44, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The MsuPackagePayload element's SourceFile or Hash attribute was not found; one of these is required.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - - [Fact] - public void ErrorWhenMissingSourceFileAndName() - { - var folder = TestData.Get(@"TestData", "PackagePayload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "MissingSourceFileAndName.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(44, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The MsiPackagePayload element's Name or SourceFile attribute was not found; one of these is required.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - - [Fact] - public void ErrorWhenSpecifiedHash() - { - var folder = TestData.Get(@"TestData", "PackagePayload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SpecifiedHash.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(4, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The MspPackagePayload element contains an unexpected attribute 'Hash'.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - - [Fact] - public void ErrorWhenSpecifiedHashAndMissingDownloadUrl() - { - var folder = TestData.Get(@"TestData", "PackagePayload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SpecifiedHashAndMissingDownloadUrl.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(10, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The MsuPackagePayload/@DownloadUrl attribute was not found; it is required when attribute Hash is specified.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - - [Fact] - public void ErrorWhenSpecifiedSourceFileAndHash() - { - var folder = TestData.Get(@"TestData", "PackagePayload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "SpecifiedSourceFileAndHash.wxs"), - "-o", Path.Combine(baseFolder, "test.wixlib") - }); - - Assert.Equal(35, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The ExePackagePayload/@Hash attribute cannot be specified when attribute SourceFile is present.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - - [Fact] - public void ErrorWhenWrongPackagePayloadInPayloadGroup() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackagePayload", "WrongPackagePayloadInPayloadGroup.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - Assert.Equal(407, result.ExitCode); - WixAssert.CompareLineByLine(new[] - { - "The ExePackagePayload element can only be used for ExePackages.", - "The location of the package related to previous error.", - "There is no payload defined for package 'WrongPackagePayloadInPayloadGroup'. This is specified on the MsiPackage element or a child MsiPackagePayload element.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs deleted file mode 100644 index cdba85de..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ParseFixture.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.Linq; - using WixToolset.Core; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - using Xunit; - - public class ParseFixture - { - [Fact] - public void GeneratesCorrectCustomActionIdentifiers() - { - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var section = new IntermediateSection("section", SectionType.Fragment); - var parseHelper = serviceProvider.GetService(); - - parseHelper.CreateCustomActionReference(null, section, "CustomAction32", Platform.X86, CustomActionPlatforms.X86); - parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86); - parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); - parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86); - parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64); - - var simpleReferences = section.Symbols.OfType(); - Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction32_X86").FirstOrDefault()); - Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_X86").FirstOrDefault()); - Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_A64").FirstOrDefault()); - Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X86").FirstOrDefault()); - Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X64").FirstOrDefault()); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs deleted file mode 100644 index 483e3fd5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs +++ /dev/null @@ -1,279 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Text; - using System.Xml; - using System.Xml.Linq; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Burn; - using Xunit; - - public class PatchFixture - { - private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd"; - - [Fact] - public void CanBuildSimplePatch() - { - var folder = TestData.Get(@"TestData\PatchSingle"); - - using (var fs = new DisposableFileSystem()) - { - var tempFolder = fs.GetFolder(); - - var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); - var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); - var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); - var patchPath = Path.ChangeExtension(patchPdb, ".msp"); - - Assert.True(File.Exists(baselinePdb)); - Assert.True(File.Exists(update1Pdb)); - - var doc = GetExtractPatchXml(patchPath); - Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); - - var names = Query.GetSubStorageNames(patchPath); - Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); - - var cab = Path.Combine(tempFolder, "foo.cab"); - Query.ExtractStream(patchPath, "foo.cab", cab); - Assert.True(File.Exists(cab)); - - var files = Query.GetCabinetFiles(cab); - Assert.Equal(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray()); - } - } - - [Fact] - public void CanBuildSimplePatchWithNoFileChanges() - { - var folder = TestData.Get(@"TestData\PatchNoFileChanges"); - - using (var fs = new DisposableFileSystem()) - { - var tempFolder = fs.GetFolder(); - - var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); - var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); - var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1", hasNoFiles: true); - var patchPath = Path.ChangeExtension(patchPdb, ".msp"); - - Assert.True(File.Exists(baselinePdb)); - Assert.True(File.Exists(update1Pdb)); - - var doc = GetExtractPatchXml(patchPath); - Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); - - var names = Query.GetSubStorageNames(patchPath); - Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); - - var cab = Path.Combine(tempFolder, "foo.cab"); - Query.ExtractStream(patchPath, "foo.cab", cab); - Assert.True(File.Exists(cab)); - - var files = Query.GetCabinetFiles(cab); - Assert.Empty(files); - } - } - - [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6387")] - public void CanBuildPatchFromProductWithFilesFromWixlib() - { - var folder = TestData.Get(@"TestData\PatchFromWixlib"); - - using (var fs = new DisposableFileSystem()) - { - var tempFolderBaseline = fs.GetFolder(); - var tempFolderUpdate = fs.GetFolder(); - var tempFolderPatch = fs.GetFolder(); - - var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0"); - var update1Pdb = BuildMsi("Update.msi", folder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1"); - var patchPdb = BuildMsp("Patch1.msp", folder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(update1Pdb) }, hasNoFiles: true); - var patchPath = Path.ChangeExtension(patchPdb, ".msp"); - - Assert.True(File.Exists(baselinePdb)); - Assert.True(File.Exists(update1Pdb)); - } - } - - [Fact] - public void CanBuildBundleWithNonSpecificPatches() - { - var folder = TestData.Get(@"TestData\PatchNonSpecific"); - - using (var fs = new DisposableFileSystem()) - { - var tempFolder = fs.GetFolder(); - - var baselinePdb = BuildMsi("Baseline.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.0", "A", "B"); - var updatePdb = BuildMsi("Update.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.1", "A", "B"); - var patchAPdb = BuildMsp("PatchA.msp", Path.Combine(folder, "PatchA"), tempFolder, "1.0.1", hasNoFiles: true); - var patchBPdb = BuildMsp("PatchB.msp", Path.Combine(folder, "PatchB"), tempFolder, "1.0.1", hasNoFiles: true); - var patchCPdb = BuildMsp("PatchC.msp", Path.Combine(folder, "PatchC"), tempFolder, "1.0.1", hasNoFiles: true); - var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); - var bundleBPdb = BuildBundle("BundleB.exe", Path.Combine(folder, "BundleB"), tempFolder); - var bundleCPdb = BuildBundle("BundleC.exe", Path.Combine(folder, "BundleC"), tempFolder); - - VerifyPatchTargetCodes(bundleAPdb, new[] - { - "", - }); - VerifyPatchTargetCodes(bundleBPdb, new[] - { - "", - "", - }); - VerifyPatchTargetCodes(bundleCPdb, new string[0]); - } - } - - [Fact] - public void CanBuildBundleWithSlipstreamPatch() - { - var folder = TestData.Get(@"TestData\PatchSingle"); - - using (var fs = new DisposableFileSystem()) - { - var tempFolder = fs.GetFolder(); - - var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); - var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); - var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); - var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); - - using (var wixOutput = WixOutput.Read(bundleAPdb)) - { - var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); - var doc = new XmlDocument(); - doc.LoadXml(manifestData); - var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); - var slipstreamMspNodes = doc.SelectNodes("/w:BurnManifest/w:Chain/w:MsiPackage/w:SlipstreamMsp", nsmgr); - Assert.Equal(1, slipstreamMspNodes.Count); - Assert.Equal("", slipstreamMspNodes[0].GetTestXml()); - } - } - } - - private static void VerifyPatchTargetCodes(string pdbPath, string[] expected) - { - using (var wixOutput = WixOutput.Read(pdbPath)) - { - var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); - var doc = new XmlDocument(); - doc.LoadXml(manifestData); - var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); - var patchTargetCodes = doc.SelectNodes("/w:BurnManifest/w:PatchTargetCode", nsmgr); - - var actual = new List(); - foreach (XmlNode patchTargetCodeNode in patchTargetCodes) - { - actual.Add(patchTargetCodeNode.GetTestXml()); - } - - WixAssert.CompareLineByLine(expected, actual.ToArray()); - } - } - - private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB) - { - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(sourceFolder, @"Package.wxs"), - "-d", "V=" + defineV, - "-d", "A=" + defineA, - "-d", "B=" + defineB, - "-bindpath", Path.Combine(sourceFolder, ".data"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", outputPath, - "-ext", extensionPath, - }); - - result.AssertSuccess(); - - return Path.ChangeExtension(outputPath, ".wixpdb"); - } - - private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable bindpaths = null, bool hasNoFiles = false) - { - var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); - - var args = new List - { - "build", - hasNoFiles ? "-sw1079" : " ", - Path.Combine(sourceFolder, @"Patch.wxs"), - "-d", "V=" + defineV, - "-bindpath", Path.Combine(baseFolder, "bin"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", outputPath - }; - - foreach (var additionaBindPath in bindpaths ?? Enumerable.Empty()) - { - args.Add("-bindpath"); - args.Add(additionaBindPath); - } - - var result = WixRunner.Execute(args.ToArray()); - - result.AssertSuccess(); - - return Path.ChangeExtension(outputPath, ".wixpdb"); - } - - private static string BuildBundle(string outputName, string sourceFolder, string baseFolder) - { - var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(sourceFolder, @"Bundle.wxs"), - Path.Combine(sourceFolder, "..", "..", "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(sourceFolder, "..", "..", "SimpleBundle", "data"), - "-bindpath", Path.Combine(baseFolder, "bin"), - "-intermediateFolder", Path.Combine(baseFolder, "obj"), - "-o", outputPath - }); - - result.AssertSuccess(); - - return Path.ChangeExtension(outputPath, ".wixpdb"); - } - - private static XDocument GetExtractPatchXml(string path) - { - var buffer = new StringBuilder(65535); - var size = buffer.Capacity; - - var er = MsiExtractPatchXMLData(path, 0, buffer, ref size); - if (er != 0) - { - throw new Win32Exception(er); - } - - return XDocument.Parse(buffer.ToString()); - } - - [DllImport("msi.dll", EntryPoint = "MsiExtractPatchXMLDataW", CharSet = CharSet.Unicode, ExactSpelling = true)] - private static extern int MsiExtractPatchXMLData(string szPatchPath, int dwReserved, StringBuilder szXMLData, ref int pcchXMLData); - - [DllImport("msi.dll", EntryPoint = "MsiApplyPatchW", CharSet = CharSet.Unicode, ExactSpelling = true)] - private static extern int MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine); - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs deleted file mode 100644 index 23f6a9ba..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs +++ /dev/null @@ -1,212 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Xml; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class PayloadFixture - { - [Fact] - public void CanParseValidName() - { - var folder = TestData.Get(@"TestData\Payload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "ValidName.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - Assert.Empty(result.Messages); - - var intermediate = Intermediate.Load(wixlibPath); - var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); - var payloadSymbol = allSymbols.OfType() - .SingleOrDefault(); - Assert.NotNull(payloadSymbol); - - var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool - ? field.AsNullableNumber()?.ToString() - : field?.AsString()) - .ToList(); - Assert.Equal(@"dir\file.ext", fields[(int)WixBundlePayloadSymbolFields.Name]); - } - } - - [Fact] - public void CanCanonicalizeName() - { - var folder = TestData.Get(@"TestData\Payload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(warningsAsErrors: false, new[] - { - "build", - Path.Combine(folder, "CanonicalizeName.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - Assert.Single(result.Messages, m => m.Id == (int)WarningMessages.Ids.PathCanonicalized); - - var intermediate = Intermediate.Load(wixlibPath); - var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); - var payloadSymbol = allSymbols.OfType() - .SingleOrDefault(); - Assert.NotNull(payloadSymbol); - - var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool - ? field.AsNullableNumber()?.ToString() - : field?.AsString()) - .ToList(); - Assert.Equal(@"c\d.exe", fields[(int)WixBundlePayloadSymbolFields.Name]); - } - } - - [Fact] - public void RejectsAbsoluteName() - { - var folder = TestData.Get(@"TestData\Payload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "AbsoluteName.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.InRange(result.ExitCode, 2, int.MaxValue); - - var expectedIllegalRelativeLongFileName = 1; - var expectedPayloadMustBeRelativeToCache = 2; - Assert.Equal(expectedIllegalRelativeLongFileName, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.IllegalRelativeLongFilename).Count()); - Assert.Equal(expectedPayloadMustBeRelativeToCache, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.PayloadMustBeRelativeToCache).Count()); - } - } - - [Fact] - public void RejectsPayloadSharedBetweenPackageAndBA() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Payload", "SharedBAAndPackagePayloadBundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - Assert.Equal((int)LinkerErrors.Ids.PayloadSharedWithBA, result.ExitCode); - } - } - - [Fact] - public void ReplacesDownloadUrlPlaceholders() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); - var baFolderPath = Path.Combine(baseFolder, "ba"); - var extractFolderPath = Path.Combine(baseFolder, "extract"); - - var result = WixRunner.Execute(false, new[] - { - "build", - Path.Combine(folder, "Payload", "DownloadUrlPlaceholdersBundle.wxs"), - Path.Combine(folder, "SimpleBundle", "MultiFileBootstrapperApplication.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-bindpath", Path.Combine(folder, ".Data"), - "-intermediateFolder", intermediateFolder, - "-o", bundlePath, - }); - - result.AssertSuccess(); - - WixAssert.CompareLineByLine(new string[] - { - "The Payload 'burn.exe' is being added to Container 'PackagesContainer', overriding its Compressed value of 'no'.", - }, result.Messages.Select(m => m.ToString()).ToArray()); - - Assert.True(File.Exists(bundlePath)); - - var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); - extractResult.AssertSuccess(); - - var ignoreAttributesByElementName = new Dictionary> - { - { "Container", new List { "FileSize", "Hash" } }, - { "Payload", new List { "FileSize", "Hash" } }, - }; - var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributesByElementName)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - "", - "", - @"", - @"", - }, payloads); - - var containers = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Container") - .Cast() - .Select(e => e.GetTestXml(ignoreAttributesByElementName)) - .ToArray(); - WixAssert.CompareLineByLine(new string[] - { - "", - }, containers); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs deleted file mode 100644 index ae8a1bcc..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs +++ /dev/null @@ -1,181 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - using Xunit; - - public class PreprocessorFixture - { - [Fact] - public void PreprocessDirectly() - { - var folder = TestData.Get(@"TestData\IncludePath"); - var sourcePath = Path.Combine(folder, "Package.wxs"); - var includeFolder = Path.Combine(folder, "data"); - var includeFile = Path.Combine(includeFolder, "Package.wxi"); - - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - - var context = serviceProvider.GetService(); - context.SourcePath = sourcePath; - context.IncludeSearchPaths = new[] { includeFolder }; - - var preprocessor = serviceProvider.GetService(); - var result = preprocessor.Preprocess(context); - - var includedFile = result.IncludedFiles.Single(); - Assert.NotNull(result.Document); - Assert.Equal(includeFile, includedFile.Path); - Assert.Equal(sourcePath, includedFile.SourceLineNumbers.FileName); - Assert.Equal(1, includedFile.SourceLineNumbers.LineNumber.Value); - Assert.Equal($"{sourcePath}*1", includedFile.SourceLineNumbers.QualifiedFileName); - Assert.Null(includedFile.SourceLineNumbers.Parent); - } - - [Fact] - public void IncludeSourceLineNumbersPreserved() - { - var folder = TestData.Get(@"TestData\IncludePath"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(warningsAsErrors: false, new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-includepath", Path.Combine(folder, "data"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - using (var output = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) - { - var intermediate = Intermediate.Load(output); - var component = intermediate.Sections.Single().Symbols.OfType().Single(); - Assert.Equal(3, component.SourceLineNumbers.LineNumber); - Assert.Equal(5, component.SourceLineNumbers.Parent.LineNumber); - - var encoded = component.SourceLineNumbers.GetEncoded(); - var decoded = SourceLineNumber.CreateFromEncoded(encoded); - Assert.Equal(3, decoded.LineNumber); - Assert.Equal(5, decoded.Parent.LineNumber); - } - } - } - - [Fact] - /// - /// This test will fail on 32-bit operating systems because it depends on "CommonProgramFiles(x86)" - /// which is only defined on 64-bit Windows. - /// - public void SupportParensInEnvironmentVariables() - { - var folder = TestData.Get(@"TestData", "Preprocessor"); - - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var context = serviceProvider.GetService(); - context.SourcePath = Path.Combine(folder, "EnvParens.wxs"); - - var preprocessor = serviceProvider.GetService(); - var result = preprocessor.Preprocess(context); - Assert.NotNull(result.Document); - } - - [Fact] - public void VariableRedefinitionIsAWarning() - { - var folder = TestData.Get(@"TestData\Variables"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(warningsAsErrors: false, new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var warning = result.Messages.Where(message => message.Id == (int)WarningMessages.Ids.VariableDeclarationCollision); - Assert.Single(warning); - } - } - - [Fact] - public void ForEachLoopsWork() - { - var folder = TestData.Get(@"TestData\ForEach"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - } - } - - [Fact] - public void NonterminatedPreprocessorInstructionShowsSourceLineNumber() - { - var folder = TestData.Get(@"TestData\BadIf"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - Assert.Equal(147, result.ExitCode); - Assert.StartsWith("Found a ", result.Messages.Single().ToString()); - } - } - } -} - diff --git a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs deleted file mode 100644 index e4d95b5d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs +++ /dev/null @@ -1,173 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class RegistryFixture - { - [Fact] - public void PopulatesRegistryTableFromRegistryValue() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "RegistryValue.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - WixAssert.CompareLineByLine(new[] - { - "Registry:reg04OIwIchl.9ZTjisTT6NzGSsQSM\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMiscComponent", - "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent", - }, results); - } - } - - [Fact] - public void PopulatesRegistryTableFromRegistryValueMultiString() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "RegistryValueMultiString.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - WixAssert.CompareLineByLine(new[] - { - "Registry:regitq_Wx9LfvJuNSc2un6gIHAzr4A\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMultiStringComponent", - "Registry:regmeTJMpOD41igfxhTcUVZ7kNG1Mo\t2\tPath\\To\\Key\t\ta[~]b[~][~]c[~]\tMultiStringComponent", - }, results); - } - } - - [Fact] - public void DuplicateRegistryValueIdsAreDetectedSmoothly() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "DuplicateRegistryValueIds.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }, out var messages); - - Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol).Count()); - Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol2).Count()); - } - } - - [Fact] - public void PopulatesRegistryTableFromRemoveRegistryKey() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "RemoveRegistryKey.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - WixAssert.CompareLineByLine(new[] - { - "Registry:RemoveAKeyName\t2\tAKeyName\t-\t\tRemoveRegistryKeyComp", - }, results); - } - } - - [Fact] - public void PopulatesRegistryTableWithoutExtraBackslash() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Registry", "RegistryKeyEndingWithBackslash.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); - WixAssert.CompareLineByLine(new[] - { - "Registry:reg1\t2\tSoftware\\WBM\\WB\t*\t\tMiscComponent", - "Registry:reg2\t2\tSoftware\\WBM\\WB\tInstallationPath\t[INSTALLFOLDER]\tMiscComponent", - }, results); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs deleted file mode 100644 index 9e19abb0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class RollbackBoundaryFixture - { - [Fact] - public void CanStartChainWithRollbackBoundary() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var exePath = Path.Combine(baseFolder, @"bin\test.exe"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "RollbackBoundary", "BeginningOfChain.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), - Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), - "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), - "-intermediateFolder", intermediateFolder, - "-o", exePath, - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(exePath)); - } - } - - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs deleted file mode 100644 index 3b6c50c0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs +++ /dev/null @@ -1,78 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using Xunit; - - public class ShortcutFixture - { - [Fact] - public void CanBuildShortcutNameWithShortname() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Shortcut", "ShortcutSameNameShortName.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - var results = Query.QueryDatabase(msiPath, new[] { "Shortcut" }); - WixAssert.CompareLineByLine(new[] - { - "Shortcut:sctzJpBYlrhdx4Mm9Xh41X0KPWYiX0\tINSTALLFOLDER\tDaName\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", - }, results); - } - } - - [Fact] - public void PopulatesMsiShortcutPropertyTable() - { - var folder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Shortcut", "ShortcutProperty.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(msiPath)); - var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty", "Shortcut" }); - WixAssert.CompareLineByLine(new[] - { - "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue", - "Shortcut:TheShortcut\tINSTALLFOLDER\td\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", - }, results); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs b/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs deleted file mode 100644 index 15276b18..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs +++ /dev/null @@ -1,100 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using System.Xml.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class SoftwareTagFixture - { - private static readonly XNamespace BurnManifestNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; - private static readonly XNamespace SwidTagNamespace = "http://standards.iso.org/iso/19770/-2/2009/schema.xsd"; - - [Fact] - public void CanBuildPackageWithTag() - { - var folder = TestData.Get(@"TestData\ProductTag"); - var build = new Builder(folder, null, new[] { folder }); - - var results = build.BuildAndQuery(Build, "File", "SoftwareIdentificationTag"); - - var replacePackageCodeStart = results[2].IndexOf("\tmsi:package/") + "\tmsi:package/".Length; - var replacePackageCodeEnd = results[2].IndexOf("\t", replacePackageCodeStart); - results[2] = results[2].Substring(0, replacePackageCodeStart) + "???" + results[2].Substring(replacePackageCodeEnd); - WixAssert.CompareLineByLine(new[] - { - "File:filF5_pLhBuF5b4N9XEo52g_hUM5Lo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\texample.txt\t20\t\t\t512\t1", - "File:tagEYRYWwOt95punO7qPPAQ9p1GBpY\ttagEYRYWwOt95punO7qPPAQ9p1GBpY\trdcfonyt.swi|~TagTestPackage.swidtag\t449\t\t\t1\t2", - "SoftwareIdentificationTag:tagEYRYWwOt95punO7qPPAQ9p1GBpY\twixtoolset.org\tmsi:package/???\tmsi:upgrade/047730A5-30FE-4A62-A520-DA9381B8226A\t" - }, results.ToArray()); - } - - [Fact] - public void CanBuildBundleWithTag() - { - var testDataFolder = TestData.Get(@"TestData"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(testDataFolder, "ProductTag", "PackageWithTag.wxs"), - Path.Combine(testDataFolder, "ProductTag", "PackageComponents.wxs"), - "-loc", Path.Combine(testDataFolder, "ProductTag", "Package.en-us.wxl"), - "-bindpath", Path.Combine(testDataFolder, "ProductTag"), - "-intermediateFolder", Path.Combine(intermediateFolder, "package"), - "-o", Path.Combine(baseFolder, "package", @"test.msi") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(testDataFolder, "BundleTag", "BundleWithTag.wxs"), - "-bindpath", Path.Combine(testDataFolder, "BundleTag"), - "-bindpath", Path.Combine(baseFolder, "package"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.exe") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - - using (var ouput = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) - { - var badata = ouput.GetDataStream("wix-burndata.xml"); - var doc = XDocument.Load(badata); - - var swidTag = doc.Root.Element(BurnManifestNamespace + "Registration").Element(BurnManifestNamespace + "SoftwareTag").Value; - - var swidTagPath = Path.Combine(baseFolder, "test.swidtag"); - File.WriteAllText(swidTagPath, swidTag); - - var docTag = XDocument.Load(swidTagPath); - var title = docTag.Root.Attribute("name").Value; - var version = docTag.Root.Attribute("version").Value; - Assert.Equal("~TagTestBundle", title); - Assert.Equal("4.3.2.1", version); - } - } - } - - private static void Build(string[] args) - { - var result = WixRunner.Execute(args) - .AssertSuccess(); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe b/src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe deleted file mode 100644 index 2a4f423f..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs deleted file mode 100644 index b34c547d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs deleted file mode 100644 index 4dd701f0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs deleted file mode 100644 index 6b9fe013..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs deleted file mode 100644 index e255c83d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs deleted file mode 100644 index c17d9848..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi deleted file mode 100644 index ea1296c3..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs deleted file mode 100644 index f800264d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs deleted file mode 100644 index 8be5abb2..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs deleted file mode 100644 index c345305d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs deleted file mode 100644 index e0c84c63..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs deleted file mode 100644 index 45cc7114..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe deleted file mode 100644 index 18129b73..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest b/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest deleted file mode 100644 index 0da1f6d0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs deleted file mode 100644 index 3caa20ff..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs deleted file mode 100644 index 1d7ebb94..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs deleted file mode 100644 index 2a75e3d7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs deleted file mode 100644 index a2d49b18..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs deleted file mode 100644 index 0c350042..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs deleted file mode 100644 index 4fe7e097..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs deleted file mode 100644 index 5ebe5472..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs deleted file mode 100644 index 78f3ebd3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs deleted file mode 100644 index c717680b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs deleted file mode 100644 index fc53c4a2..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs deleted file mode 100644 index 6cf8528e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs deleted file mode 100644 index c3528a67..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt deleted file mode 100644 index 3b862323..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs deleted file mode 100644 index 5b41e807..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs deleted file mode 100644 index 7f5ea456..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs deleted file mode 100644 index e52302d4..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs deleted file mode 100644 index eefae822..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs deleted file mode 100644 index fd8d3698..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs deleted file mode 100644 index c5a93eb3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs deleted file mode 100644 index 7303a05a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs deleted file mode 100644 index f44fb7bc..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll deleted file mode 100644 index 64061ea0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll +++ /dev/null @@ -1 +0,0 @@ -This is fakeba.dll. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs deleted file mode 100644 index 78e754c1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs deleted file mode 100644 index 18cdfd32..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs deleted file mode 100644 index a93b23ef..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs deleted file mode 100644 index e738b407..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs deleted file mode 100644 index b0bde4f6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs deleted file mode 100644 index 514f9243..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs deleted file mode 100644 index c0dc9bc0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi deleted file mode 100644 index 2cd10f09..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs deleted file mode 100644 index 15a9a0ce..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs deleted file mode 100644 index db07af2c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs deleted file mode 100644 index 7f17b538..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt deleted file mode 100644 index 1b4ffe8a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt +++ /dev/null @@ -1 +0,0 @@ -This is example.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt deleted file mode 100644 index 8c874ae7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt +++ /dev/null @@ -1 +0,0 @@ -This is other.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs deleted file mode 100644 index a0e921cb..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs deleted file mode 100644 index d7b5bdc0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs deleted file mode 100644 index beaf70bf..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs deleted file mode 100644 index e175a18f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs deleted file mode 100644 index 28900e55..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs deleted file mode 100644 index 90d66cc3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs deleted file mode 100644 index be991c65..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs deleted file mode 100644 index c64ef143..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs deleted file mode 100644 index ff8741cf..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs deleted file mode 100644 index f8ce1c38..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs deleted file mode 100644 index 10c4f91f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs deleted file mode 100644 index d7d86008..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs deleted file mode 100644 index d32e808c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs deleted file mode 100644 index 08a9c470..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl deleted file mode 100644 index bc2ccf04..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - - This is row one - This is row two - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs deleted file mode 100644 index e1da74f8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt deleted file mode 100644 index 97f701ce..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt +++ /dev/null @@ -1 +0,0 @@ -This is file1.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt deleted file mode 100644 index 46493186..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt +++ /dev/null @@ -1 +0,0 @@ -This is file2.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs deleted file mode 100644 index 71553e2a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab deleted file mode 100644 index 125eeb2c..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi deleted file mode 100644 index 81335041..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs deleted file mode 100644 index 246bcafc..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab deleted file mode 100644 index 125eeb2c..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi deleted file mode 100644 index 9cb6d6bc..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs deleted file mode 100644 index 81915759..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab deleted file mode 100644 index 125eeb2c..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi deleted file mode 100644 index 762b136c..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs deleted file mode 100644 index 7c5fe3cf..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm b/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm deleted file mode 100644 index 2a7b5e3a..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs deleted file mode 100644 index 2f277956..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs deleted file mode 100644 index 6df8a7c0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs deleted file mode 100644 index 4d188d3a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs deleted file mode 100644 index 9c3a9690..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs deleted file mode 100644 index ec6e62df..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs deleted file mode 100644 index 3e7887c4..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs deleted file mode 100644 index 6e9a4495..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs deleted file mode 100644 index 50cf6850..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs deleted file mode 100644 index cc87b49f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs deleted file mode 100644 index a58b68c8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs deleted file mode 100644 index 01767abb..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs deleted file mode 100644 index de9744a7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl deleted file mode 100644 index 066e16bb..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl +++ /dev/null @@ -1,9 +0,0 @@ - - - - - A newer version of [ProductName] is already installed. - MsiPackage - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs deleted file mode 100644 index 287085e8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs deleted file mode 100644 index 88a4ac81..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs deleted file mode 100644 index 5c84f33e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs deleted file mode 100644 index 7f17b538..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt deleted file mode 100644 index 1b4ffe8a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt +++ /dev/null @@ -1 +0,0 @@ -This is example.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs deleted file mode 100644 index e57180f7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs deleted file mode 100644 index 0b094860..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs deleted file mode 100644 index be302720..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs deleted file mode 100644 index 6fb9ef05..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs deleted file mode 100644 index 6ac48963..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs deleted file mode 100644 index 8fff563e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs deleted file mode 100644 index 2a75e3d7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs deleted file mode 100644 index 1de84e81..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs deleted file mode 100644 index 0bd80c50..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs deleted file mode 100644 index 7a0485ed..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi deleted file mode 100644 index 03885e3e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi deleted file mode 100644 index f2df3b86..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs deleted file mode 100644 index 7826d673..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl deleted file mode 100644 index f7453566..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl deleted file mode 100644 index ef287da7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl deleted file mode 100644 index 10ebf2c5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs deleted file mode 100644 index 13c79e90..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl deleted file mode 100644 index 596ee077..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl +++ /dev/null @@ -1,7 +0,0 @@ - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs deleted file mode 100644 index dfae2157..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs deleted file mode 100644 index 4fd3493a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs deleted file mode 100644 index e7492db4..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt deleted file mode 100644 index ad9cdcb5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt +++ /dev/null @@ -1 +0,0 @@ -This is a1.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt deleted file mode 100644 index d5de23de..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt +++ /dev/null @@ -1 +0,0 @@ -This is a2.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt deleted file mode 100644 index 88bc4a56..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt +++ /dev/null @@ -1 +0,0 @@ -This is b1.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt deleted file mode 100644 index 38525276..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt +++ /dev/null @@ -1 +0,0 @@ -This is b2.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs deleted file mode 100644 index e72b6402..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs deleted file mode 100644 index e72b6402..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs deleted file mode 100644 index e72b6402..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs deleted file mode 100644 index e72b6402..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs deleted file mode 100644 index e6527a36..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs deleted file mode 100644 index f1c939db..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs deleted file mode 100644 index dbca3393..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll deleted file mode 100644 index b3cf17d8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll +++ /dev/null @@ -1 +0,0 @@ -This is a fake BA DLL diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu b/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu deleted file mode 100644 index d63da4be..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu +++ /dev/null @@ -1 +0,0 @@ -This is a fake MSU package diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs deleted file mode 100644 index 2b1a1a0f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs deleted file mode 100644 index 82797ebe..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs deleted file mode 100644 index 0bf0e963..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs deleted file mode 100644 index 5e1b99ff..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs deleted file mode 100644 index f220d81a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs deleted file mode 100644 index 149870a4..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs deleted file mode 100644 index 3c361c49..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs deleted file mode 100644 index 8e62f660..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs deleted file mode 100644 index f79da874..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs deleted file mode 100644 index dda306cf..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt deleted file mode 100644 index 6fd385bd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt +++ /dev/null @@ -1 +0,0 @@ -This is A v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt deleted file mode 100644 index b1f0bc01..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt +++ /dev/null @@ -1 +0,0 @@ -This ia A v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt deleted file mode 100644 index ece55fec..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt +++ /dev/null @@ -1 +0,0 @@ -This is B v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt deleted file mode 100644 index cf3372fd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt +++ /dev/null @@ -1 +0,0 @@ -This ia B v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs deleted file mode 100644 index c9dcdd72..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs deleted file mode 100644 index d39170c0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs deleted file mode 100644 index 5cb8ede8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs deleted file mode 100644 index 52e87f64..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt deleted file mode 100644 index 6fd385bd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt +++ /dev/null @@ -1 +0,0 @@ -This is A v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs deleted file mode 100644 index dab959d5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs deleted file mode 100644 index 889b1220..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs deleted file mode 100644 index 4a8f5630..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs deleted file mode 100644 index 7fb3cb56..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs deleted file mode 100644 index 201d177b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs deleted file mode 100644 index 62a89af3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs deleted file mode 100644 index 1b01774c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs deleted file mode 100644 index f0630ead..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs deleted file mode 100644 index f9d2a55a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt deleted file mode 100644 index 6fd385bd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt +++ /dev/null @@ -1 +0,0 @@ -This is A v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt deleted file mode 100644 index b1f0bc01..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt +++ /dev/null @@ -1 +0,0 @@ -This ia A v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt deleted file mode 100644 index ece55fec..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt +++ /dev/null @@ -1 +0,0 @@ -This is B v1.0.0 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt deleted file mode 100644 index cf3372fd..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt +++ /dev/null @@ -1 +0,0 @@ -This ia B v1.0.1 diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs deleted file mode 100644 index bc460636..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs deleted file mode 100644 index e3845382..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs deleted file mode 100644 index 52e87f64..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs deleted file mode 100644 index dc94d688..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs deleted file mode 100644 index 544b80ec..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs deleted file mode 100644 index f8f38ea6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs deleted file mode 100644 index 5263cbd4..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs deleted file mode 100644 index 9c37a27d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs deleted file mode 100644 index 68d115c5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs deleted file mode 100644 index 37a2c462..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs deleted file mode 100644 index 5bf78a9d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt deleted file mode 100644 index 1b4ffe8a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt +++ /dev/null @@ -1 +0,0 @@ -This is example.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs deleted file mode 100644 index f62bbd0e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs deleted file mode 100644 index 433be7f0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs deleted file mode 100644 index 0621eb8d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs deleted file mode 100644 index d3b31db5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs deleted file mode 100644 index 5166be16..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs deleted file mode 100644 index 8f4f661d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs deleted file mode 100644 index 452aea69..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs deleted file mode 100644 index 1fb2e906..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs deleted file mode 100644 index fe6e179e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs deleted file mode 100644 index c62c571d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs deleted file mode 100644 index a55a1e18..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs deleted file mode 100644 index 3218295b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs deleted file mode 100644 index ecfccfcb..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs deleted file mode 100644 index bbad63e6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt deleted file mode 100644 index 1970cae6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is a\test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt deleted file mode 100644 index fa2c7082..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is b\test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt deleted file mode 100644 index 1c0cbda6..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is c\test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs deleted file mode 100644 index d5379e7b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi deleted file mode 100644 index 7f894091..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs deleted file mode 100644 index 65cba20e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs deleted file mode 100644 index d3f8accf..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs deleted file mode 100644 index 7e8f2e99..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs deleted file mode 100644 index f16fce0d..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs deleted file mode 100644 index da1e4f38..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs deleted file mode 100644 index 27f2ab9b..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs deleted file mode 100644 index d704bbf1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi deleted file mode 100644 index 8737f3c2..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl deleted file mode 100644 index bc1dee83..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - ~TestBundle - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs deleted file mode 100644 index 21749c07..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs deleted file mode 100644 index f5fe9885..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs deleted file mode 100644 index 48f53ae3..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll deleted file mode 100644 index 0e461ba8..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll +++ /dev/null @@ -1 +0,0 @@ -This is Shared.dll. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt deleted file mode 100644 index 8b986220..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll deleted file mode 100644 index 970efdf0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll +++ /dev/null @@ -1 +0,0 @@ -This is a fakeba.dll \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi deleted file mode 100644 index 0722d60e..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm deleted file mode 100644 index 6f179aba..00000000 Binary files a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm and /dev/null differ diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs deleted file mode 100644 index 3c999812..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl deleted file mode 100644 index c74e86a7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - Example Company - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj deleted file mode 100644 index 597d4318..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - Debug - x86 - 0.9 - 27df04c6-3cef-4b9a-bac6-4e78d188384f - MergeModule1 - Module - MergeModule1 - MergeModule1 - - - $(Platform) - bin\$(Platform)\$(Configuration)\ - Debug - - - $(Platform) - bin\$(Platform)\$(Configuration)\ - - - $(Platform) - bin\$(Platform)\$(Configuration)\ - Debug - - - $(Platform) - bin\$(Platform)\$(Configuration)\ - - - - - - - - - - FgwepExtension.wixext - $(WixExtDir)\FgwepExtension.wixext.dll - - - - - - - - \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs deleted file mode 100644 index 8317e7af..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs deleted file mode 100644 index cad1f049..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs deleted file mode 100644 index 0d459f02..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs deleted file mode 100644 index d7b5bdc0..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs deleted file mode 100644 index b8e9f59c..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs deleted file mode 100644 index baa0c6b1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl deleted file mode 100644 index c74e86a7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - Example Company - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs deleted file mode 100644 index f4ce9c48..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs deleted file mode 100644 index 669de6ec..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl deleted file mode 100644 index 77d46861..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - Tahoma - 8 - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs deleted file mode 100644 index a591fdd9..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs deleted file mode 100644 index fa64f98f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs deleted file mode 100644 index 587d8e95..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs deleted file mode 100644 index 59839f30..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs deleted file mode 100644 index 7e459e9a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt deleted file mode 100644 index 1b4ffe8a..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt +++ /dev/null @@ -1 +0,0 @@ -This is example.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs deleted file mode 100644 index 7de55810..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - -= 4 AND $(sys.WIXMAJORVERSION) < 5 ?> - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs deleted file mode 100644 index f8203a07..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs deleted file mode 100644 index df867923..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt deleted file mode 100644 index eab3a9b5..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt +++ /dev/null @@ -1 +0,0 @@ -This is test2.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs deleted file mode 100644 index 7e6eee9f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs deleted file mode 100644 index e26c4509..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl deleted file mode 100644 index 38c12ac1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - A newer version of [ProductName] is already installed. - MsiPackage - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs deleted file mode 100644 index b29a785f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs deleted file mode 100644 index 7d1a4ae1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll deleted file mode 100644 index fd36c768..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll +++ /dev/null @@ -1 +0,0 @@ -This is alpha\foo.dll. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll deleted file mode 100644 index 292925c7..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll +++ /dev/null @@ -1 +0,0 @@ -This is mips\foo.dll. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll deleted file mode 100644 index 663e9d99..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll +++ /dev/null @@ -1 +0,0 @@ -This is powerpc\foo.dll. diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt deleted file mode 100644 index cd0db0e1..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is test.txt. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs b/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs deleted file mode 100644 index 5330305e..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.Collections.Generic; - using WixToolset.Core.TestPackage; - using Xunit; - - public class TestXmlFixture - { - [Fact] - public void ChangesIgnoredAttributesToStarToHelpMakeTestsLessFragile() - { - var original = @" - - - - -"; - var expected = ""; - var ignored = new Dictionary> { { "Target", new List { "One", "Two", "Missing" } } }; - Assert.Equal(expected, original.GetTestXml(ignored)); - } - - [Fact] - public void OutputsSingleQuotesSinceDoubleQuotesInCsharpLiteralStringsArePainful() - { - var original = ""; - var expected = ""; - Assert.Equal(expected, original.GetTestXml()); - } - - [Fact] - public void RemovesAllNamespacesToReduceTyping() - { - var original = ""; - var expected = ""; - Assert.Equal(expected, original.GetTestXml()); - } - - [Fact] - public void RemovesUnnecessaryWhitespaceToAvoidLineEndingIssues() - { - var original = @" - - - - -"; - var expected = ""; - Assert.Equal(expected, original.GetTestXml()); - } - - [Fact] - public void RemovesXmlDeclarationToReduceTyping() - { - var original = ""; - var expected = ""; - Assert.Equal(expected, original.GetTestXml()); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs b/src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs deleted file mode 100644 index 15e5d334..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs +++ /dev/null @@ -1,75 +0,0 @@ - -// 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 WixToolsetTest.CoreIntegration -{ - using System.Collections.Generic; - using WixToolset.Core; - using WixToolset.Data; - using WixToolset.Data.Bind; - using WixToolset.Extensibility.Services; - using Xunit; - - public class VariableResolverFixture - { - [Fact] - public void CanRecursivelyResolveVariables() - { - var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); - var variableResolver = serviceProvider.GetService(); - - var variables = new Dictionary() - { - { "ProductName", new BindVariable() { Id = "ProductName", Value = "Localized Product Name" } }, - { "ProductNameEdition", new BindVariable() { Id = "ProductNameEdition", Value = "!(loc.ProductName) Enterprise Edition" } }, - { "ProductNameEditionVersion", new BindVariable() { Id = "ProductNameEditionVersion", Value = "!(loc.ProductNameEdition) v1.2.3" } }, - }; - - var localization = new Localization(0, null, "x-none", variables, new Dictionary()); - - variableResolver.AddLocalization(localization); - - var result = variableResolver.ResolveVariables(null, "These are not the loc strings you're looking for."); - Assert.Equal("These are not the loc strings you're looking for.", result.Value); - Assert.False(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductName)"); - Assert.Equal("Welcome to Localized Product Name", result.Value); - Assert.True(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEdition)"); - Assert.Equal("Welcome to Localized Product Name Enterprise Edition", result.Value); - Assert.True(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion)"); - Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3", result.Value); - Assert.True(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !(bind.property.ProductVersion)"); - Assert.Equal("Welcome to !(bind.property.ProductVersion)", result.Value); - Assert.False(result.UpdatedValue); - Assert.True(result.DelayedResolve); - - var withUnknownLocString = "Welcome to !(loc.UnknownLocalizationVariable)"; - Assert.Throws(() => variableResolver.ResolveVariables(null, withUnknownLocString)); - - result = variableResolver.ResolveVariables(null, withUnknownLocString, errorOnUnknown: false); - Assert.Equal(withUnknownLocString, result.Value); - Assert.False(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable)"); - Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable)", result.Value); - Assert.True(result.UpdatedValue); - - result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)"); - Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value); - Assert.True(result.UpdatedValue); - Assert.True(result.DelayedResolve); - - result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion) !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)"); - Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3 !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value); - Assert.True(result.UpdatedValue); - Assert.True(result.DelayedResolve); - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/WarningFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WarningFixture.cs deleted file mode 100644 index c5b6c261..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/WarningFixture.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using Xunit; - - public class WarningFixture - { - [Fact] - public void SuppressedWarningsWithWarningAsErrorsAreNotErrors() - { - var folder = TestData.Get(@"TestData\Payload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(warningsAsErrors: true, new[] - { - "build", - "-sw1152", - Path.Combine(folder, "CanonicalizeName.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - } - } - - [Fact] - public void WarningsAsErrorsTreatsWarningsAsErrors() - { - var folder = TestData.Get(@"TestData\Payload"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(warningsAsErrors: true, new[] - { - "build", - Path.Combine(folder, "CanonicalizeName.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - Assert.Equal((int)WarningMessages.Ids.PathCanonicalized, result.ExitCode); - - var message = Assert.Single(result.Messages); - Assert.Equal(MessageLevel.Warning, message.Level); // TODO: is this right? - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj deleted file mode 100644 index fc62e932..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - netcoreapp3.1 - false - embedded - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs deleted file mode 100644 index 942f253f..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs +++ /dev/null @@ -1,205 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Example.Extension; - using Xunit; - - public class WixiplFixture - { - [Fact] - public void CanBuildSingleFile() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixiplPath = Path.Combine(intermediateFolder, @"test.wixipl"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixiplPath, - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(wixiplPath); - - Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); - Assert.False(intermediate.HasLevel(IntermediateLevels.Resolved)); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(intermediateFolder, @"test.wixipl"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - - Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); - - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().First(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CannotBuildWithSourceFileAndWixipl() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"test.wixipl") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - Path.Combine(intermediateFolder, @"test.wixipl"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - Assert.Equal((int)ErrorMessages.Ids.WixiplSourceFileIsExclusive, result.ExitCode); - } - } - - [Fact] - public void CanBuildMsiUsingExtensionLibrary() - { - var folder = TestData.Get(@"TestData\Wixipl"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - "-ext", extensionPath, - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi"), - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - { - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - - { - var binary = section.Symbols.OfType().Single(); - var path = binary[BinarySymbolFields.Data].AsPath().Path; - Assert.StartsWith(Path.Combine(baseFolder, @"obj\Example.Extension"), path); - Assert.EndsWith(@"wix-ir\example.txt", path); - Assert.Equal(@"BinFromWir", binary.Id.Id); - } - } - } - - [Fact] - public void CanBuildWixiplUsingExtensionLibrary() - { - var folder = TestData.Get(@"TestData\Wixipl"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - "-ext", extensionPath, - Path.Combine(folder, "Package.wxs"), - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"test.wixipl"), - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(intermediateFolder, @"test.wixipl"), - "-ext", extensionPath, - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi"), - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - { - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - - { - var binary = section.Symbols.OfType().Single(); - var path = binary[BinarySymbolFields.Data].AsPath().Path; - Assert.StartsWith(Path.Combine(baseFolder, @"obj\test"), path); - Assert.EndsWith(@"wix-ir\example.txt", path); - Assert.Equal(@"BinFromWir", binary.Id.Id); - } - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs deleted file mode 100644 index d7296cfe..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs +++ /dev/null @@ -1,316 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System; - using System.IO; - using System.Linq; - using Example.Extension; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class WixlibFixture - { - [Fact] - public void CanBuildSimpleBundleUsingWixlib() - { - var folder = TestData.Get(@"TestData\SimpleBundle"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"test.wixlib") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "MultiFileBundle.wxs"), - "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), - "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.exe") - }); - - result.AssertSuccess(); - - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); - Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); - } - } - - [Fact] - public void CanBuildWixlibWithBinariesFromNamedBindPaths() - { - var folder = TestData.Get(@"TestData\WixlibWithBinaries"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-bf", - "-bindpath", Path.Combine(folder, "data"), - // Use names that aren't excluded in default .gitignores. - "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", - "-bindpath", $"MipsBits={Path.Combine(folder, "data", "mips")}", - "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - var wixlib = Intermediate.Load(wixlibPath); - var binarySymbols = wixlib.Sections.SelectMany(s => s.Symbols).OfType().ToList(); - Assert.Equal(3, binarySymbols.Count); - Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll")); - Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-1")); - Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-2")); - } - } - - [Fact] - public void CanBuildSingleFileUsingWixlib() - { - var folder = TestData.Get(@"TestData\SingleFile"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - var wixlib = Intermediate.Load(wixlibPath); - - Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); - Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); - Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); - Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - - Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); - Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); - - var section = intermediate.Sections.Single(); - - var wixFile = section.Symbols.OfType().First(); - Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"test.txt", wixFile[FileSymbolFields.Source].PreviousValue.AsPath().Path); - } - } - - [Fact] - public void CanOverridePathWixVariable() - { - var folder = TestData.Get(@"TestData\WixVariableOverride"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-bf", - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - var wixlib = Intermediate.Load(wixlibPath); - - Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); - Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); - Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); - Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(baseFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); - - Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); - Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); - Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); - - var section = intermediate.Sections.Single(); - - var wixFile = section.Symbols.OfType().First(); - Assert.Equal(Path.Combine(folder, @"data\test2.txt"), wixFile.Data.Path); - } - } - - [Fact] - public void CanBuildWithExtensionUsingWixlib() - { - var folder = TestData.Get(@"TestData\ExampleExtension"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-ext", extensionPath, - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"test.wixlib") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbol = section.Symbols.OfType().Single(); - Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); - - var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single(); - Assert.Equal("Foo", example.Id?.Id); - Assert.Equal("Bar", example[0].AsString()); - } - } - - [Fact] - public void CanBuildWithExtensionUsingMultipleWixlibs() - { - var folder = TestData.Get(@"TestData\ComplexExampleExtension"); - var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "PackageComponents.wxs"), - "-ext", extensionPath, - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"components.wixlib") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "OtherComponents.wxs"), - "-ext", extensionPath, - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"other.wixlib") - }); - - result.AssertSuccess(); - - result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Package.wxs"), - "-loc", Path.Combine(folder, "Package.en-us.wxl"), - "-lib", Path.Combine(intermediateFolder, @"components.wixlib"), - "-lib", Path.Combine(intermediateFolder, @"other.wixlib"), - "-ext", extensionPath, - "-bindpath", Path.Combine(folder, "data"), - "-intermediateFolder", intermediateFolder, - "-o", Path.Combine(intermediateFolder, @"bin\test.msi") - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); - var section = intermediate.Sections.Single(); - - var fileSymbols = section.Symbols.OfType().OrderBy(t => Path.GetFileName(t.Source.Path)).ToArray(); - Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbols[0][FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"example.txt", fileSymbols[0][FileSymbolFields.Source].PreviousValue.AsPath().Path); - Assert.Equal(Path.Combine(folder, @"data\other.txt"), fileSymbols[1][FileSymbolFields.Source].AsPath().Path); - Assert.Equal(@"other.txt", fileSymbols[1][FileSymbolFields.Source].PreviousValue.AsPath().Path); - - var examples = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).ToArray(); - Assert.Equal(new string[] { "Foo", "Other" }, examples.Select(t => t.Id?.Id).ToArray()); - Assert.Equal(new[] { "Bar", "Value" }, examples.Select(t => t[0].AsString()).ToArray()); - } - } - } -} diff --git a/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs deleted file mode 100644 index 57351b27..00000000 --- a/src/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs +++ /dev/null @@ -1,81 +0,0 @@ -// 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 WixToolsetTest.CoreIntegration -{ - using System.IO; - using System.Linq; - using WixBuildTools.TestSupport; - using WixToolset.Core.TestPackage; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using Xunit; - - public class WixlibQueryFixture - { - [Fact] - public void UpgradeProducesReferenceToRemoveExistingProducts() - { - var folder = TestData.Get(@"TestData\Upgrade"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "DetectOnly.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath, - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(wixlibPath); - var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); - var wixSimpleRefSymbols = allSymbols.OfType(); - var repRef = wixSimpleRefSymbols.Where(t => t.Table == "WixAction" && - t.PrimaryKeys == "InstallExecuteSequence/RemoveExistingProducts") - .SingleOrDefault(); - Assert.NotNull(repRef); - } - } - - [Fact] - public void TypeLibLanguageAsStringReturnsZero() - { - var folder = TestData.Get(@"TestData\TypeLib"); - - using (var fs = new DisposableFileSystem()) - { - var baseFolder = fs.GetFolder(); - var intermediateFolder = Path.Combine(baseFolder, "obj"); - var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); - - var result = WixRunner.Execute(new[] - { - "build", - Path.Combine(folder, "Language0.wxs"), - "-intermediateFolder", intermediateFolder, - "-o", wixlibPath - }); - - result.AssertSuccess(); - - var intermediate = Intermediate.Load(wixlibPath); - var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); - var typeLibSymbol = allSymbols.OfType() - .SingleOrDefault(); - Assert.NotNull(typeLibSymbol); - - var fields = typeLibSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool - ? field.AsNullableNumber()?.ToString() - : field?.AsString()) - .ToList(); - Assert.Equal("0", fields[1]); - } - } - } -} diff --git a/src/version.json b/src/version.json new file mode 100644 index 00000000..5f857771 --- /dev/null +++ b/src/version.json @@ -0,0 +1,11 @@ +{ + "version": "4.0", + "publicReleaseRefSpec": [ + "^refs/heads/master$" + ], + "cloudBuild": { + "buildNumber": { + "enabled": true + } + } +} diff --git a/src/wix/Custom.Build.props b/src/wix/Custom.Build.props new file mode 100644 index 00000000..889fb62e --- /dev/null +++ b/src/wix/Custom.Build.props @@ -0,0 +1,6 @@ + + + + true + + diff --git a/src/wix/Directory.Build.props b/src/wix/Directory.Build.props new file mode 100644 index 00000000..b3c6287c --- /dev/null +++ b/src/wix/Directory.Build.props @@ -0,0 +1,27 @@ + + + + + + Debug + false + MSB3246 + + $(MSBuildProjectName) + $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\)) + $(BaseOutputPath)obj\$(ProjectName)\ + $(BaseOutputPath)$(Configuration)\ + + WiX Toolset Team + WiX Toolset + Copyright (c) .NET Foundation and contributors. All rights reserved. + MS-RL + WiX Toolset + + + + + diff --git a/src/wix/Directory.Build.targets b/src/wix/Directory.Build.targets new file mode 100644 index 00000000..2fcc765a --- /dev/null +++ b/src/wix/Directory.Build.targets @@ -0,0 +1,51 @@ + + + + + + + true + $(SolutionPath) + $(NCrunchOriginalSolutionPath) + + + + + + + $([System.IO.File]::ReadAllText($(TheSolutionPath))) + $([System.IO.Path]::GetDirectoryName( $(TheSolutionPath) )) + (?<="[PackageName]", ")(.*)(?=", ") + + + + + + %(Identity) + $(SolutionFileContent.Contains('\%(Identity).csproj')) + + + + + $(RegexPattern.Replace('[PackageName]','%(PackageName)') ) + $([System.Text.RegularExpressions.Regex]::Match('$(SolutionFileContent)', '%(Pattern)')) + + + + + + + + + + + + + + diff --git a/src/wix/Directory.csproj.props b/src/wix/Directory.csproj.props new file mode 100644 index 00000000..81d24ad1 --- /dev/null +++ b/src/wix/Directory.csproj.props @@ -0,0 +1,13 @@ + + + + + true + true + $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk)) + false + + diff --git a/src/wix/Directory.csproj.targets b/src/wix/Directory.csproj.targets new file mode 100644 index 00000000..c3270426 --- /dev/null +++ b/src/wix/Directory.csproj.targets @@ -0,0 +1,26 @@ + + + + + false + $(OutputPath)\$(AssemblyName).xml + + + + + $(PrivateRepositoryUrl.Replace('.git','')) + + $(MSBuildProjectName).nuspec + $(OutputPath)..\ + $(NuspecProperties);Id=$(PackageId);Authors=$(Authors);Copyright=$(Copyright);Description=$(Description);Title=$(Title) + $(NuspecProperties);Version=$(PackageVersion);RepositoryCommit=$(SourceRevisionId);RepositoryType=$(RepositoryType);RepositoryUrl=$(PrivateRepositoryUrl);ProjectFolder=$(MSBuildProjectDirectory)\;ProjectUrl=$(ProjectUrl) + true + snupkg + + + + diff --git a/src/wix/README.md b/src/wix/README.md new file mode 100644 index 00000000..622cd3f9 --- /dev/null +++ b/src/wix/README.md @@ -0,0 +1,3 @@ +# Core +WixToolset.Core - preprocessor, compiler, linker and binder for Windows Installer and Burn + diff --git a/src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs new file mode 100644 index 00000000..0da78797 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs @@ -0,0 +1,27 @@ +// 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.Burn +{ + using System; + using System.Xml; + using WixToolset.Data.Symbols; + + internal abstract class BaseSearchFacade : ISearchFacade + { + protected WixSearchSymbol SearchSymbol { get; set; } + + public virtual void WriteXml(XmlTextWriter writer) + { + writer.WriteAttributeString("Id", this.SearchSymbol.Id.Id); + writer.WriteAttributeString("Variable", this.SearchSymbol.Variable); + if (!String.IsNullOrEmpty(this.SearchSymbol.Condition)) + { + writer.WriteAttributeString("Condition", this.SearchSymbol.Condition); + } + if (!String.IsNullOrEmpty(this.SearchSymbol.BundleExtensionRef)) + { + writer.WriteAttributeString("ExtensionId", this.SearchSymbol.BundleExtensionRef); + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs new file mode 100644 index 00000000..4a4f06f3 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -0,0 +1,650 @@ +// 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.Burn +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Core.Burn.Bind; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.Interfaces; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Binds a this.bundle. + /// + internal class BindBundleCommand + { + public BindBundleCommand(IBindContext context, IEnumerable backedExtensions) + { + this.ServiceProvider = context.ServiceProvider; + + this.Messaging = context.ServiceProvider.GetService(); + + this.BackendHelper = context.ServiceProvider.GetService(); + this.InternalBurnBackendHelper = context.ServiceProvider.GetService(); + this.PayloadHarvester = context.ServiceProvider.GetService(); + + this.DefaultCompressionLevel = context.DefaultCompressionLevel; + this.DelayedFields = context.DelayedFields; + this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; + this.IntermediateFolder = context.IntermediateFolder; + this.Output = context.IntermediateRepresentation; + this.OutputPath = context.OutputPath; + this.OutputPdbPath = context.PdbPath; + //this.VariableResolver = context.VariableResolver; + + this.BackendExtensions = backedExtensions; + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } + + private IPayloadHarvester PayloadHarvester { get; } + + private CompressionLevel? DefaultCompressionLevel { get; } + + public IEnumerable DelayedFields { get; } + + public IEnumerable ExpectedEmbeddedFiles { get; } + + private IEnumerable BackendExtensions { get; } + + private Intermediate Output { get; } + + private string OutputPath { get; } + + private string OutputPdbPath { get; } + + private string IntermediateFolder { get; } + + private IVariableResolver VariableResolver { get; } + + public IReadOnlyCollection FileTransfers { get; private set; } + + public IReadOnlyCollection TrackedFiles { get; private set; } + + public WixOutput Wixout { get; private set; } + + public void Execute() + { + var section = this.Output.Sections.Single(); + + var fileTransfers = new List(); + var trackedFiles = new List(); + + // First look for data we expect to find... Chain, WixGroups, etc. + + // We shouldn't really get past the linker phase if there are + // no group items... that means that there's no UX, no Chain, + // *and* no Containers! + var chainPackageSymbols = this.GetRequiredSymbols(); + + var wixGroupSymbols = this.GetRequiredSymbols(); + + // Ensure there is one and only one WixBundleSymbol. + // The compiler and linker behavior should have colluded to get + // this behavior. + var bundleSymbol = this.GetSingleSymbol(); + + bundleSymbol.ProviderKey = bundleSymbol.BundleId = Guid.NewGuid().ToString("B").ToUpperInvariant(); + + bundleSymbol.Attributes |= WixBundleAttributes.PerMachine; // default to per-machine but the first-per user package wil flip the bundle per-user. + + // Ensure there is one and only one WixBootstrapperApplicationDllSymbol. + // The compiler and linker behavior should have colluded to get + // this behavior. + var bundleApplicationDllSymbol = this.GetSingleSymbol(); + + // Ensure there is one and only one WixChainSymbol. + // The compiler and linker behavior should have colluded to get + // this behavior. + var chainSymbol = this.GetSingleSymbol(); + + if (this.Messaging.EncounteredError) + { + return; + } + + // If there are any fields to resolve later, create the cache to populate during bind. + var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; + + IEnumerable orderedSearches; + IDictionary> extensionSearchSymbolsById; + { + var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section); + orderSearchesCommand.Execute(); + + orderedSearches = orderSearchesCommand.OrderedSearchFacades; + extensionSearchSymbolsById = orderSearchesCommand.ExtensionSearchSymbolsByExtensionId; + } + + // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). + { + var extractedFiles = this.BackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); + + trackedFiles.AddRange(extractedFiles); + } + + // Get the explicit payloads. + var payloadSymbols = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); + + var layoutDirectory = Path.GetDirectoryName(this.OutputPath); + + // Process the explicitly authored payloads. + ISet processedPayloads; + { + var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory); + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + + processedPayloads = new HashSet(payloadSymbols.Keys); + } + + IDictionary facades; + { + var command = new GetPackageFacadesCommand(this.Messaging, chainPackageSymbols, section); + command.Execute(); + + facades = command.PackageFacades; + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Process each package facade. Note this is likely to add payloads and other symbols so + // note that any indexes created above may be out of date now. + foreach (var facade in facades.Values) + { + switch (facade.PackageSymbol.Type) + { + case WixBundlePackageType.Exe: + { + var command = new ProcessExePackageCommand(facade, payloadSymbols); + command.Execute(); + } + break; + + case WixBundlePackageType.Msi: + { + var command = new ProcessMsiPackageCommand(this.ServiceProvider, this.BackendExtensions, section, facade, packagesPayloads[facade.PackageId]); + command.Execute(); + } + break; + + case WixBundlePackageType.Msp: + { + var command = new ProcessMspPackageCommand(this.Messaging, section, facade, payloadSymbols); + command.Execute(); + } + break; + + case WixBundlePackageType.Msu: + { + var command = new ProcessMsuPackageCommand(facade, payloadSymbols); + command.Execute(); + } + break; + } + + if (null != variableCache) + { + BindBundleCommand.PopulatePackageVariableCache(facade, variableCache); + } + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) + // are present. + payloadSymbols = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + wixGroupSymbols = this.GetRequiredSymbols(); + packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols); + + // Process the payloads that were added by processing the packages. + { + var toProcess = payloadSymbols.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList(); + + var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory); + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + + processedPayloads = null; + } + + // Set the package metadata from the payloads now that we have the complete payload information. + { + foreach (var facade in facades.Values) + { + facade.PackageSymbol.Size = 0; + + var packagePayloads = packagesPayloads[facade.PackageId]; + + foreach (var payload in packagePayloads.Values) + { + facade.PackageSymbol.Size += payload.FileSize.Value; + } + + if (!facade.PackageSymbol.InstallSize.HasValue) + { + facade.PackageSymbol.InstallSize = facade.PackageSymbol.Size; + } + + var packagePayload = payloadSymbols[facade.PackageSymbol.PayloadRef]; + + if (String.IsNullOrEmpty(facade.PackageSymbol.Description)) + { + facade.PackageSymbol.Description = packagePayload.Description; + } + + if (String.IsNullOrEmpty(facade.PackageSymbol.DisplayName)) + { + facade.PackageSymbol.DisplayName = packagePayload.DisplayName; + } + } + } + + // Give the UX payloads their embedded IDs... + var uxPayloadIndex = 0; + { + foreach (var payload in payloadSymbols.Values.Where(p => BurnConstants.BurnUXContainerName == p.ContainerRef)) + { + // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even + // downloaded. The current engine requires the UX to be fully present before any downloading starts, + // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads + // into the temporary UX directory correctly, so we don't allow external either. + if (PackagingType.Embedded != payload.Packaging) + { + this.Messaging.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.SourceFile.Path)); + payload.Packaging = PackagingType.Embedded; + } + + payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex); + ++uxPayloadIndex; + } + + if (0 == uxPayloadIndex) + { + // If we didn't get any UX payloads, it's an error! + throw new WixException(ErrorMessages.MissingBundleInformation("BootstrapperApplication")); + } + + // Give the embedded payloads without an embedded id yet an embedded id. + var payloadIndex = 0; + foreach (var payload in payloadSymbols.Values) + { + Debug.Assert(PackagingType.Unknown != payload.Packaging); + + if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) + { + payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAuthoredContainerEmbeddedIdFormat, payloadIndex); + ++payloadIndex; + } + } + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Determine patches to automatically slipstream. + { + var command = new AutomaticallySlipstreamPatchesCommand(section, facades.Values); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + IEnumerable orderedFacades; + IEnumerable boundaries; + { + var command = new OrderPackagesAndRollbackBoundariesCommand(this.Messaging, section, facades); + command.Execute(); + + orderedFacades = command.OrderedPackageFacades; + boundaries = command.UsedRollbackBoundaries; + } + + // Resolve any delayed fields before generating the manifest. + if (this.DelayedFields.Any()) + { + this.BackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); + } + + { + var command = new ProcessDependencyProvidersCommand(this.Messaging, section, facades); + command.Execute(); + + if (!String.IsNullOrEmpty(command.BundleProviderKey)) + { + bundleSymbol.ProviderKey = command.BundleProviderKey; // set the overridable bundle provider key. + } + } + + // Update the bundle per-machine/per-user scope based on the chained packages. + this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades); + + var softwareTags = section.Symbols.OfType().ToList(); + if (softwareTags.Any()) + { + var command = new ProcessBundleSoftwareTagsCommand(section, softwareTags); + command.Execute(); + } + + this.DetectDuplicateCacheIds(facades); + + if (this.Messaging.EncounteredError) + { + return; + } + + // Give the extension one last hook before generating the output files. + foreach (var extension in this.BackendExtensions) + { + extension.SymbolsFinalized(section); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Generate data for all manifests. + { + var command = new GenerateManifestDataFromIRCommand(this.Messaging, section, this.BackendExtensions, this.InternalBurnBackendHelper, extensionSearchSymbolsById); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Generate the core-defined BA manifest tables... + string baManifestPath; + { + var command = new CreateBootstrapperApplicationManifestCommand(section, bundleSymbol, orderedFacades, uxPayloadIndex, payloadSymbols, packagesPayloads, this.IntermediateFolder, this.InternalBurnBackendHelper); + command.Execute(); + + var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; + baManifestPath = command.OutputPath; + payloadSymbols.Add(baManifestPayload.Id.Id, baManifestPayload); + ++uxPayloadIndex; + + trackedFiles.Add(this.BackendHelper.TrackFile(baManifestPath, TrackedFileType.Temporary)); + } + + // Generate the bundle extension manifest... + string bextManifestPath; + { + var command = new CreateBundleExtensionManifestCommand(section, bundleSymbol, uxPayloadIndex, this.IntermediateFolder, this.InternalBurnBackendHelper); + command.Execute(); + + var bextManifestPayload = command.BundleExtensionManifestPayloadRow; + bextManifestPath = command.OutputPath; + payloadSymbols.Add(bextManifestPayload.Id.Id, bextManifestPayload); + ++uxPayloadIndex; + + trackedFiles.Add(this.BackendHelper.TrackFile(bextManifestPath, TrackedFileType.Temporary)); + } + + var containers = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + { + var command = new DetectPayloadCollisionsCommand(this.Messaging, containers, facades.Values, payloadSymbols, packagesPayloads); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Create all the containers except the UX container first so the manifest (that goes in the UX container) + // can contain all size and hash information about the non-UX containers. + WixBundleContainerSymbol uxContainer; + IEnumerable uxPayloads; + { + var command = new CreateNonUXContainers(this.BackendHelper, this.Messaging, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + + uxContainer = command.UXContainer; + uxPayloads = command.UXContainerPayloads; + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Resolve the download URLs now that we have all of the containers and payloads calculated. + { + var command = new ResolveDownloadUrlsCommand(this.Messaging, this.BackendExtensions, containers.Values, payloadSymbols); + command.Execute(); + } + + // Create the bundle manifest. + string manifestPath; + { + var executableName = Path.GetFileName(this.OutputPath); + + var command = new CreateBurnManifestCommand(executableName, section, bundleSymbol, containers.Values, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, packagesPayloads, orderedSearches, this.IntermediateFolder); + command.Execute(); + + manifestPath = command.OutputPath; + trackedFiles.Add(this.BackendHelper.TrackFile(manifestPath, TrackedFileType.Temporary)); + } + + // Create the UX container. + { + var command = new CreateContainerCommand(manifestPath, uxPayloads, uxContainer.WorkingPath, this.DefaultCompressionLevel); + command.Execute(); + + uxContainer.Hash = command.Hash; + uxContainer.Size = command.Size; + + trackedFiles.Add(this.BackendHelper.TrackFile(uxContainer.WorkingPath, TrackedFileType.Temporary)); + } + + { + var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers.Values); + command.Execute(); + + fileTransfers.Add(command.Transfer); + trackedFiles.Add(this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final)); + } + +#if TODO // does this need to come back, or do they only need to be in TrackedFiles? + this.ContentFilePaths = payloadSymbols.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); +#endif + this.FileTransfers = fileTransfers; + this.TrackedFiles = trackedFiles; + this.Wixout = this.CreateWixout(trackedFiles, this.Output, manifestPath, baManifestPath, bextManifestPath); + } + + private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, string manifestPath, string baDataPath, string bextDataPath) + { + WixOutput wixout; + + if (String.IsNullOrEmpty(this.OutputPdbPath)) + { + wixout = WixOutput.Create(); + } + else + { + var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); + trackedFiles.Add(trackPdb); + + wixout = WixOutput.Create(trackPdb.Path); + } + + intermediate.Save(wixout); + + wixout.ImportDataStream(BurnConstants.BurnManifestWixOutputStreamName, manifestPath); + wixout.ImportDataStream(BurnConstants.BootstrapperApplicationDataWixOutputStreamName, baDataPath); + wixout.ImportDataStream(BurnConstants.BundleExtensionDataWixOutputStreamName, bextDataPath); + + wixout.Reopen(); + + return wixout; + } + + /// + /// Populates the variable cache with specific package properties. + /// + /// The package facade with properties to cache. + /// The property cache. + private static void PopulatePackageVariableCache(PackageFacade facade, IDictionary variableCache) + { + var package = facade.PackageSymbol; + var id = package.Id.Id; + + variableCache.Add(String.Concat("packageDescription.", id), package.Description ?? String.Empty); + variableCache.Add(String.Concat("packageName.", id), package.DisplayName ?? String.Empty); + variableCache.Add(String.Concat("packageVersion.", id), package.Version); + + if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) + { + variableCache.Add(String.Concat("packageLanguage.", id), msiPackage.ProductLanguage.ToString()); + variableCache.Add(String.Concat("packageManufacturer.", id), msiPackage.Manufacturer ?? String.Empty); + } + else + { + variableCache.Add(String.Concat("packageLanguage.", id), String.Empty); + variableCache.Add(String.Concat("packageManufacturer.", id), String.Empty); + } + } + + private void ResolveBundleInstallScope(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable facades) + { + var dependencySymbolsById = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + + foreach (var facade in facades) + { + if (bundleSymbol.PerMachine && YesNoDefaultType.No == facade.PackageSymbol.PerMachine) + { + this.Messaging.Write(VerboseMessages.SwitchingToPerUserPackage(facade.PackageSymbol.SourceLineNumbers, facade.PackageId)); + + bundleSymbol.Attributes &= ~WixBundleAttributes.PerMachine; + break; + } + } + + foreach (var facade in facades) + { + // Update package scope from bundle scope if default. + if (YesNoDefaultType.Default == facade.PackageSymbol.PerMachine) + { + facade.PackageSymbol.PerMachine = bundleSymbol.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; + } + + // We will only register packages in the same scope as the bundle. Warn if any packages with providers + // are in a different scope and not permanent (permanents typically don't need a ref-count). + if (!bundleSymbol.PerMachine && + YesNoDefaultType.Yes == facade.PackageSymbol.PerMachine && + !facade.PackageSymbol.Permanent && + dependencySymbolsById.ContainsKey(facade.PackageId)) + { + this.Messaging.Write(WarningMessages.NoPerMachineDependencies(facade.PackageSymbol.SourceLineNumbers, facade.PackageId)); + } + } + } + + private void DetectDuplicateCacheIds(IDictionary facades) + { + var duplicateCacheIdDetector = new Dictionary(); + + foreach (var facade in facades.Values) + { + if (duplicateCacheIdDetector.TryGetValue(facade.PackageSymbol.CacheId, out var collisionPackage)) + { + this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.CacheId, facade.PackageId)); + this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds2(collisionPackage.SourceLineNumbers)); + } + else + { + duplicateCacheIdDetector.Add(facade.PackageSymbol.CacheId, facade.PackageSymbol); + } + } + } + + private IEnumerable GetRequiredSymbols() where T : IntermediateSymbol + { + var symbols = this.Output.Sections.Single().Symbols.OfType().ToList(); + + if (0 == symbols.Count) + { + throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); + } + + return symbols; + } + + private T GetSingleSymbol() where T : IntermediateSymbol + { + var symbols = this.Output.Sections.Single().Symbols.OfType().ToList(); + + if (1 != symbols.Count) + { + throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); + } + + return symbols[0]; + } + + private static Dictionary> RecalculatePackagesPayloads(Dictionary payloadSymbols, IEnumerable wixGroupSymbols) + { + var packagesPayloads = new Dictionary>(); + + foreach (var groupSymbol in wixGroupSymbols) + { + if (ComplexReferenceChildType.Payload == groupSymbol.ChildType) + { + var payloadSymbol = payloadSymbols[groupSymbol.ChildId]; + + if (ComplexReferenceParentType.Package == groupSymbol.ParentType) + { + if (!packagesPayloads.TryGetValue(groupSymbol.ParentId, out var packagePayloadsById)) + { + packagePayloadsById = new Dictionary(); + packagesPayloads.Add(groupSymbol.ParentId, packagePayloadsById); + } + + packagePayloadsById.Add(payloadSymbol.Id.Id, payloadSymbol); + } + } + } + + return packagesPayloads; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs new file mode 100644 index 00000000..773250d7 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs @@ -0,0 +1,24 @@ +// 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.Burn +{ + using System.Xml; + using WixToolset.Data.Symbols; + + internal class ExtensionSearchFacade : BaseSearchFacade + { + public ExtensionSearchFacade(WixSearchSymbol searchSymbol) + { + this.SearchSymbol = searchSymbol; + } + + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("ExtensionSearch"); + + base.WriteXml(writer); + + writer.WriteEndElement(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs new file mode 100644 index 00000000..a76f84ec --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs @@ -0,0 +1,237 @@ +// 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.Burn.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.ExtensibilityServices; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class GenerateManifestDataFromIRCommand + { + public GenerateManifestDataFromIRCommand(IMessaging messaging, IntermediateSection section, IEnumerable backendExtensions, IBurnBackendHelper backendHelper, IDictionary> extensionSearchSymbolsById) + { + this.Messaging = messaging; + this.Section = section; + this.BackendExtensions = backendExtensions; + this.BackendHelper = backendHelper; + this.ExtensionSearchSymbolsById = extensionSearchSymbolsById; + } + + private IEnumerable BackendExtensions { get; } + + private IBurnBackendHelper BackendHelper { get; } + + private IDictionary> ExtensionSearchSymbolsById { get; } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public void Execute() + { + var symbols = this.Section.Symbols.ToList(); + var cellsByCustomDataAndElementId = new Dictionary>(); + var customDataById = new Dictionary(); + + foreach (var kvp in this.ExtensionSearchSymbolsById) + { + var extensionId = kvp.Key; + var extensionSearchSymbols = kvp.Value; + foreach (var extensionSearchSymbol in extensionSearchSymbols) + { + this.BackendHelper.AddBundleExtensionData(extensionId, extensionSearchSymbol, symbolIdIsIdAttribute: true); + symbols.Remove(extensionSearchSymbol); + } + } + + foreach (var symbol in symbols) + { + var unknownSymbol = false; + switch (symbol.Definition.Type) + { + // Symbols used internally and are not added to a data manifest. + case SymbolDefinitionType.ProvidesDependency: + case SymbolDefinitionType.WixApprovedExeForElevation: + case SymbolDefinitionType.WixBootstrapperApplication: + case SymbolDefinitionType.WixBootstrapperApplicationDll: + case SymbolDefinitionType.WixBundle: + case SymbolDefinitionType.WixBundleContainer: + case SymbolDefinitionType.WixBundleCustomDataAttribute: + case SymbolDefinitionType.WixBundleExePackage: + case SymbolDefinitionType.WixBundleExePackagePayload: + case SymbolDefinitionType.WixBundleExtension: + case SymbolDefinitionType.WixBundleMsiFeature: + case SymbolDefinitionType.WixBundleMsiPackage: + case SymbolDefinitionType.WixBundleMsiPackagePayload: + case SymbolDefinitionType.WixBundleMsiProperty: + case SymbolDefinitionType.WixBundleMspPackage: + case SymbolDefinitionType.WixBundleMspPackagePayload: + case SymbolDefinitionType.WixBundleMsuPackage: + case SymbolDefinitionType.WixBundleMsuPackagePayload: + case SymbolDefinitionType.WixBundlePackage: + case SymbolDefinitionType.WixBundlePackageCommandLine: + case SymbolDefinitionType.WixBundlePackageExitCode: + case SymbolDefinitionType.WixBundlePackageGroup: + case SymbolDefinitionType.WixBundlePatchTargetCode: + case SymbolDefinitionType.WixBundlePayload: + case SymbolDefinitionType.WixBundlePayloadGroup: + case SymbolDefinitionType.WixBundleRelatedPackage: + case SymbolDefinitionType.WixBundleRollbackBoundary: + case SymbolDefinitionType.WixBundleSlipstreamMsp: + case SymbolDefinitionType.WixBundleTag: + case SymbolDefinitionType.WixBundleUpdate: + case SymbolDefinitionType.WixBundleVariable: + case SymbolDefinitionType.WixBuildInfo: + case SymbolDefinitionType.WixChain: + case SymbolDefinitionType.WixComponentSearch: + case SymbolDefinitionType.WixDependencyProvider: + case SymbolDefinitionType.WixFileSearch: + case SymbolDefinitionType.WixGroup: + case SymbolDefinitionType.WixProductSearch: + case SymbolDefinitionType.WixRegistrySearch: + case SymbolDefinitionType.WixRelatedBundle: + case SymbolDefinitionType.WixSearch: + case SymbolDefinitionType.WixSearchRelation: + case SymbolDefinitionType.WixSetVariable: + case SymbolDefinitionType.WixUpdateRegistration: + break; + + // Symbols used before binding. + case SymbolDefinitionType.WixComplexReference: + case SymbolDefinitionType.WixOrdering: + case SymbolDefinitionType.WixSimpleReference: + case SymbolDefinitionType.WixVariable: + break; + + // Symbols to investigate: + case SymbolDefinitionType.WixChainItem: + break; + + case SymbolDefinitionType.WixBundleCustomData: + unknownSymbol = !this.IndexBundleCustomDataSymbol((WixBundleCustomDataSymbol)symbol, customDataById); + break; + + case SymbolDefinitionType.WixBundleCustomDataCell: + this.IndexBundleCustomDataCellSymbol((WixBundleCustomDataCellSymbol)symbol, cellsByCustomDataAndElementId); + break; + + case SymbolDefinitionType.MustBeFromAnExtension: + unknownSymbol = !this.AddSymbolFromExtension(symbol); + break; + + default: + unknownSymbol = true; + break; + } + + if (unknownSymbol) + { + this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol)); + } + } + + this.AddIndexedCellSymbols(customDataById, cellsByCustomDataAndElementId); + } + + private bool IndexBundleCustomDataSymbol(WixBundleCustomDataSymbol wixBundleCustomDataSymbol, Dictionary customDataById) + { + switch (wixBundleCustomDataSymbol.Type) + { + case WixBundleCustomDataType.BootstrapperApplication: + case WixBundleCustomDataType.BundleExtension: + break; + default: + return false; + } + + var customDataId = wixBundleCustomDataSymbol.Id.Id; + customDataById.Add(customDataId, wixBundleCustomDataSymbol); + return true; + } + + private void IndexBundleCustomDataCellSymbol(WixBundleCustomDataCellSymbol wixBundleCustomDataCellSymbol, Dictionary> cellsByCustomDataAndElementId) + { + var tableAndRowId = wixBundleCustomDataCellSymbol.CustomDataRef + "/" + wixBundleCustomDataCellSymbol.ElementId; + if (!cellsByCustomDataAndElementId.TryGetValue(tableAndRowId, out var cells)) + { + cells = new List(); + cellsByCustomDataAndElementId.Add(tableAndRowId, cells); + } + + cells.Add(wixBundleCustomDataCellSymbol); + } + + private void AddIndexedCellSymbols(Dictionary customDataById, Dictionary> cellsByCustomDataAndElementId) + { + foreach (var elementValues in cellsByCustomDataAndElementId.Values) + { + var elementName = elementValues[0].CustomDataRef; + var customDataSymbol = customDataById[elementName]; + + var attributeNames = customDataSymbol.AttributeNamesSeparated; + + var elementValuesByAttribute = elementValues.ToDictionary(t => t.AttributeRef, t => t.Value); + + var sb = new StringBuilder(); + using (var writer = XmlWriter.Create(sb, BurnBackendHelper.WriterSettings)) + { + switch (customDataSymbol.Type) + { + case WixBundleCustomDataType.BootstrapperApplication: + writer.WriteStartElement(elementName, BurnCommon.BADataNamespace); + break; + case WixBundleCustomDataType.BundleExtension: + writer.WriteStartElement(elementName, BurnCommon.BundleExtensionDataNamespace); + break; + default: + throw new NotImplementedException(); + } + + // Write all row data as attributes in table column order. + foreach (var attributeName in attributeNames) + { + if (elementValuesByAttribute.TryGetValue(attributeName, out var value)) + { + writer.WriteAttributeString(attributeName, value); + } + } + + writer.WriteEndElement(); + } + + switch (customDataSymbol.Type) + { + case WixBundleCustomDataType.BootstrapperApplication: + this.BackendHelper.AddBootstrapperApplicationData(sb.ToString()); + break; + case WixBundleCustomDataType.BundleExtension: + this.BackendHelper.AddBundleExtensionData(customDataSymbol.BundleExtensionRef, sb.ToString()); + break; + default: + throw new NotImplementedException(); + } + } + } + + private bool AddSymbolFromExtension(IntermediateSymbol symbol) + { + foreach (var extension in this.BackendExtensions) + { + if (extension.TryProcessSymbol(this.Section, symbol)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs new file mode 100644 index 00000000..24d6f542 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs @@ -0,0 +1,185 @@ +// 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.Burn +{ + using System; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class LegacySearchFacade : BaseSearchFacade + { + public LegacySearchFacade(WixSearchSymbol searchSymbol, IntermediateSymbol searchSpecificSymbol) + { + this.SearchSymbol = searchSymbol; + this.SearchSpecificSymbol = searchSpecificSymbol; + } + + public IntermediateSymbol SearchSpecificSymbol { get; } + + /// + /// Generates Burn manifest and ParameterInfo-style markup a search. + /// + /// + public override void WriteXml(XmlTextWriter writer) + { + switch (this.SearchSpecificSymbol) + { + case WixComponentSearchSymbol symbol: + this.WriteComponentSearchXml(writer, symbol); + break; + case WixFileSearchSymbol symbol: + this.WriteFileSearchXml(writer, symbol); + break; + case WixProductSearchSymbol symbol: + this.WriteProductSearchXml(writer, symbol); + break; + case WixRegistrySearchSymbol symbol: + this.WriteRegistrySearchXml(writer, symbol); + break; + } + } + + private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchSymbol searchSymbol) + { + writer.WriteStartElement("MsiComponentSearch"); + + base.WriteXml(writer); + + writer.WriteAttributeString("ComponentId", searchSymbol.Guid); + + if (!String.IsNullOrEmpty(searchSymbol.ProductCode)) + { + writer.WriteAttributeString("ProductCode", searchSymbol.ProductCode); + } + + if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.KeyPath)) + { + writer.WriteAttributeString("Type", "keyPath"); + } + else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.WantDirectory)) + { + writer.WriteAttributeString("Type", "directory"); + } + + writer.WriteEndElement(); + } + + private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchSymbol searchSymbol) + { + writer.WriteStartElement((0 == (searchSymbol.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); + + base.WriteXml(writer); + + writer.WriteAttributeString("Path", searchSymbol.Path); + if (WixFileSearchAttributes.WantExists == (searchSymbol.Attributes & WixFileSearchAttributes.WantExists)) + { + writer.WriteAttributeString("Type", "exists"); + } + else if (WixFileSearchAttributes.WantVersion == (searchSymbol.Attributes & WixFileSearchAttributes.WantVersion)) + { + // Can never get here for DirectorySearch. + writer.WriteAttributeString("Type", "version"); + } + else + { + writer.WriteAttributeString("Type", "path"); + } + writer.WriteEndElement(); + } + + private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchSymbol symbol) + { + writer.WriteStartElement("MsiProductSearch"); + + base.WriteXml(writer); + + if (0 != (symbol.Attributes & WixProductSearchAttributes.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", symbol.Guid); + } + else + { + writer.WriteAttributeString("ProductCode", symbol.Guid); + } + + if (0 != (symbol.Attributes & WixProductSearchAttributes.Version)) + { + writer.WriteAttributeString("Type", "version"); + } + else if (0 != (symbol.Attributes & WixProductSearchAttributes.Language)) + { + writer.WriteAttributeString("Type", "language"); + } + else if (0 != (symbol.Attributes & WixProductSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (symbol.Attributes & WixProductSearchAttributes.Assignment)) + { + writer.WriteAttributeString("Type", "assignment"); + } + + writer.WriteEndElement(); + } + + private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchSymbol symbol) + { + writer.WriteStartElement("RegistrySearch"); + + base.WriteXml(writer); + + switch (symbol.Root) + { + case RegistryRootType.ClassesRoot: + writer.WriteAttributeString("Root", "HKCR"); + break; + case RegistryRootType.CurrentUser: + writer.WriteAttributeString("Root", "HKCU"); + break; + case RegistryRootType.LocalMachine: + writer.WriteAttributeString("Root", "HKLM"); + break; + case RegistryRootType.Users: + writer.WriteAttributeString("Root", "HKU"); + break; + } + + writer.WriteAttributeString("Key", symbol.Key); + + if (!String.IsNullOrEmpty(symbol.Value)) + { + writer.WriteAttributeString("Value", symbol.Value); + } + + var existenceOnly = 0 != (symbol.Attributes & WixRegistrySearchAttributes.WantExists); + + writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); + + if (0 != (symbol.Attributes & WixRegistrySearchAttributes.Win64)) + { + writer.WriteAttributeString("Win64", "yes"); + } + + if (!existenceOnly) + { + if (0 != (symbol.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) + { + writer.WriteAttributeString("ExpandEnvironment", "yes"); + } + + // We *always* say this is VariableType="string". If we end up + // needing to be more specific, we will have to expand the "Format" + // attribute to allow "number" and "version". + + writer.WriteAttributeString("VariableType", "string"); + } + + writer.WriteEndElement(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs new file mode 100644 index 00000000..f9ff23cb --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs @@ -0,0 +1,133 @@ +// 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.Burn.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class ProcessBundleSoftwareTagsCommand + { + public ProcessBundleSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags) + { + this.Section = section; + this.SoftwareTags = softwareTags; + } + + private IntermediateSection Section { get; } + + private IEnumerable SoftwareTags { get; } + + public void Execute() + { + var bundleInfo = this.Section.Symbols.OfType().FirstOrDefault(); + var bundleId = NormalizeGuid(bundleInfo.BundleId); + var upgradeCode = NormalizeGuid(bundleInfo.UpgradeCode); + + var uniqueId = String.Concat("wix:bundle/", bundleId); + var persistentId = String.Concat("wix:bundle.upgrade/", upgradeCode); + + // Try to collect all the software id tags from all the child packages. + var containedTags = CollectPackageTags(this.Section); + + foreach (var bundleTag in this.SoftwareTags) + { + using (var ms = new MemoryStream()) + { + CreateTagFile(ms, uniqueId, bundleInfo.Name, bundleInfo.Version, bundleTag.Regid, bundleInfo.Manufacturer, persistentId, containedTags); + bundleTag.Xml = Encoding.UTF8.GetString(ms.ToArray()); + } + } + } + + private static string NormalizeGuid(string guidString) + { + if (Guid.TryParse(guidString, out var guid)) + { + return guid.ToString("D").ToUpperInvariant(); + } + + return guidString; + } + + private static IEnumerable CollectPackageTags(IntermediateSection section) + { + var tags = new List(); + + var msiPackages = section.Symbols.OfType().Where(s => s.Type == WixBundlePackageType.Msi).ToList(); + if (msiPackages.Any()) + { + var payloadSymbolsById = section.Symbols.OfType().ToDictionary(s => s.Id.Id); + + foreach (var msiPackage in msiPackages) + { + var payload = payloadSymbolsById[msiPackage.PayloadRef]; + + using (var db = new Database(payload.SourceFile.Path, OpenDatabase.ReadOnly)) + { + using (var view = db.OpenExecuteView("SELECT `Regid`, `TagId` FROM `SoftwareIdentificationTag`")) + { + foreach (var record in view.Records) + { + tags.Add(new SoftwareTag { Regid = record.GetString(1), Id = record.GetString(2) }); + } + } + } + } + } + + return tags; + } + + private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId, IEnumerable containedTags) + { + var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; + + using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) + { + writer.WriteStartDocument(); + writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); + writer.WriteAttributeString("tagId", uniqueId); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("version", version); + writer.WriteAttributeString("versionScheme", versionScheme); + + writer.WriteStartElement("Entity"); + writer.WriteAttributeString("name", manufacturer); + writer.WriteAttributeString("regid", regid); + writer.WriteAttributeString("role", "softwareCreator tagCreator"); + writer.WriteEndElement(); // + + if (!String.IsNullOrEmpty(persistendId)) + { + writer.WriteStartElement("Meta"); + writer.WriteAttributeString("persistentId", persistendId); + writer.WriteEndElement(); // + } + + foreach (var containedTag in containedTags) + { + writer.WriteStartElement("Link"); + writer.WriteAttributeString("rel", "component"); + writer.WriteAttributeString("href", String.Concat("swid:", containedTag.Id)); + writer.WriteEndElement(); // + } + + writer.WriteEndElement(); // + } + } + + private class SoftwareTag + { + public string Regid { get; set; } + + public string Id { get; set; } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs new file mode 100644 index 00000000..99effbc7 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs @@ -0,0 +1,147 @@ +// 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.Burn.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Extensibility.Services; + using WixToolset.Data.Symbols; + + internal class ProcessDependencyProvidersCommand + { + public ProcessDependencyProvidersCommand(IMessaging messaging, IntermediateSection section, IDictionary facades) + { + this.Messaging = messaging; + this.Section = section; + + this.Facades = facades; + } + + public string BundleProviderKey { get; private set; } + + public Dictionary DependencySymbolsByKey { get; private set; } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private IDictionary Facades { get; } + + /// + /// Sets the explicitly provided bundle provider key, if provided. And... + /// Imports authored dependency providers for each package in the manifest, + /// and generates dependency providers for certain package types that do not + /// have a provider defined. + /// + public void Execute() + { + var dependencySymbols = this.Section.Symbols.OfType(); + + foreach (var dependency in dependencySymbols) + { + // Sets the provider key for the bundle, if it is not set already. + if (String.IsNullOrEmpty(this.BundleProviderKey)) + { + if (dependency.Bundle) + { + this.BundleProviderKey = dependency.ProviderKey; + } + } + + // Import any authored dependencies. These may merge with imported provides from MSI packages. + var packageId = dependency.ParentRef; + + if (this.Facades.TryGetValue(packageId, out var facade)) + { + if (String.IsNullOrEmpty(dependency.ProviderKey)) + { + switch (facade.SpecificPackageSymbol) + { + // The WixDependencyExtension allows an empty Key for MSIs and MSPs. + case WixBundleMsiPackageSymbol msiPackage: + dependency.ProviderKey = msiPackage.ProductCode; + break; + case WixBundleMspPackageSymbol mspPackage: + dependency.ProviderKey = mspPackage.PatchCode; + break; + } + } + + if (String.IsNullOrEmpty(dependency.Version)) + { + dependency.Version = facade.PackageSymbol.Version; + } + + // If the version is still missing, a version could not be gathered from the package and was not authored. + if (String.IsNullOrEmpty(dependency.Version)) + { + this.Messaging.Write(ErrorMessages.MissingDependencyVersion(facade.PackageId)); + } + + if (String.IsNullOrEmpty(dependency.DisplayName)) + { + dependency.DisplayName = facade.PackageSymbol.DisplayName; + } + } + } + + this.DependencySymbolsByKey = this.GetDependencySymbolsByKey(dependencySymbols); + + // Generate providers for MSI and MSP packages that still do not have providers. + foreach (var facade in this.Facades.Values) + { + string key = null; + + if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) + { + key = msiPackage.ProductCode; + } + else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) + { + key = mspPackage.PatchCode; + } + + if (!String.IsNullOrEmpty(key) && !this.DependencySymbolsByKey.ContainsKey(key)) + { + var dependency = this.Section.AddSymbol(new WixDependencyProviderSymbol(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.Id) + { + ParentRef = facade.PackageId, + ProviderKey = key, + Version = facade.PackageSymbol.Version, + DisplayName = facade.PackageSymbol.DisplayName + }); + + this.DependencySymbolsByKey.Add(dependency.ProviderKey, dependency); + } + } + } + + private Dictionary GetDependencySymbolsByKey(IEnumerable dependencySymbols) + { + var dependencySymbolsByKey = new Dictionary(); + + foreach (var dependency in dependencySymbols) + { + if (dependencySymbolsByKey.TryGetValue(dependency.ProviderKey, out var collision)) + { + // If not a perfect dependency collision, display an error. + if (dependency.ProviderKey != collision.ProviderKey || + dependency.Version != collision.Version || + dependency.DisplayName != collision.DisplayName) + { + this.Messaging.Write(ErrorMessages.DuplicateProviderDependencyKey(dependency.ProviderKey, dependency.ParentRef)); + } + } + else + { + dependencySymbolsByKey.Add(dependency.ProviderKey, dependency); + } + } + + return dependencySymbolsByKey; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs new file mode 100644 index 00000000..c678b114 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs @@ -0,0 +1,128 @@ +// 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.Burn.Bind +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class ResolveDownloadUrlsCommand + { + public ResolveDownloadUrlsCommand(IMessaging messaging, IEnumerable backendExtensions, IEnumerable containers, Dictionary payloadsById) + { + this.Messaging = messaging; + this.BackendExtensions = backendExtensions; + this.Containers = containers; + this.PayloadsById = payloadsById; + } + + private IMessaging Messaging { get; } + + private IEnumerable BackendExtensions { get; } + + private IEnumerable Containers { get; } + + private Dictionary PayloadsById { get; } + + public void Execute() + { + this.ResolveContainerUrls(); + + this.ResolvePayloadUrls(); + } + + private void ResolveContainerUrls() + { + foreach (var container in this.Containers) + { + if (container.Type == ContainerType.Detached) + { + var resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id.Id, container.Name); + if (!String.IsNullOrEmpty(resolvedUrl)) + { + container.DownloadUrl = resolvedUrl; + } + } + else if (container.Type == ContainerType.Attached) + { + if (!String.IsNullOrEmpty(container.DownloadUrl)) + { + this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id.Id)); + } + } + } + } + + private void ResolvePayloadUrls() + { + foreach (var payload in this.PayloadsById.Values) + { + if (payload.Packaging == PackagingType.Embedded && payload.ContainerRef == BurnConstants.BurnUXContainerName) + { + if (!String.IsNullOrEmpty(payload.DownloadUrl)) + { + this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(payload.SourceLineNumbers, payload.Id.Id)); + } + } + else + { + var packageId = payload.ParentPackagePayloadRef; + var parentUrl = payload.ParentPackagePayloadRef == null ? null : this.PayloadsById[payload.ParentPackagePayloadRef].DownloadUrl; + var resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id.Id, payload.Name); + if (!String.IsNullOrEmpty(resolvedUrl)) + { + payload.DownloadUrl = resolvedUrl; + } + } + } + } + + private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName) + { + string resolvedUrl = null; + + foreach (var extension in this.BackendExtensions) + { + resolvedUrl = extension.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName); + if (!String.IsNullOrEmpty(resolvedUrl)) + { + break; + } + } + + if (String.IsNullOrEmpty(resolvedUrl)) + { + // If a URL was not specified but there is a fallback URL that has a format specifier in it + // then use the fallback URL formatter for this URL. + if (String.IsNullOrEmpty(url) && !String.IsNullOrEmpty(fallbackUrl)) + { + var formattedFallbackUrl = String.Format(fallbackUrl, packageId, payloadId, fileName); + if (!String.Equals(fallbackUrl, formattedFallbackUrl, StringComparison.OrdinalIgnoreCase)) + { + url = fallbackUrl; + } + } + + if (!String.IsNullOrEmpty(url)) + { + var formattedUrl = String.Format(url, packageId, payloadId, fileName); + + if (Uri.TryCreate(formattedUrl, UriKind.Absolute, out var canonicalUri)) + { + resolvedUrl = canonicalUri.AbsoluteUri; + } + else + { + resolvedUrl = null; + } + } + } + + return resolvedUrl; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs b/src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs new file mode 100644 index 00000000..e88f26ef --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs @@ -0,0 +1,48 @@ +// 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.Burn +{ + using System.Xml; + using WixToolset.Data.Symbols; + + internal class SetVariableSearchFacade : BaseSearchFacade + { + public SetVariableSearchFacade(WixSearchSymbol searchSymbol, WixSetVariableSymbol setVariableSymbol) + { + this.SearchSymbol = searchSymbol; + this.SetVariableSymbol = setVariableSymbol; + } + + private WixSetVariableSymbol SetVariableSymbol { get; } + + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("SetVariable"); + + base.WriteXml(writer); + + if (this.SetVariableSymbol.Type != WixBundleVariableType.Unknown) + { + writer.WriteAttributeString("Value", this.SetVariableSymbol.Value); + + switch (this.SetVariableSymbol.Type) + { + case WixBundleVariableType.Formatted: + writer.WriteAttributeString("Type", "formatted"); + break; + case WixBundleVariableType.Numeric: + writer.WriteAttributeString("Type", "numeric"); + break; + case WixBundleVariableType.String: + writer.WriteAttributeString("Type", "string"); + break; + case WixBundleVariableType.Version: + writer.WriteAttributeString("Type", "version"); + break; + } + } + + writer.WriteEndElement(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/BundleBackend.cs b/src/wix/WixToolset.Core.Burn/BundleBackend.cs new file mode 100644 index 00000000..60e9ea60 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BundleBackend.cs @@ -0,0 +1,77 @@ +// 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.Burn +{ + using System; + using System.IO; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.Inscribe; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class BundleBackend : IBackend + { + public IBindResult Bind(IBindContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + + var command = new BindBundleCommand(context, backendExtensions); + command.Execute(); + + var result = context.ServiceProvider.GetService(); + result.FileTransfers = command.FileTransfers; + result.TrackedFiles = command.TrackedFiles; + result.Wixout = command.Wixout; + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } + + return result; + } + + public IDecompileResult Decompile(IDecompileContext context) + { + throw new NotImplementedException(); + } + + public bool Inscribe(IInscribeContext context) + { + if (String.IsNullOrEmpty(context.SignedEngineFile)) + { + var command = new InscribeBundleCommand(context); + return command.Execute(); + } + else + { + var command = new InscribeBundleEngineCommand(context); + return command.Execute(); + } + } + + public Intermediate Unbind(IUnbindContext context) + { + var uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); + var acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); + var messaging = context.ServiceProvider.GetService(); + + using (var reader = BurnReader.Open(messaging, context.InputFilePath)) + { + reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder); + reader.ExtractAttachedContainer(acExtractPath, context.IntermediateFolder); + } + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs new file mode 100644 index 00000000..75c60e56 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs @@ -0,0 +1,117 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class AutomaticallySlipstreamPatchesCommand + { + public AutomaticallySlipstreamPatchesCommand(IntermediateSection section, ICollection packageFacades) + { + this.Section = section; + this.PackageFacades = packageFacades; + } + + private IntermediateSection Section { get; } + + private IEnumerable PackageFacades { get; } + + public void Execute() + { + var msiPackages = new List(); + var targetsProductCode = new Dictionary>(); + var targetsUpgradeCode = new Dictionary>(); + + foreach (var facade in this.PackageFacades) + { + // Keep track of all MSI packages. + if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) + { + msiPackages.Add(msiPackage); + } + else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage && mspPackage.Slipstream) + { + var patchTargetCodeSymbols = this.Section.Symbols + .OfType() + .Where(r => r.PackageRef == facade.PackageId); + + // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. + foreach (var symbol in patchTargetCodeSymbols) + { + if (symbol.TargetsProductCode) + { + if (!targetsProductCode.TryGetValue(symbol.TargetCode, out var symbols)) + { + symbols = new List(); + targetsProductCode.Add(symbol.TargetCode, symbols); + } + + symbols.Add(symbol); + } + else if (symbol.TargetsUpgradeCode) + { + if (!targetsUpgradeCode.TryGetValue(symbol.TargetCode, out var symbols)) + { + symbols = new List(); + targetsUpgradeCode.Add(symbol.TargetCode, symbols); + } + } + } + } + } + + var slipstreamMspIds = new HashSet(); + + // Loop through the MSI and slipstream patches targeting it. + foreach (var msi in msiPackages) + { + if (targetsProductCode.TryGetValue(msi.ProductCode, out var symbols)) + { + foreach (var symbol in symbols) + { + Debug.Assert(symbol.TargetsProductCode); + Debug.Assert(!symbol.TargetsUpgradeCode); + + this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol); + } + } + + if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out symbols)) + { + foreach (var symbol in symbols) + { + Debug.Assert(!symbol.TargetsProductCode); + Debug.Assert(symbol.TargetsUpgradeCode); + + this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol); + } + + symbols = null; + } + } + } + + private bool TryAddSlipstreamSymbol(HashSet slipstreamMspIds, WixBundleMsiPackageSymbol msiPackage, WixBundlePatchTargetCodeSymbol patchTargetCode) + { + var id = new Identifier(AccessModifier.Section, msiPackage.Id.Id, patchTargetCode.PackageRef); + + if (slipstreamMspIds.Add(id.Id)) + { + this.Section.AddSymbol(new WixBundleSlipstreamMspSymbol(patchTargetCode.SourceLineNumbers) + { + TargetPackageRef = msiPackage.Id.Id, + MspPackageRef = patchTargetCode.PackageRef, + }); + + return true; + } + + return false; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs b/src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs new file mode 100644 index 00000000..3b4a4156 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs @@ -0,0 +1,30 @@ +// 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.Burn.Bundles +{ + using System.IO; + using System.Security.Cryptography; + using System.Text; + + internal static class BundleHashAlgorithm + { + public static string Hash(FileInfo fileInfo) + { + byte[] hashBytes; + + using (var managed = new SHA512CryptoServiceProvider()) + using (var stream = fileInfo.OpenRead()) + { + hashBytes = managed.ComputeHash(stream); + } + + var sb = new StringBuilder(hashBytes.Length * 2); + for (var i = 0; i < hashBytes.Length; i++) + { + sb.AppendFormat("{0:X2}", hashBytes[i]); + } + + return sb.ToString(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs new file mode 100644 index 00000000..1eb3563a --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs @@ -0,0 +1,385 @@ +// 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.Burn.Bundles +{ + using System; + using System.Diagnostics; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + /// + /// Common functionality for Burn PE Writer & Reader for the WiX toolset. + /// + /// This class encapsulates common functionality related to + /// bundled/chained setup packages. + /// + /// + internal abstract class BurnCommon : IDisposable + { + public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; + public const string BurnUXContainerEmbeddedIdFormat = "u{0}"; + public const string BurnAuthoredContainerEmbeddedIdFormat = "a{0}"; + + public const string BADataFileName = "BootstrapperApplicationData.xml"; + public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; + + public const string BundleExtensionDataFileName = "BundleExtensionData.xml"; + public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData"; + + // See WinNT.h for details about the PE format, including the + // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, + // IMAGE_FILE_HEADER, etc. + protected const UInt32 IMAGE_DOS_HEADER_SIZE = 64; + protected const UInt32 IMAGE_DOS_HEADER_OFFSET_MAGIC = 0; + protected const UInt32 IMAGE_DOS_HEADER_OFFSET_NTHEADER = 60; + + protected const UInt32 IMAGE_NT_HEADER_SIZE = 24; // signature DWORD (4) + IMAGE_FILE_HEADER (20) + protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIGNATURE = 0; + protected const UInt32 IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS = 6; + protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER = 20; + + protected const UInt32 IMAGE_OPTIONAL_OFFSET_CHECKSUM = 4 * 16; // checksum is 16 DWORDs into IMAGE_OPTIONAL_HEADER which is right after the IMAGE_NT_HEADER. + protected const UInt32 IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE = (IMAGE_DATA_DIRECTORY_SIZE * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY)); + + protected const UInt32 IMAGE_SECTION_HEADER_SIZE = 40; + protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_NAME = 0; + protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_VIRTUALSIZE = 8; + protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA = 16; + protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA = 20; + + protected const UInt32 IMAGE_DATA_DIRECTORY_SIZE = 8; // struct of two DWORDs. + protected const UInt32 IMAGE_DIRECTORY_ENTRY_SECURITY = 4; + protected const UInt32 IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; + + protected const UInt16 IMAGE_DOS_SIGNATURE = 0x5A4D; + protected const UInt32 IMAGE_NT_SIGNATURE = 0x00004550; + protected const UInt64 IMAGE_SECTION_WIXBURN_NAME = 0x6E7275627869772E; // ".wixburn", as a qword. + + // The ".wixburn" section contains: + // 0- 3: magic number + // 4- 7: version + // 8-23: bundle GUID + // 24-27: engine (stub) size + // 28-31: original checksum + // 32-35: original signature offset + // 36-39: original signature size + // 40-43: container type (1 = CAB) + // 44-47: container count + // 48-51: byte count of manifest + UX container + // 52-55: byte count of attached container + protected const UInt32 BURN_SECTION_OFFSET_MAGIC = 0; + protected const UInt32 BURN_SECTION_OFFSET_VERSION = 4; + protected const UInt32 BURN_SECTION_OFFSET_BUNDLEGUID = 8; + protected const UInt32 BURN_SECTION_OFFSET_STUBSIZE = 24; + protected const UInt32 BURN_SECTION_OFFSET_ORIGINALCHECKSUM = 28; + protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET = 32; + protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE = 36; + protected const UInt32 BURN_SECTION_OFFSET_FORMAT = 40; + protected const UInt32 BURN_SECTION_OFFSET_COUNT = 44; + protected const UInt32 BURN_SECTION_OFFSET_UXSIZE = 48; + protected const UInt32 BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE = 52; + protected const UInt32 BURN_SECTION_SIZE = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE + 4; // last field + sizeof(DWORD) + + protected const UInt32 BURN_SECTION_MAGIC = 0x00f14300; + protected const UInt32 BURN_SECTION_VERSION = 0x00000002; + protected string fileExe; + protected UInt32 peOffset = UInt32.MaxValue; + protected UInt16 sections = UInt16.MaxValue; + protected UInt32 firstSectionOffset = UInt32.MaxValue; + protected UInt32 checksumOffset; + protected UInt32 certificateTableSignatureOffset; + protected UInt32 certificateTableSignatureSize; + protected UInt32 wixburnDataOffset = UInt32.MaxValue; + + // TODO: does this enum exist in another form somewhere? + /// + /// The types of attached containers that BurnWriter supports. + /// + public enum Container + { + Nothing = 0, + UX, + Attached + } + + /// + /// Creates a BurnCommon for re-writing a PE file. + /// + /// + /// File to modify in-place. + public BurnCommon(IMessaging messaging, string fileExe) + { + this.Messaging = messaging; + this.fileExe = fileExe; + } + + public UInt32 Checksum { get; protected set; } + public UInt32 SignatureOffset { get; protected set; } + public UInt32 SignatureSize { get; protected set; } + public UInt32 Version { get; protected set; } + public UInt32 StubSize { get; protected set; } + public UInt32 OriginalChecksum { get; protected set; } + public UInt32 OriginalSignatureOffset { get; protected set; } + public UInt32 OriginalSignatureSize { get; protected set; } + public UInt32 EngineSize { get; protected set; } + public UInt32 ContainerCount { get; protected set; } + public UInt32 UXAddress { get; protected set; } + public UInt32 UXSize { get; protected set; } + public UInt32 AttachedContainerAddress { get; protected set; } + public UInt32 AttachedContainerSize { get; protected set; } + + protected IMessaging Messaging { get; } + + public void Dispose() + { + this.Dispose(true); + + GC.SuppressFinalize(this); + } + + /// + /// Copies one stream to another. + /// + /// Input stream. + /// Output stream. + /// Optional count of bytes to copy. 0 indicates whole input stream from current should be copied. + protected static int CopyStream(Stream input, Stream output, int size) + { + var bytes = new byte[4096]; + var total = 0; + do + { + var read = Math.Min(bytes.Length, size - total); + read = input.Read(bytes, 0, read); + if (0 == read) + { + break; + } + + output.Write(bytes, 0, read); + total += read; + } while (0 == size || total < size); + + return total; + } + + /// + /// Initialize the common information about a Burn engine. + /// + /// Binary reader open against a Burn engine. + /// True if initialized. + protected bool Initialize(BinaryReader reader) + { + if (!this.GetWixburnSectionInfo(reader)) + { + return false; + } + + reader.BaseStream.Seek(this.wixburnDataOffset, SeekOrigin.Begin); + byte[] bytes = reader.ReadBytes((int)BURN_SECTION_SIZE); + UInt32 uint32 = 0; + + uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_MAGIC); + if (BURN_SECTION_MAGIC != uint32) + { + this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe)); + return false; + } + + this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION); + if (BURN_SECTION_VERSION != this.Version) + { + this.Messaging.Write(ErrorMessages.BundleTooNew(this.fileExe, this.Version)); + return false; + } + + uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_FORMAT); // We only know how to deal with CABs right now + if (1 != uint32) + { + this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe)); + return false; + } + + this.StubSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_STUBSIZE); + this.OriginalChecksum = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALCHECKSUM); + this.OriginalSignatureOffset = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET); + this.OriginalSignatureSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE); + + this.ContainerCount = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_COUNT); + this.UXAddress = this.StubSize; + this.UXSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_UXSIZE); + + // If there is an original signature use that to determine the engine size. + if (0 < this.OriginalSignatureOffset) + { + this.EngineSize = this.OriginalSignatureOffset + this.OriginalSignatureSize; + } + else if (0 < this.SignatureOffset && 2 > this.ContainerCount) // if there is a signature and no attached containers, use the current signature. + { + this.EngineSize = this.SignatureOffset + this.SignatureSize; + } + else // just use the stub and UX container as the size of the engine. + { + this.EngineSize = this.StubSize + this.UXSize; + } + + this.AttachedContainerAddress = this.ContainerCount > 1 ? this.EngineSize : 0; + this.AttachedContainerSize = this.ContainerCount > 1 ? BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE) : 0; + + return true; + } + + protected virtual void Dispose(bool disposing) + { + } + + /// + /// Finds the ".wixburn" section in the current exe. + /// + /// true if the ".wixburn" section is successfully found; false otherwise + private bool GetWixburnSectionInfo(BinaryReader reader) + { + if (UInt32.MaxValue == this.wixburnDataOffset) + { + if (!this.EnsureNTHeader(reader)) + { + return false; + } + + UInt32 wixburnSectionOffset = UInt32.MaxValue; + byte[] bytes = new byte[IMAGE_SECTION_HEADER_SIZE]; + + reader.BaseStream.Seek(this.firstSectionOffset, SeekOrigin.Begin); + for (UInt16 sectionIndex = 0; sectionIndex < this.sections; ++sectionIndex) + { + reader.Read(bytes, 0, bytes.Length); + + if (IMAGE_SECTION_WIXBURN_NAME == BurnCommon.ReadUInt64(bytes, IMAGE_SECTION_HEADER_OFFSET_NAME)) + { + wixburnSectionOffset = this.firstSectionOffset + (IMAGE_SECTION_HEADER_SIZE * sectionIndex); + break; + } + } + + if (UInt32.MaxValue == wixburnSectionOffset) + { + this.Messaging.Write(ErrorMessages.StubMissingWixburnSection(this.fileExe)); + return false; + } + + // we need 56 bytes for the manifest header, which is always going to fit in + // the smallest alignment (512 bytes), but just to be paranoid... + if (BURN_SECTION_SIZE > BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA)) + { + this.Messaging.Write(ErrorMessages.StubWixburnSectionTooSmall(this.fileExe)); + return false; + } + + this.wixburnDataOffset = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA); + } + + return true; + } + + /// + /// Checks for a valid Windows PE signature (IMAGE_NT_SIGNATURE) in the current exe. + /// + /// true if the exe is a Windows executable; false otherwise + private bool EnsureNTHeader(BinaryReader reader) + { + if (UInt32.MaxValue == this.firstSectionOffset) + { + if (!this.EnsureDosHeader(reader)) + { + return false; + } + + reader.BaseStream.Seek(this.peOffset, SeekOrigin.Begin); + byte[] bytes = reader.ReadBytes((int)IMAGE_NT_HEADER_SIZE); + + // Verify the NT signature... + if (IMAGE_NT_SIGNATURE != BurnCommon.ReadUInt32(bytes, IMAGE_NT_HEADER_OFFSET_SIGNATURE)) + { + this.Messaging.Write(ErrorMessages.InvalidStubExe(this.fileExe)); + return false; + } + + ushort sizeOptionalHeader = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER); + + this.sections = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS); + this.firstSectionOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader; + + this.checksumOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + IMAGE_OPTIONAL_OFFSET_CHECKSUM; + this.certificateTableSignatureOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE; + this.certificateTableSignatureSize = this.certificateTableSignatureOffset + 4; // size is in the DWORD after the offset. + + bytes = reader.ReadBytes(sizeOptionalHeader); + this.Checksum = BurnCommon.ReadUInt32(bytes, IMAGE_OPTIONAL_OFFSET_CHECKSUM); + this.SignatureOffset = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE); + this.SignatureSize = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE + 4); + } + + return true; + } + + /// + /// Checks for a valid DOS header in the current exe. + /// + /// true if the exe starts with a DOS stub; false otherwise + private bool EnsureDosHeader(BinaryReader reader) + { + if (UInt32.MaxValue == this.peOffset) + { + byte[] bytes = reader.ReadBytes((int)IMAGE_DOS_HEADER_SIZE); + + // Verify the DOS 'MZ' signature. + if (IMAGE_DOS_SIGNATURE != BurnCommon.ReadUInt16(bytes, IMAGE_DOS_HEADER_OFFSET_MAGIC)) + { + this.Messaging.Write(ErrorMessages.InvalidStubExe(this.fileExe)); + return false; + } + + this.peOffset = BurnCommon.ReadUInt32(bytes, IMAGE_DOS_HEADER_OFFSET_NTHEADER); + } + + return true; + } + + /// + /// Reads a UInt16 value in little-endian format from an offset in an array of bytes. + /// + /// Array from which to read. + /// Beginning offset from which to read. + /// value at offset + internal static UInt16 ReadUInt16(byte[] bytes, UInt32 offset) + { + Debug.Assert(offset + 2 <= bytes.Length); + return (UInt16)(bytes[offset] + (bytes[offset + 1] << 8)); + } + + /// + /// Reads a UInt32 value in little-endian format from an offset in an array of bytes. + /// + /// Array from which to read. + /// Beginning offset from which to read. + /// value at offset + internal static UInt32 ReadUInt32(byte[] bytes, UInt32 offset) + { + Debug.Assert(offset + 4 <= bytes.Length); + return BurnCommon.ReadUInt16(bytes, offset) + ((UInt32)BurnCommon.ReadUInt16(bytes, offset + 2) << 16); + } + + /// + /// Reads a UInt64 value in little-endian format from an offset in an array of bytes. + /// + /// Array from which to read. + /// Beginning offset from which to read. + /// value at offset + internal static UInt64 ReadUInt64(byte[] bytes, UInt32 offset) + { + Debug.Assert(offset + 8 <= bytes.Length); + return BurnCommon.ReadUInt32(bytes, offset) + ((UInt64)BurnCommon.ReadUInt32(bytes, offset + 4) << 32); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs new file mode 100644 index 00000000..5b06b31e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs @@ -0,0 +1,212 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Xml; + using WixToolset.Core.Native; + using WixToolset.Extensibility.Services; + + /// + /// Burn PE reader for the WiX toolset. + /// + /// This class encapsulates reading from a stub EXE with containers attached + /// for dissecting bundled/chained setup packages. + /// + /// using (BurnReader reader = BurnReader.Open(fileExe, this.core, guid)) + /// { + /// reader.ExtractUXContainer(file1, tempFolder); + /// } + /// + internal class BurnReader : BurnCommon + { + private bool disposed; + + private bool invalidBundle; + private BinaryReader binaryReader; + private readonly List attachedContainerPayloadNames; + + /// + /// Creates a BurnReader for reading a PE file. + /// + /// + /// File to read. + private BurnReader(IMessaging messaging, string fileExe) + : base(messaging, fileExe) + { + this.attachedContainerPayloadNames = new List(); + } + + /// + /// Gets the underlying stream. + /// + public Stream Stream => this.binaryReader?.BaseStream; + + internal static BurnReader Open(object inputFilePath) + { + throw new NotImplementedException(); + } + + /// + /// Opens a Burn reader. + /// + /// + /// Path to file. + /// Burn reader. + public static BurnReader Open(IMessaging messaging, string fileExe) + { + var reader = new BurnReader(messaging, fileExe); + + reader.binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)); + if (!reader.Initialize(reader.binaryReader)) + { + reader.invalidBundle = true; + } + + return reader; + } + + /// + /// Gets the UX container from the exe and extracts its contents to the output directory. + /// + /// Directory to write extracted files to. + /// Scratch directory. + /// True if successful, false otherwise + public bool ExtractUXContainer(string outputDirectory, string tempDirectory) + { + // No UX container to extract + if (this.UXAddress == 0 || this.UXSize == 0) + { + return false; + } + + if (this.invalidBundle) + { + return false; + } + + Directory.CreateDirectory(outputDirectory); + string tempCabPath = Path.Combine(tempDirectory, "ux.cab"); + string manifestOriginalPath = Path.Combine(outputDirectory, "0"); + string manifestPath = Path.Combine(outputDirectory, "manifest.xml"); + + this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin); + using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) + { + BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.UXSize); + } + + var cabinet = new Cabinet(tempCabPath); + cabinet.Extract(outputDirectory); + + Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); + FileSystem.MoveFile(manifestOriginalPath, manifestPath); + + XmlDocument document = new XmlDocument(); + document.Load(manifestPath); + XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace("burn", BurnCommon.BurnNamespace); + XmlNodeList uxPayloads = document.SelectNodes("/burn:BurnManifest/burn:UX/burn:Payload", namespaceManager); + XmlNodeList payloads = document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager); + + foreach (XmlNode uxPayload in uxPayloads) + { + XmlNode sourcePathNode = uxPayload.Attributes.GetNamedItem("SourcePath"); + XmlNode filePathNode = uxPayload.Attributes.GetNamedItem("FilePath"); + + string sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value); + string destinationPath = Path.Combine(outputDirectory, filePathNode.Value); + + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + FileSystem.MoveFile(sourcePath, destinationPath); + } + + foreach (XmlNode payload in payloads) + { + XmlNode sourcePathNode = payload.Attributes.GetNamedItem("SourcePath"); + XmlNode filePathNode = payload.Attributes.GetNamedItem("FilePath"); + XmlNode packagingNode = payload.Attributes.GetNamedItem("Packaging"); + + string sourcePath = sourcePathNode.Value; + string destinationPath = filePathNode.Value; + string packaging = packagingNode.Value; + + if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase)) + { + this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath)); + } + } + + return true; + } + + internal void ExtractUXContainer(string uxExtractPath, object intermediateFolder) + { + throw new NotImplementedException(); + } + + /// + /// Gets the attached container from the exe and extracts its contents to the output directory. + /// + /// Directory to write extracted files to. + /// Scratch directory. + /// True if successful, false otherwise + public bool ExtractAttachedContainer(string outputDirectory, string tempDirectory) + { + // No attached container to extract + if (this.AttachedContainerAddress == 0 || this.AttachedContainerSize == 0) + { + return false; + } + + if (this.invalidBundle) + { + return false; + } + + Directory.CreateDirectory(outputDirectory); + string tempCabPath = Path.Combine(tempDirectory, "attached.cab"); + + this.binaryReader.BaseStream.Seek(this.AttachedContainerAddress, SeekOrigin.Begin); + using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) + { + BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.AttachedContainerSize); + } + + var cabinet = new Cabinet(tempCabPath); + cabinet.Extract(outputDirectory); + + foreach (DictionaryEntry entry in this.attachedContainerPayloadNames) + { + string sourcePath = Path.Combine(outputDirectory, (string)entry.Key); + string destinationPath = Path.Combine(outputDirectory, (string)entry.Value); + + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + FileSystem.MoveFile(sourcePath, destinationPath); + } + + return true; + } + + /// + /// Dispose object. + /// + /// True when releasing managed objects. + protected override void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing && this.binaryReader != null) + { + this.binaryReader.Close(); + this.binaryReader = null; + } + + this.disposed = true; + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs new file mode 100644 index 00000000..2d16d11c --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs @@ -0,0 +1,245 @@ +// 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.Burn.Bundles +{ + using System; + using System.Diagnostics; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + /// + /// Burn PE writer for the WiX toolset. + /// + /// This class encapsulates reading/writing to a stub EXE for + /// creating bundled/chained setup packages. + /// + /// using (BurnWriter writer = new BurnWriter(fileExe, this.core, guid)) + /// { + /// writer.AppendContainer(file1, BurnWriter.Container.UX); + /// writer.AppendContainer(file2, BurnWriter.Container.Attached); + /// } + /// + internal class BurnWriter : BurnCommon + { + private bool disposed; + private bool invalidBundle; + private BinaryWriter binaryWriter; + + /// + /// Creates a BurnWriter for re-writing a PE file. + /// + /// + /// File to modify in-place. + private BurnWriter(IMessaging messaging, string fileExe) + : base(messaging, fileExe) + { + } + + /// + /// Opens a Burn writer. + /// + /// + /// Path to file. + /// Burn writer. + public static BurnWriter Open(IMessaging messaging, string fileExe) + { + BurnWriter writer = new BurnWriter(messaging, fileExe); + + using (BinaryReader binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))) + { + if (!writer.Initialize(binaryReader)) + { + writer.invalidBundle = true; + } + } + + if (!writer.invalidBundle) + { + writer.binaryWriter = new BinaryWriter(File.Open(fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete)); + } + + return writer; + } + + /// + /// Update the ".wixburn" section data. + /// + /// Size of the stub engine "burn.exe". + /// Unique identifier for this bundle. + /// + public bool InitializeBundleSectionData(long stubSize, string bundleId) + { + if (this.invalidBundle) + { + return false; + } + + var bundleGuid = Guid.Parse(bundleId); + + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); + + this.Messaging.Write(VerboseMessages.BundleGuid(bundleId)); + this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); + this.binaryWriter.Write(bundleGuid.ToByteArray()); + + this.StubSize = (uint)stubSize; + + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_STUBSIZE, this.StubSize); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, 0); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, 0); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, 0); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_FORMAT, 1); // Hard-coded to CAB for now. + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, 0); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_UXSIZE, 0); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE, 0); + this.binaryWriter.BaseStream.Flush(); + + this.EngineSize = this.StubSize; + + return true; + } + + /// + /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. + /// + /// File path to append to the current exe. + /// Container section represented by the fileContainer. + /// true if the container data is successfully appended; false otherwise + public bool AppendContainer(string fileContainer, BurnCommon.Container container) + { + using (FileStream reader = File.OpenRead(fileContainer)) + { + return this.AppendContainer(reader, reader.Length, container); + } + } + + /// + /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. + /// + /// File stream to append to the current exe. + /// Size of container to append. + /// Container section represented by the fileContainer. + /// true if the container data is successfully appended; false otherwise + public bool AppendContainer(Stream containerStream, long containerSize, BurnCommon.Container container) + { + UInt32 burnSectionCount = 0; + UInt32 burnSectionOffsetSize = 0; + + switch (container) + { + case Container.UX: + burnSectionCount = 1; + burnSectionOffsetSize = BURN_SECTION_OFFSET_UXSIZE; + // TODO: verify that the size in the section data is 0 or the same size. + this.EngineSize += (uint)containerSize; + this.UXSize = (uint)containerSize; + break; + + case Container.Attached: + burnSectionCount = 2; + burnSectionOffsetSize = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE; + // TODO: verify that the size in the section data is 0 or the same size. + this.AttachedContainerSize = (uint)containerSize; + break; + + default: + Debug.Assert(false); + return false; + } + + return this.AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount); + } + + public void RememberThenResetSignature() + { + if (this.invalidBundle) + { + return; + } + + this.OriginalChecksum = this.Checksum; + this.OriginalSignatureOffset = this.SignatureOffset; + this.OriginalSignatureSize = this.SignatureSize; + + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, this.OriginalChecksum); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, this.OriginalSignatureOffset); + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, this.OriginalSignatureSize); + + this.Checksum = 0; + this.SignatureOffset = 0; + this.SignatureSize = 0; + + this.WriteToOffset(this.checksumOffset, this.Checksum); + this.WriteToOffset(this.certificateTableSignatureOffset, this.SignatureOffset); + this.WriteToOffset(this.certificateTableSignatureSize, this.SignatureSize); + } + + /// + /// Dispose object. + /// + /// True when releasing managed objects. + protected override void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing && this.binaryWriter != null) + { + this.binaryWriter.Close(); + this.binaryWriter = null; + } + + this.disposed = true; + } + } + + /// + /// Appends a container to the exe and updates the ".wixburn" section data to point to it. + /// + /// File stream to append to the current exe. + /// Size of the container. + /// Offset of size field for this container in ".wixburn" section data. + /// Number of Burn sections. + /// true if the container data is successfully appended; false otherwise + private bool AppendContainer(Stream containerStream, UInt32 containerSize, UInt32 burnSectionOffsetSize, UInt32 burnSectionCount) + { + if (this.invalidBundle) + { + return false; + } + + // Update the ".wixburn" section data + this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, burnSectionCount); + this.WriteToBurnSectionOffset(burnSectionOffsetSize, containerSize); + + // Append the container to the end of the existing bits. + this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End); + BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize); + this.binaryWriter.BaseStream.Flush(); + + return true; + } + + /// + /// Writes the value to an offset in the Burn section data. + /// + /// Offset in to the Burn section data. + /// Value to write. + private void WriteToBurnSectionOffset(uint offset, uint value) + { + this.WriteToOffset(this.wixburnDataOffset + offset, value); + } + + /// + /// Writes the value to an offset in the Burn stub. + /// + /// Offset in to the Burn stub. + /// Value to write. + private void WriteToOffset(uint offset, uint value) + { + this.binaryWriter.BaseStream.Seek((int)offset, SeekOrigin.Begin); + this.binaryWriter.Write(value); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs new file mode 100644 index 00000000..a0ee606d --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -0,0 +1,290 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + + internal class CreateBootstrapperApplicationManifestCommand + { + public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable chainPackages, int lastUXPayloadIndex, Dictionary payloadSymbols, Dictionary> packagesPayloads, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) + { + this.Section = section; + this.BundleSymbol = bundleSymbol; + this.ChainPackages = chainPackages; + this.LastUXPayloadIndex = lastUXPayloadIndex; + this.Payloads = payloadSymbols; + this.PackagesPayloads = packagesPayloads; + this.IntermediateFolder = intermediateFolder; + this.InternalBurnBackendHelper = internalBurnBackendHelper; + } + + private IntermediateSection Section { get; } + + private WixBundleSymbol BundleSymbol { get; } + + private IEnumerable ChainPackages { get; } + + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } + + private int LastUXPayloadIndex { get; } + + private Dictionary Payloads { get; } + + private Dictionary> PackagesPayloads { get; } + + private string IntermediateFolder { get; } + + public WixBundlePayloadSymbol BootstrapperApplicationManifestPayloadRow { get; private set; } + + public string OutputPath { get; private set; } + + public void Execute() + { + this.OutputPath = this.CreateBootstrapperApplicationManifest(); + + this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(this.OutputPath); + } + + private string CreateBootstrapperApplicationManifest() + { + var path = Path.Combine(this.IntermediateFolder, "wix-badata.xml"); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var writer = new XmlTextWriter(path, Encoding.Unicode)) + { + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument(); + writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace); + + this.WriteBundleInfo(writer); + + this.WritePackageInfo(writer); + + this.WriteFeatureInfo(writer); + + this.WritePayloadInfo(writer); + + this.InternalBurnBackendHelper.WriteBootstrapperApplicationData(writer); + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + + return path; + } + + private void WriteBundleInfo(XmlTextWriter writer) + { + writer.WriteStartElement("WixBundleProperties"); + + writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); + writer.WriteAttributeString("LogPathVariable", this.BundleSymbol.LogPathVariable); + writer.WriteAttributeString("Compressed", this.BundleSymbol.Compressed == true ? "yes" : "no"); + writer.WriteAttributeString("Id", this.BundleSymbol.BundleId.ToUpperInvariant()); + writer.WriteAttributeString("UpgradeCode", this.BundleSymbol.UpgradeCode); + writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); + + writer.WriteEndElement(); + } + + private void WritePackageInfo(XmlTextWriter writer) + { + foreach (var package in this.ChainPackages) + { + if (!this.PackagesPayloads.TryGetValue(package.PackageId, out var payloads)) + { + continue; + } + + var packagePayload = payloads[package.PackageSymbol.PayloadRef]; + + var size = package.PackageSymbol.Size.ToString(CultureInfo.InvariantCulture); + + writer.WriteStartElement("WixPackageProperties"); + + writer.WriteAttributeString("Package", package.PackageId); + writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == true ? "yes" : "no"); + + if (!String.IsNullOrEmpty(package.PackageSymbol.DisplayName)) + { + writer.WriteAttributeString("DisplayName", package.PackageSymbol.DisplayName); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.Description)) + { + writer.WriteAttributeString("Description", package.PackageSymbol.Description); + } + + writer.WriteAttributeString("DownloadSize", size); + writer.WriteAttributeString("PackageSize", size); + writer.WriteAttributeString("InstalledSize", package.PackageSymbol.InstallSize?.ToString(CultureInfo.InvariantCulture) ?? size); + writer.WriteAttributeString("PackageType", package.PackageSymbol.Type.ToString()); + writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); + writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); + writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); + writer.WriteAttributeString("Compressed", packagePayload.Packaging == PackagingType.Embedded ? "yes" : "no"); + + if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) + { + if (!String.IsNullOrEmpty(msiPackage.ProductCode)) + { + writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); + } + + if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); + } + } + else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) + { + if (!String.IsNullOrEmpty(mspPackage.PatchCode)) + { + writer.WriteAttributeString("ProductCode", mspPackage.PatchCode); + } + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.Version)) + { + writer.WriteAttributeString("Version", package.PackageSymbol.Version); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) + { + writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); + } + + switch (package.PackageSymbol.Cache) + { + case YesNoAlwaysType.No: + writer.WriteAttributeString("Cache", "remove"); + break; + case YesNoAlwaysType.Yes: + writer.WriteAttributeString("Cache", "keep"); + break; + case YesNoAlwaysType.Always: + writer.WriteAttributeString("Cache", "force"); + break; + } + + writer.WriteEndElement(); + } + } + + private void WriteFeatureInfo(XmlTextWriter writer) + { + var featureSymbols = this.Section.Symbols.OfType(); + + foreach (var featureSymbol in featureSymbols) + { + writer.WriteStartElement("WixPackageFeatureInfo"); + + writer.WriteAttributeString("Package", featureSymbol.PackageRef); + writer.WriteAttributeString("Feature", featureSymbol.Name); + writer.WriteAttributeString("Size", featureSymbol.Size.ToString(CultureInfo.InvariantCulture)); + + if (!String.IsNullOrEmpty(featureSymbol.Parent)) + { + writer.WriteAttributeString("Parent", featureSymbol.Parent); + } + + if (!String.IsNullOrEmpty(featureSymbol.Title)) + { + writer.WriteAttributeString("Title", featureSymbol.Title); + } + + if (!String.IsNullOrEmpty(featureSymbol.Description)) + { + writer.WriteAttributeString("Description", featureSymbol.Description); + } + + writer.WriteAttributeString("Display", featureSymbol.Display.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Level", featureSymbol.Level.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Directory", featureSymbol.Directory); + writer.WriteAttributeString("Attributes", featureSymbol.Attributes.ToString(CultureInfo.InvariantCulture)); + + writer.WriteEndElement(); + } + } + + private void WritePayloadInfo(XmlTextWriter writer) + { + foreach (var kvp in this.PackagesPayloads.OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) + { + var packageId = kvp.Key; + var payloadsById = kvp.Value; + + foreach (var payloadSymbol in payloadsById.Values.OrderBy(p => p.Id.Id, StringComparer.Ordinal)) + { + this.WritePayloadInfo(writer, payloadSymbol, packageId); + } + } + + foreach (var payloadSymbol in this.Payloads.Values.Where(p => p.LayoutOnly).OrderBy(p => p.Id.Id, StringComparer.Ordinal)) + { + this.WritePayloadInfo(writer, payloadSymbol, null); + } + } + + private void WritePayloadInfo(XmlTextWriter writer, WixBundlePayloadSymbol payloadSymbol, string packageId) + { + writer.WriteStartElement("WixPayloadProperties"); + + if (!String.IsNullOrEmpty(packageId)) + { + writer.WriteAttributeString("Package", packageId); + } + + writer.WriteAttributeString("Payload", payloadSymbol.Id.Id); + + if (!String.IsNullOrEmpty(payloadSymbol.ContainerRef)) + { + writer.WriteAttributeString("Container", payloadSymbol.ContainerRef); + } + + writer.WriteAttributeString("Name", payloadSymbol.Name); + writer.WriteAttributeString("Size", payloadSymbol.FileSize.Value.ToString(CultureInfo.InvariantCulture)); + + if (!String.IsNullOrEmpty(payloadSymbol.DownloadUrl)) + { + writer.WriteAttributeString("DownloadUrl", payloadSymbol.DownloadUrl); + } + + writer.WriteEndElement(); + } + + private WixBundlePayloadSymbol CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) + { + var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BADataFileName); + + var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) + { + Name = BurnCommon.BADataFileName, + SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, + Compressed = true, + UnresolvedSourceFile = baManifestPath, + ContainerRef = BurnConstants.BurnUXContainerName, + EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), + Packaging = PackagingType.Embedded, + }); + + var fileInfo = new FileInfo(baManifestPath); + + symbol.FileSize = (int)fileInfo.Length; + + symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); + + return symbol; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs new file mode 100644 index 00000000..b802f556 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs @@ -0,0 +1,325 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using System.Text; + using System.Xml; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Dtf.Resources; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CreateBundleExeCommand + { + public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable containers) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.IntermediateFolder = intermediateFolder; + this.OutputPath = outputPath; + this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; + this.BundleSymbol = bundleSymbol; + this.UXContainer = uxContainer; + this.Containers = containers; + } + + public IFileTransfer Transfer { get; private set; } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private string IntermediateFolder { get; } + + private string OutputPath { get; } + + private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } + + private WixBundleSymbol BundleSymbol { get; } + + private WixBundleContainerSymbol UXContainer { get; } + + private IEnumerable Containers { get; } + + public void Execute() + { + var bundleFilename = Path.GetFileName(this.OutputPath); + + // Copy the burn.exe to a writable location then mark it to be moved to its final build location. + + var stubPlatform = this.BundleSymbol.Platform.ToString(); + var stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); + + if (stubPlatform != "X86") + { + this.Messaging.Write(WarningMessages.ExperimentalBundlePlatform(stubPlatform)); + } + + var bundleTempPath = Path.Combine(this.IntermediateFolder, bundleFilename); + + this.Messaging.Write(VerboseMessages.GeneratingBundle(bundleTempPath, stubFile)); + + if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.InsecureBundleFilename(bundleFilename)); + } + + this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleSymbol.SourceLineNumbers); + + FileSystem.CopyFile(stubFile, bundleTempPath, allowHardlink: false); + File.SetAttributes(bundleTempPath, FileAttributes.Normal); + + var windowsAssemblyVersion = GetWindowsAssemblyVersion(this.BundleSymbol); + + var applicationManifestData = GenerateApplicationManifest(this.BundleSymbol, this.BootstrapperApplicationDllSymbol, this.OutputPath, windowsAssemblyVersion); + + UpdateBurnResources(bundleTempPath, this.OutputPath, this.BundleSymbol, windowsAssemblyVersion, applicationManifestData); + + // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers + // if they should be attached. + using (var writer = BurnWriter.Open(this.Messaging, bundleTempPath)) + { + var burnStubFile = new FileInfo(bundleTempPath); + writer.InitializeBundleSectionData(burnStubFile.Length, this.BundleSymbol.BundleId); + + // Always attach the UX container first + writer.AppendContainer(this.UXContainer.WorkingPath, BurnWriter.Container.UX); + + // Now append all other attached containers + foreach (var container in this.Containers) + { + if (ContainerType.Attached == container.Type) + { + // The container was only created if it had payloads. + if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) + { + writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); + } + } + } + } + } + + private static byte[] GenerateApplicationManifest(WixBundleSymbol bundleSymbol, WixBootstrapperApplicationDllSymbol bootstrapperApplicationSymbol, string outputPath, Version windowsAssemblyVersion) + { + const string asmv1Namespace = "urn:schemas-microsoft-com:asm.v1"; + const string asmv3Namespace = "urn:schemas-microsoft-com:asm.v3"; + const string compatv1Namespace = "urn:schemas-microsoft-com:compatibility.v1"; + const string ws2005Namespace = "http://schemas.microsoft.com/SMI/2005/WindowsSettings"; + const string ws2016Namespace = "http://schemas.microsoft.com/SMI/2016/WindowsSettings"; + const string ws2017Namespace = "http://schemas.microsoft.com/SMI/2017/WindowsSettings"; + + var bundleFileName = Path.GetFileName(outputPath); + var bundleAssemblyVersion = windowsAssemblyVersion.ToString(); + var bundlePlatform = bundleSymbol.Platform == Platform.X64 ? "amd64" : bundleSymbol.Platform.ToString().ToLower(); + var bundleDescription = bundleSymbol.Name; + + using (var memoryStream = new MemoryStream()) + using (var writer = new XmlTextWriter(memoryStream, Encoding.UTF8)) + { + writer.WriteStartDocument(); + + writer.WriteStartElement("assembly", asmv1Namespace); + writer.WriteAttributeString("manifestVersion", "1.0"); + + writer.WriteStartElement("assemblyIdentity"); + writer.WriteAttributeString("name", bundleFileName); + writer.WriteAttributeString("version", bundleAssemblyVersion); + writer.WriteAttributeString("processorArchitecture", bundlePlatform); + writer.WriteAttributeString("type", "win32"); + writer.WriteEndElement(); // + + if (!String.IsNullOrEmpty(bundleDescription)) + { + writer.WriteStartElement("description"); + writer.WriteString(bundleDescription); + writer.WriteEndElement(); + } + + writer.WriteStartElement("dependency"); + writer.WriteStartElement("dependentAssembly"); + writer.WriteStartElement("assemblyIdentity"); + writer.WriteAttributeString("name", "Microsoft.Windows.Common-Controls"); + writer.WriteAttributeString("version", "6.0.0.0"); + writer.WriteAttributeString("processorArchitecture", bundlePlatform); + writer.WriteAttributeString("publicKeyToken", "6595b64144ccf1df"); + writer.WriteAttributeString("language", "*"); + writer.WriteAttributeString("type", "win32"); + writer.WriteEndElement(); // + writer.WriteEndElement(); // + writer.WriteEndElement(); // + + writer.WriteStartElement("compatibility", compatv1Namespace); + writer.WriteStartElement("application"); + + writer.WriteStartElement("supportedOS"); + writer.WriteAttributeString("Id", "{e2011457-1546-43c5-a5fe-008deee3d3f0}"); // Windows Vista + writer.WriteEndElement(); + writer.WriteStartElement("supportedOS"); + writer.WriteAttributeString("Id", "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"); // Windows 7 + writer.WriteEndElement(); + writer.WriteStartElement("supportedOS"); + writer.WriteAttributeString("Id", "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"); // Windows 8 + writer.WriteEndElement(); + writer.WriteStartElement("supportedOS"); + writer.WriteAttributeString("Id", "{1f676c76-80e1-4239-95bb-83d0f6d0da78}"); // Windows 8.1 + writer.WriteEndElement(); + writer.WriteStartElement("supportedOS"); + writer.WriteAttributeString("Id", "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"); // Windows 10 + writer.WriteEndElement(); + + writer.WriteEndElement(); // + writer.WriteEndElement(); // + + writer.WriteStartElement("trustInfo", asmv3Namespace); + writer.WriteStartElement("security"); + writer.WriteStartElement("requestedPrivileges"); + writer.WriteStartElement("requestedExecutionLevel"); + writer.WriteAttributeString("level", "asInvoker"); + writer.WriteAttributeString("uiAccess", "false"); + writer.WriteEndElement(); // + writer.WriteEndElement(); // + writer.WriteEndElement(); // + writer.WriteEndElement(); // + + if (bootstrapperApplicationSymbol.DpiAwareness != WixBootstrapperApplicationDpiAwarenessType.Unaware) + { + string dpiAwareValue = null; + string dpiAwarenessValue = null; + string gdiScalingValue = null; + + switch(bootstrapperApplicationSymbol.DpiAwareness) + { + case WixBootstrapperApplicationDpiAwarenessType.GdiScaled: + gdiScalingValue = "true"; + break; + case WixBootstrapperApplicationDpiAwarenessType.PerMonitor: + dpiAwareValue = "true/pm"; + break; + case WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2: + dpiAwareValue = "true/pm"; + dpiAwarenessValue = "PerMonitorV2, PerMonitor"; + break; + case WixBootstrapperApplicationDpiAwarenessType.System: + dpiAwareValue = "true"; + break; + } + + writer.WriteStartElement("application", asmv3Namespace); + writer.WriteStartElement("windowsSettings"); + + if (dpiAwareValue != null) + { + writer.WriteStartElement("dpiAware", ws2005Namespace); + writer.WriteString(dpiAwareValue); + writer.WriteEndElement(); + } + + if (dpiAwarenessValue != null) + { + writer.WriteStartElement("dpiAwareness", ws2016Namespace); + writer.WriteString(dpiAwarenessValue); + writer.WriteEndElement(); + } + + if (gdiScalingValue != null) + { + writer.WriteStartElement("gdiScaling", ws2017Namespace); + writer.WriteString(gdiScalingValue); + writer.WriteEndElement(); + } + + writer.WriteEndElement(); // + writer.WriteEndElement(); // + } + + writer.WriteEndDocument(); // + writer.Close(); + + return memoryStream.ToArray(); + } + } + + private static Version GetWindowsAssemblyVersion(WixBundleSymbol bundleSymbol) + { + // Ensure the bundle info provides a full four part version. + var fourPartVersion = new Version(bundleSymbol.Version); + var major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; + var minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; + var build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; + var revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; + + if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) + { + throw new WixException(ErrorMessages.InvalidModuleOrBundleVersion(bundleSymbol.SourceLineNumbers, "Bundle", bundleSymbol.Version)); + } + + return new Version(major, minor, build, revision); + } + + private static void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleSymbol bundleInfo, Version windowsAssemblyVersion, byte[] applicationManifestData) + { + const int burnLocale = 1033; + var resources = new Dtf.Resources.ResourceCollection(); + var version = new Dtf.Resources.VersionResource("#1", burnLocale); + + version.Load(bundleTempPath); + resources.Add(version); + + version.FileVersion = windowsAssemblyVersion; + version.ProductVersion = windowsAssemblyVersion; + + var strings = version[burnLocale] ?? version.Add(burnLocale); + strings["LegalCopyright"] = bundleInfo.Copyright; + strings["OriginalFilename"] = Path.GetFileName(outputPath); + strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. + strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts. + + if (!String.IsNullOrEmpty(bundleInfo.Name)) + { + strings["ProductName"] = bundleInfo.Name; + strings["FileDescription"] = bundleInfo.Name; + } + + if (!String.IsNullOrEmpty(bundleInfo.Manufacturer)) + { + strings["CompanyName"] = bundleInfo.Manufacturer; + } + else + { + strings["CompanyName"] = String.Empty; + } + + if (!String.IsNullOrEmpty(bundleInfo.IconSourceFile)) + { + var iconGroup = new Dtf.Resources.GroupIconResource("#1", burnLocale); + iconGroup.ReadFromFile(bundleInfo.IconSourceFile); + resources.Add(iconGroup); + + foreach (var icon in iconGroup.Icons) + { + resources.Add(icon); + } + } + + if (!String.IsNullOrEmpty(bundleInfo.SplashScreenSourceFile)) + { + var bitmap = new Dtf.Resources.BitmapResource("#1", burnLocale); + bitmap.ReadFromFile(bundleInfo.SplashScreenSourceFile); + resources.Add(bitmap); + } + + var manifestResource = new Resource(ResourceType.Manifest, "#1", burnLocale, applicationManifestData); + resources.Add(manifestResource); + + resources.Save(bundleTempPath); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs new file mode 100644 index 00000000..e587413e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs @@ -0,0 +1,99 @@ +// 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.Burn.Bundles +{ + using System; + using System.Globalization; + using System.IO; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + + internal class CreateBundleExtensionManifestCommand + { + public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, int lastUXPayloadIndex, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) + { + this.Section = section; + this.BundleSymbol = bundleSymbol; + this.LastUXPayloadIndex = lastUXPayloadIndex; + this.IntermediateFolder = intermediateFolder; + this.InternalBurnBackendHelper = internalBurnBackendHelper; + } + + private IntermediateSection Section { get; } + + private WixBundleSymbol BundleSymbol { get; } + + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } + + private int LastUXPayloadIndex { get; } + + private string IntermediateFolder { get; } + + public WixBundlePayloadSymbol BundleExtensionManifestPayloadRow { get; private set; } + + public string OutputPath { get; private set; } + + public void Execute() + { + this.OutputPath = this.CreateBundleExtensionManifest(); + + this.BundleExtensionManifestPayloadRow = this.CreateBundleExtensionManifestPayloadRow(this.OutputPath); + } + + private string CreateBundleExtensionManifest() + { + var path = Path.Combine(this.IntermediateFolder, "wix-bextdata.xml"); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var writer = new XmlTextWriter(path, Encoding.Unicode)) + { + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument(); + writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); + + this.InternalBurnBackendHelper.WriteBundleExtensionData(writer); + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + + return path; + } + + private WixBundlePayloadSymbol CreateBundleExtensionManifestPayloadRow(string bextManifestPath) + { + var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName); + + this.Section.AddSymbol(new WixGroupSymbol(this.BundleSymbol.SourceLineNumbers) + { + ParentType = ComplexReferenceParentType.Container, + ParentId = BurnConstants.BurnUXContainerName, + ChildType = ComplexReferenceChildType.Payload, + ChildId = generatedId + }); + + var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) + { + Name = BurnCommon.BundleExtensionDataFileName, + SourceFile = new IntermediateFieldPathValue { Path = bextManifestPath }, + Compressed = true, + UnresolvedSourceFile = bextManifestPath, + ContainerRef = BurnConstants.BurnUXContainerName, + EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), + Packaging = PackagingType.Embedded, + }); + + var fileInfo = new FileInfo(bextManifestPath); + + symbol.FileSize = (int)fileInfo.Length; + + symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); + + return symbol; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs new file mode 100644 index 00000000..5655d23d --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -0,0 +1,700 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class CreateBurnManifestCommand + { + public CreateBurnManifestCommand(string executableName, IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable containers, WixChainSymbol chainSymbol, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, Dictionary> packagesPayloads, IEnumerable orderedSearches, string intermediateFolder) + { + this.ExecutableName = executableName; + this.Section = section; + this.BundleSymbol = bundleSymbol; + this.Chain = chainSymbol; + this.Containers = containers; + this.OrderedPackages = orderedPackages; + this.RollbackBoundaries = boundaries; + this.UXContainerPayloads = uxPayloads; + this.Payloads = allPayloadsById; + this.PackagesPayloads = packagesPayloads; + this.OrderedSearches = orderedSearches; + this.IntermediateFolder = intermediateFolder; + } + + public string OutputPath { get; private set; } + + private string ExecutableName { get; } + + private IntermediateSection Section { get; } + + private WixBundleSymbol BundleSymbol { get; } + + private WixChainSymbol Chain { get; } + + private IEnumerable RollbackBoundaries { get; } + + private IEnumerable OrderedPackages { get; } + + private IEnumerable OrderedSearches { get; } + + private Dictionary Payloads { get; } + + private Dictionary> PackagesPayloads { get; } + + private IEnumerable Containers { get; } + + private IEnumerable UXContainerPayloads { get; } + + private string IntermediateFolder { get; } + + public void Execute() + { + this.OutputPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml"); + + using (var writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) + { + writer.WriteStartDocument(); + + writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); + + // Write the condition, if there is one + if (null != this.BundleSymbol.Condition) + { + writer.WriteElementString("Condition", this.BundleSymbol.Condition); + } + + // Write the log element if default logging wasn't disabled. + if (!String.IsNullOrEmpty(this.BundleSymbol.LogPrefix)) + { + writer.WriteStartElement("Log"); + if (!String.IsNullOrEmpty(this.BundleSymbol.LogPathVariable)) + { + writer.WriteAttributeString("PathVariable", this.BundleSymbol.LogPathVariable); + } + writer.WriteAttributeString("Prefix", this.BundleSymbol.LogPrefix); + writer.WriteAttributeString("Extension", this.BundleSymbol.LogExtension); + writer.WriteEndElement(); + } + + + // Get update if specified. + var updateSymbol = this.Section.Symbols.OfType().FirstOrDefault(); + + if (null != updateSymbol) + { + writer.WriteStartElement("Update"); + writer.WriteAttributeString("Location", updateSymbol.Location); + writer.WriteEndElement(); // + } + + // Write the RelatedBundle elements + + // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates + // enumeration in the index row list is not used). + var relatedBundles = this.Section.Symbols.OfType(); + var distinctRelatedBundles = new HashSet(); + + foreach (var relatedBundle in relatedBundles) + { + if (distinctRelatedBundles.Add(relatedBundle.BundleId)) + { + writer.WriteStartElement("RelatedBundle"); + writer.WriteAttributeString("Id", relatedBundle.BundleId); + writer.WriteAttributeString("Action", relatedBundle.Action.ToString()); + writer.WriteEndElement(); + } + } + + // Write the variables + var variables = this.Section.Symbols.OfType(); + + foreach (var variable in variables) + { + writer.WriteStartElement("Variable"); + writer.WriteAttributeString("Id", variable.Id.Id); + if (variable.Type != WixBundleVariableType.Unknown) + { + writer.WriteAttributeString("Value", variable.Value); + + switch (variable.Type) + { + case WixBundleVariableType.Formatted: + writer.WriteAttributeString("Type", "formatted"); + break; + case WixBundleVariableType.Numeric: + writer.WriteAttributeString("Type", "numeric"); + break; + case WixBundleVariableType.String: + writer.WriteAttributeString("Type", "string"); + break; + case WixBundleVariableType.Version: + writer.WriteAttributeString("Type", "version"); + break; + } + } + writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no"); + writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no"); + writer.WriteEndElement(); + } + + // Write the searches + foreach (var searchinfo in this.OrderedSearches) + { + searchinfo.WriteXml(writer); + } + + // write the UX element + writer.WriteStartElement("UX"); + if (!String.IsNullOrEmpty(this.BundleSymbol.SplashScreenSourceFile)) + { + writer.WriteAttributeString("SplashScreen", "yes"); + } + + // write the UX allPayloads... + foreach (var payload in this.UXContainerPayloads) + { + this.WriteBurnManifestUXPayload(writer, payload); + } + + writer.WriteEndElement(); // + + foreach (var container in this.Containers) + { + if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id) + { + writer.WriteStartElement("Container"); + this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); + writer.WriteEndElement(); + } + } + + foreach (var payload in this.Payloads.Values.Where(p => p.ContainerRef != BurnConstants.BurnUXContainerName)) + { + this.WriteBurnManifestPayload(writer, payload); + } + + foreach (var rollbackBoundary in this.RollbackBoundaries) + { + writer.WriteStartElement("RollbackBoundary"); + writer.WriteAttributeString("Id", rollbackBoundary.Id.Id); + writer.WriteAttributeString("Vital", rollbackBoundary.Vital == false ? "no" : "yes"); + writer.WriteAttributeString("Transaction", rollbackBoundary.Transaction == true ? "yes" : "no"); + writer.WriteEndElement(); + } + + // Write the registration information... + writer.WriteStartElement("Registration"); + + writer.WriteAttributeString("Id", this.BundleSymbol.BundleId); + writer.WriteAttributeString("ExecutableName", this.ExecutableName); + writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); + writer.WriteAttributeString("Tag", this.BundleSymbol.Tag); + writer.WriteAttributeString("Version", this.BundleSymbol.Version); + writer.WriteAttributeString("ProviderKey", this.BundleSymbol.ProviderKey); + + writer.WriteStartElement("Arp"); + writer.WriteAttributeString("Register", (this.BundleSymbol.DisableModify || this.BundleSymbol.SingleChangeUninstallButton) && this.BundleSymbol.DisableRemove ? "no" : "yes"); // do not register if disabled modify and remove. + writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); + writer.WriteAttributeString("DisplayVersion", this.BundleSymbol.Version); + + if (!String.IsNullOrEmpty(this.BundleSymbol.Manufacturer)) + { + writer.WriteAttributeString("Publisher", this.BundleSymbol.Manufacturer); + } + + if (!String.IsNullOrEmpty(this.BundleSymbol.HelpUrl)) + { + writer.WriteAttributeString("HelpLink", this.BundleSymbol.HelpUrl); + } + + if (!String.IsNullOrEmpty(this.BundleSymbol.HelpTelephone)) + { + writer.WriteAttributeString("HelpTelephone", this.BundleSymbol.HelpTelephone); + } + + if (!String.IsNullOrEmpty(this.BundleSymbol.AboutUrl)) + { + writer.WriteAttributeString("AboutUrl", this.BundleSymbol.AboutUrl); + } + + if (!String.IsNullOrEmpty(this.BundleSymbol.UpdateUrl)) + { + writer.WriteAttributeString("UpdateUrl", this.BundleSymbol.UpdateUrl); + } + + if (!String.IsNullOrEmpty(this.BundleSymbol.ParentName)) + { + writer.WriteAttributeString("ParentDisplayName", this.BundleSymbol.ParentName); + } + + if (this.BundleSymbol.DisableModify) + { + writer.WriteAttributeString("DisableModify", "yes"); + } + + if (this.BundleSymbol.DisableRemove) + { + writer.WriteAttributeString("DisableRemove", "yes"); + } + + if (this.BundleSymbol.SingleChangeUninstallButton) + { + writer.WriteAttributeString("DisableModify", "button"); + } + writer.WriteEndElement(); // + + // Get update registration if specified. + var updateRegistrationInfo = this.Section.Symbols.OfType().FirstOrDefault(); + + if (null != updateRegistrationInfo) + { + writer.WriteStartElement("Update"); // + writer.WriteAttributeString("Manufacturer", updateRegistrationInfo.Manufacturer); + + if (!String.IsNullOrEmpty(updateRegistrationInfo.Department)) + { + writer.WriteAttributeString("Department", updateRegistrationInfo.Department); + } + + if (!String.IsNullOrEmpty(updateRegistrationInfo.ProductFamily)) + { + writer.WriteAttributeString("ProductFamily", updateRegistrationInfo.ProductFamily); + } + + writer.WriteAttributeString("Name", updateRegistrationInfo.Name); + writer.WriteAttributeString("Classification", updateRegistrationInfo.Classification); + writer.WriteEndElement(); // + } + + foreach (var bundleTagSymbol in this.Section.Symbols.OfType()) + { + writer.WriteStartElement("SoftwareTag"); + writer.WriteAttributeString("Filename", bundleTagSymbol.Filename); + writer.WriteAttributeString("Regid", bundleTagSymbol.Regid); + writer.WriteAttributeString("Path", bundleTagSymbol.InstallPath); + writer.WriteCData(bundleTagSymbol.Xml); + writer.WriteEndElement(); + } + + writer.WriteEndElement(); // + + // write the Chain... + writer.WriteStartElement("Chain"); + if (this.Chain.DisableRollback) + { + writer.WriteAttributeString("DisableRollback", "yes"); + } + + if (this.Chain.DisableSystemRestore) + { + writer.WriteAttributeString("DisableSystemRestore", "yes"); + } + + if (this.Chain.ParallelCache) + { + writer.WriteAttributeString("ParallelCache", "yes"); + } + + // Index a few tables by package. + var targetCodesByPatch = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); + var msiFeaturesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); + var msiPropertiesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); + var relatedPackagesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.PackageRef); + var slipstreamMspsByPackage = this.Section.Symbols.OfType().ToLookup(r => r.TargetPackageRef); + var exitCodesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.ChainPackageId); + var commandLinesByPackage = this.Section.Symbols.OfType().ToLookup(r => r.WixBundlePackageRef); + + var dependenciesByPackage = this.Section.Symbols.OfType().ToLookup(p => p.ParentRef); + + + // Build up the list of target codes from all the MSPs in the chain. + var targetCodes = new List(); + + foreach (var package in this.OrderedPackages) + { + writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.PackageSymbol.Type)); + + writer.WriteAttributeString("Id", package.PackageId); + + switch (package.PackageSymbol.Cache) + { + case YesNoAlwaysType.No: + writer.WriteAttributeString("Cache", "remove"); + break; + case YesNoAlwaysType.Yes: + writer.WriteAttributeString("Cache", "keep"); + break; + case YesNoAlwaysType.Always: + writer.WriteAttributeString("Cache", "force"); + break; + } + + writer.WriteAttributeString("CacheId", package.PackageSymbol.CacheId); + writer.WriteAttributeString("InstallSize", Convert.ToString(package.PackageSymbol.InstallSize)); + writer.WriteAttributeString("Size", Convert.ToString(package.PackageSymbol.Size)); + writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.PackageSymbol.PerMachine ? "yes" : "no"); + writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); + writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == false ? "no" : "yes"); + + if (null != package.PackageSymbol.RollbackBoundaryRef) + { + writer.WriteAttributeString("RollbackBoundaryForward", package.PackageSymbol.RollbackBoundaryRef); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackBoundaryBackwardRef)) + { + writer.WriteAttributeString("RollbackBoundaryBackward", package.PackageSymbol.RollbackBoundaryBackwardRef); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.LogPathVariable)) + { + writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackLogPathVariable)) + { + writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); + } + + if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) + { + writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); + } + + if (package.SpecificPackageSymbol is WixBundleExePackageSymbol exePackage) // EXE + { + writer.WriteAttributeString("DetectCondition", exePackage.DetectCondition); + writer.WriteAttributeString("InstallArguments", exePackage.InstallCommand); + writer.WriteAttributeString("UninstallArguments", exePackage.UninstallCommand); + writer.WriteAttributeString("RepairArguments", exePackage.RepairCommand); + writer.WriteAttributeString("Repairable", exePackage.Repairable ? "yes" : "no"); + if (!String.IsNullOrEmpty(exePackage.ExeProtocol)) + { + writer.WriteAttributeString("Protocol", exePackage.ExeProtocol); + } + } + else if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) // MSI + { + writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); + writer.WriteAttributeString("Language", msiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Version", msiPackage.ProductVersion); + if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); + } + } + else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) // MSP + { + writer.WriteAttributeString("PatchCode", mspPackage.PatchCode); + writer.WriteAttributeString("PatchXml", mspPackage.PatchXml); + + // If there is still a chance that all of our patches will target a narrow set of + // product codes, add the patch list to the overall list. + if (null != targetCodes) + { + if (!mspPackage.TargetUnspecified) + { + var patchTargetCodes = targetCodesByPatch[mspPackage.Id.Id]; + + targetCodes.AddRange(patchTargetCodes); + } + else // we have a patch that targets the world, so throw the whole list away. + { + targetCodes = null; + } + } + } + else if (package.SpecificPackageSymbol is WixBundleMsuPackageSymbol msuPackage) // MSU + { + writer.WriteAttributeString("DetectCondition", msuPackage.DetectCondition); + writer.WriteAttributeString("KB", msuPackage.MsuKB); + } + + var packageMsiFeatures = msiFeaturesByPackage[package.PackageId]; + + foreach (var feature in packageMsiFeatures) + { + writer.WriteStartElement("MsiFeature"); + writer.WriteAttributeString("Id", feature.Name); + writer.WriteEndElement(); + } + + var packageMsiProperties = msiPropertiesByPackage[package.PackageId]; + + foreach (var msiProperty in packageMsiProperties) + { + writer.WriteStartElement("MsiProperty"); + writer.WriteAttributeString("Id", msiProperty.Name); + writer.WriteAttributeString("Value", msiProperty.Value); + if (!String.IsNullOrEmpty(msiProperty.Condition)) + { + writer.WriteAttributeString("Condition", msiProperty.Condition); + } + writer.WriteEndElement(); + } + + var packageSlipstreamMsps = slipstreamMspsByPackage[package.PackageId]; + + foreach (var slipstreamMsp in packageSlipstreamMsps) + { + writer.WriteStartElement("SlipstreamMsp"); + writer.WriteAttributeString("Id", slipstreamMsp.MspPackageRef); + writer.WriteEndElement(); + } + + var packageExitCodes = exitCodesByPackage[package.PackageId]; + + foreach (var exitCode in packageExitCodes) + { + writer.WriteStartElement("ExitCode"); + + if (exitCode.Code.HasValue) + { + writer.WriteAttributeString("Code", unchecked((uint)exitCode.Code).ToString(CultureInfo.InvariantCulture)); + } + else + { + writer.WriteAttributeString("Code", "*"); + } + + writer.WriteAttributeString("Type", ((int)exitCode.Behavior).ToString(CultureInfo.InvariantCulture)); + writer.WriteEndElement(); + } + + var packageCommandLines = commandLinesByPackage[package.PackageId]; + + foreach (var commandLine in packageCommandLines) + { + writer.WriteStartElement("CommandLine"); + writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument); + writer.WriteAttributeString("UninstallArgument", commandLine.UninstallArgument); + writer.WriteAttributeString("RepairArgument", commandLine.RepairArgument); + writer.WriteAttributeString("Condition", commandLine.Condition); + writer.WriteEndElement(); + } + + // Output the dependency information. + var dependencies = dependenciesByPackage[package.PackageId]; + + foreach (var dependency in dependencies) + { + writer.WriteStartElement("Provides"); + writer.WriteAttributeString("Key", dependency.ProviderKey); + + if (!String.IsNullOrEmpty(dependency.Version)) + { + writer.WriteAttributeString("Version", dependency.Version); + } + + if (!String.IsNullOrEmpty(dependency.DisplayName)) + { + writer.WriteAttributeString("DisplayName", dependency.DisplayName); + } + + if (dependency.Imported) + { + // The package dependency was explicitly authored into the manifest. + writer.WriteAttributeString("Imported", "yes"); + } + + writer.WriteEndElement(); + } + + var packageRelatedPackages = relatedPackagesByPackage[package.PackageId]; + + foreach (var related in packageRelatedPackages) + { + writer.WriteStartElement("RelatedPackage"); + writer.WriteAttributeString("Id", related.RelatedId); + if (!String.IsNullOrEmpty(related.MinVersion)) + { + writer.WriteAttributeString("MinVersion", related.MinVersion); + writer.WriteAttributeString("MinInclusive", related.MinInclusive ? "yes" : "no"); + } + if (!String.IsNullOrEmpty(related.MaxVersion)) + { + writer.WriteAttributeString("MaxVersion", related.MaxVersion); + writer.WriteAttributeString("MaxInclusive", related.MaxInclusive ? "yes" : "no"); + } + writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no"); + + var relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (0 < relatedLanguages.Length) + { + writer.WriteAttributeString("LangInclusive", related.LangInclusive ? "yes" : "no"); + foreach (string language in relatedLanguages) + { + writer.WriteStartElement("Language"); + writer.WriteAttributeString("Id", language); + writer.WriteEndElement(); + } + } + writer.WriteEndElement(); + } + + // Write any contained Payloads with the PackagePayload being first + var packagePayloadId = package.PackageSymbol.PayloadRef; + writer.WriteStartElement("PayloadRef"); + writer.WriteAttributeString("Id", packagePayloadId); + writer.WriteEndElement(); + + var packagePayloads = this.PackagesPayloads[package.PackageId]; + + foreach (var payload in packagePayloads.Values) + { + if (payload.Id.Id != packagePayloadId) + { + writer.WriteStartElement("PayloadRef"); + writer.WriteAttributeString("Id", payload.Id.Id); + writer.WriteEndElement(); + } + } + + writer.WriteEndElement(); // + } + writer.WriteEndElement(); // + + if (null != targetCodes) + { + foreach (var targetCode in targetCodes) + { + writer.WriteStartElement("PatchTargetCode"); + writer.WriteAttributeString("TargetCode", targetCode.TargetCode); + writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no"); + writer.WriteEndElement(); + } + } + + // Write the ApprovedExeForElevation elements. + var approvedExesForElevation = this.Section.Symbols.OfType(); + + foreach (var approvedExeForElevation in approvedExesForElevation) + { + writer.WriteStartElement("ApprovedExeForElevation"); + writer.WriteAttributeString("Id", approvedExeForElevation.Id.Id); + writer.WriteAttributeString("Key", approvedExeForElevation.Key); + + if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName)) + { + writer.WriteAttributeString("ValueName", approvedExeForElevation.ValueName); + } + + if (approvedExeForElevation.Win64) + { + writer.WriteAttributeString("Win64", "yes"); + } + + writer.WriteEndElement(); + } + + // Write the BundleExtension elements. + var bundleExtensions = this.Section.Symbols.OfType(); + + foreach (var bundleExtension in bundleExtensions) + { + writer.WriteStartElement("BundleExtension"); + writer.WriteAttributeString("Id", bundleExtension.Id.Id); + writer.WriteAttributeString("EntryPayloadId", bundleExtension.PayloadRef); + + writer.WriteEndElement(); + } + + writer.WriteEndDocument(); // + } + } + + private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerSymbol container) + { + writer.WriteAttributeString("Id", container.Id.Id); + writer.WriteAttributeString("FileSize", container.Size.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Hash", container.Hash); + + if (ContainerType.Detached == container.Type) + { + if (!String.IsNullOrEmpty(container.DownloadUrl)) + { + writer.WriteAttributeString("DownloadUrl", container.DownloadUrl); + } + + writer.WriteAttributeString("FilePath", container.Name); + } + else if (ContainerType.Attached == container.Type) + { + writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. + writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Attached", "yes"); + writer.WriteAttributeString("Primary", "yes"); + } + } + + private void WriteBurnManifestPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) + { + writer.WriteStartElement("Payload"); + + writer.WriteAttributeString("Id", payload.Id.Id); + writer.WriteAttributeString("FilePath", payload.Name); + writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Hash", payload.Hash); + + if (payload.LayoutOnly) + { + writer.WriteAttributeString("LayoutOnly", "yes"); + } + + if (!String.IsNullOrEmpty(payload.DownloadUrl)) + { + writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); + } + + switch (payload.Packaging) + { + case PackagingType.Embedded: // this means it's in a container. + Debug.Assert(BurnConstants.BurnUXContainerName != payload.ContainerRef); + + writer.WriteAttributeString("Packaging", "embedded"); + writer.WriteAttributeString("SourcePath", payload.EmbeddedId); + writer.WriteAttributeString("Container", payload.ContainerRef); + break; + + case PackagingType.External: + writer.WriteAttributeString("Packaging", "external"); + writer.WriteAttributeString("SourcePath", payload.Name); + break; + } + + writer.WriteEndElement(); + } + + private void WriteBurnManifestUXPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) + { + Debug.Assert(PackagingType.Embedded == payload.Packaging); + Debug.Assert(BurnConstants.BurnUXContainerName == payload.ContainerRef); + + writer.WriteStartElement("Payload"); + + // TODO: The engine should be updated to not require FileSize, Hash, or Packaging for UX payloads since the values are never used. + writer.WriteAttributeString("Id", payload.Id.Id); + writer.WriteAttributeString("FilePath", payload.Name); + writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteAttributeString("Hash", payload.Hash); + writer.WriteAttributeString("Packaging", "embedded"); + writer.WriteAttributeString("SourcePath", payload.EmbeddedId); + + writer.WriteEndElement(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs new file mode 100644 index 00000000..87a63cc3 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs @@ -0,0 +1,70 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Creates cabinet files. + /// + internal class CreateContainerCommand + { + public CreateContainerCommand(IEnumerable payloads, string outputPath, CompressionLevel? compressionLevel) + { + this.Payloads = payloads; + this.OutputPath = outputPath; + this.CompressionLevel = compressionLevel; + } + + public CreateContainerCommand(string manifestPath, IEnumerable payloads, string outputPath, CompressionLevel? compressionLevel) + { + this.ManifestFile = manifestPath; + this.Payloads = payloads; + this.OutputPath = outputPath; + this.CompressionLevel = compressionLevel; + } + + private CompressionLevel? CompressionLevel { get; } + + private string ManifestFile { get; } + + private string OutputPath { get; } + + private IEnumerable Payloads { get; } + + public string Hash { get; private set; } + + public long Size { get; private set; } + + public void Execute() + { + var cabinetPath = Path.GetFullPath(this.OutputPath); + + var files = new List(); + + // If a manifest was provided always add it as "payload 0" to the container. + if (!String.IsNullOrEmpty(this.ManifestFile)) + { + files.Add(new CabinetCompressFile(this.ManifestFile, "0")); + } + + files.AddRange(this.Payloads.Select(p => new CabinetCompressFile(p.SourceFile.Path, p.EmbeddedId))); + + var cab = new Cabinet(cabinetPath); + cab.Compress(files, this.CompressionLevel ?? Data.CompressionLevel.Medium); + + // Now that the container is created, set the outputs of the command. + var fileInfo = new FileInfo(cabinetPath); + + this.Hash = BundleHashAlgorithm.Hash(fileInfo); + + this.Size = fileInfo.Length; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs new file mode 100644 index 00000000..f020ed84 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs @@ -0,0 +1,151 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CreateNonUXContainers + { + public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable containerSymbols, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) + { + this.BackendHelper = backendHelper; + this.Messaging = messaging; + this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol; + this.Containers = containerSymbols; + this.PayloadSymbols = payloadSymbols; + this.IntermediateFolder = intermediateFolder; + this.LayoutFolder = layoutFolder; + this.DefaultCompressionLevel = defaultCompressionLevel; + } + + public IEnumerable FileTransfers { get; private set; } + + public IEnumerable TrackedFiles { get; private set; } + + public WixBundleContainerSymbol UXContainer { get; set; } + + public IEnumerable UXContainerPayloads { get; private set; } + + private IEnumerable Containers { get; } + + private IBackendHelper BackendHelper { get; } + + private IMessaging Messaging { get; } + + private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } + + private Dictionary PayloadSymbols { get; } + + private string IntermediateFolder { get; } + + private string LayoutFolder { get; } + + private CompressionLevel? DefaultCompressionLevel { get; } + + public void Execute() + { + var fileTransfers = new List(); + var trackedFiles = new List(); + var uxPayloadSymbols = new List(); + + var attachedContainerIndex = 1; // count starts at one because UX container is "0". + + var payloadsByContainer = this.PayloadSymbols.Values.ToLookup(p => p.ContainerRef); + + foreach (var container in this.Containers) + { + var containerId = container.Id.Id; + + var containerPayloads = payloadsByContainer[containerId]; + + if (!containerPayloads.Any()) + { + if (containerId != BurnConstants.BurnDefaultAttachedContainerName) + { + this.Messaging.Write(BurnBackendWarnings.EmptyContainer(container.SourceLineNumbers, containerId)); + } + } + else if (BurnConstants.BurnUXContainerName == containerId) + { + this.UXContainer = container; + + container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + container.AttachedContainerIndex = 0; + + // Gather the list of UX payloads but ensure the BootstrapperApplicationDll Payload is the first + // in the list since that is the Payload that Burn attempts to load. + var baPayloadId = this.BootstrapperApplicationDllSymbol.Id.Id; + + foreach (var uxPayload in containerPayloads) + { + if (uxPayload.Id.Id == baPayloadId) + { + uxPayloadSymbols.Insert(0, uxPayload); + } + else + { + uxPayloadSymbols.Add(uxPayload); + } + } + } + else + { + container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); + + // Add detached containers to the list of file transfers. + if (ContainerType.Detached == container.Type) + { + var transfer = this.BackendHelper.CreateFileTransfer(container.WorkingPath, Path.Combine(this.LayoutFolder, container.Name), true, container.SourceLineNumbers); + fileTransfers.Add(transfer); + } + else // update the attached container index. + { + Debug.Assert(ContainerType.Attached == container.Type); + + container.AttachedContainerIndex = attachedContainerIndex; + ++attachedContainerIndex; + } + } + } + + foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName)) + { + if (container.Type == ContainerType.Attached && attachedContainerIndex > 2 && container.Id.Id != BurnConstants.BurnDefaultAttachedContainerName) + { + this.Messaging.Write(BurnBackendErrors.MultipleAttachedContainersUnsupported(container.SourceLineNumbers, container.Id.Id)); + } + } + + if (!this.Messaging.EncounteredError) + { + foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName)) + { + this.CreateContainer(container, payloadsByContainer[container.Id.Id]); + trackedFiles.Add(this.BackendHelper.TrackFile(container.WorkingPath, TrackedFileType.Temporary, container.SourceLineNumbers)); + } + } + + this.UXContainerPayloads = uxPayloadSymbols; + this.FileTransfers = fileTransfers; + this.TrackedFiles = trackedFiles; + } + + private void CreateContainer(WixBundleContainerSymbol container, IEnumerable containerPayloads) + { + var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); + command.Execute(); + + container.Hash = command.Hash; + container.Size = command.Size; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs new file mode 100644 index 00000000..bfb6b918 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs @@ -0,0 +1,137 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class DetectPayloadCollisionsCommand + { + public DetectPayloadCollisionsCommand(IMessaging messaging, Dictionary containerSymbols, IEnumerable packages, Dictionary payloadSymbols, Dictionary> packagePayloads) + { + this.Messaging = messaging; + this.Containers = containerSymbols; + this.Packages = packages; + this.PayloadSymbols = payloadSymbols; + this.PackagePayloads = packagePayloads; + } + + private IMessaging Messaging { get; } + + private Dictionary Containers { get; } + + private IEnumerable Packages { get; } + + private Dictionary PayloadSymbols { get; } + + private Dictionary> PackagePayloads { get; } + + public void Execute() + { + this.DetectAttachedContainerCollisions(); + this.DetectExternalCollisions(); + this.DetectPackageCacheCollisions(); + } + + public void DetectAttachedContainerCollisions() + { + var attachedContainerPayloadsByNameByContainer = new Dictionary>(); + + foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.Embedded)) + { + var containerId = payload.ContainerRef; + var container = this.Containers[containerId]; + if (container.Type == ContainerType.Attached) + { + if (!attachedContainerPayloadsByNameByContainer.TryGetValue(containerId, out var attachedContainerPayloadsByName)) + { + attachedContainerPayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + attachedContainerPayloadsByNameByContainer.Add(containerId, attachedContainerPayloadsByName); + } + + if (!attachedContainerPayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) + { + attachedContainerPayloadsByName.Add(payload.Name, payload); + } + else + { + if (containerId == BurnConstants.BurnUXContainerName) + { + this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); + this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); + } + else + { + this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name)); + this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision2(collisionPayload.SourceLineNumbers)); + } + } + } + } + } + + public void DetectExternalCollisions() + { + var externalPayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.External)) + { + if (!externalPayloadsByName.TryGetValue(payload.Name, out var collisionSymbol)) + { + externalPayloadsByName.Add(payload.Name, payload); + } + else + { + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(payload.SourceLineNumbers, "Payload", payload.Id.Id, payload.Name)); + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); + } + } + + foreach (var container in this.Containers.Values.Where(c => c.Type == ContainerType.Detached)) + { + if (!externalPayloadsByName.TryGetValue(container.Name, out var collisionSymbol)) + { + externalPayloadsByName.Add(container.Name, container); + } + else + { + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(container.SourceLineNumbers, "Container", container.Id.Id, container.Name)); + this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers)); + } + } + } + + public void DetectPackageCacheCollisions() + { + var packageCachePayloadsByNameByCacheId = new Dictionary>(); + + foreach (var packageFacade in this.Packages) + { + var packagePayloads = this.PackagePayloads[packageFacade.PackageId]; + if (!packageCachePayloadsByNameByCacheId.TryGetValue(packageFacade.PackageSymbol.CacheId, out var packageCachePayloadsByName)) + { + packageCachePayloadsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + packageCachePayloadsByNameByCacheId.Add(packageFacade.PackageSymbol.CacheId, packageCachePayloadsByName); + } + + foreach (var payload in packagePayloads.Values) + { + if (!packageCachePayloadsByName.TryGetValue(payload.Name, out var collisionPayload)) + { + packageCachePayloadsByName.Add(payload.Name, payload); + } + else + { + this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name, packageFacade.PackageId)); + this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision2(collisionPayload.SourceLineNumbers)); + } + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs new file mode 100644 index 00000000..b8b256fd --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs @@ -0,0 +1,181 @@ +// 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.Burn.Bundles +{ + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class GetPackageFacadesCommand + { + public GetPackageFacadesCommand(IMessaging messaging, IEnumerable chainPackageSymbols, IntermediateSection section) + { + this.Messaging = messaging; + this.ChainPackageSymbols = chainPackageSymbols; + this.Section = section; + } + + private IEnumerable ChainPackageSymbols { get; } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public IDictionary PackageFacades { get; private set; } + + public void Execute() + { + var wixGroupPackagesGroupedById = this.Section.Symbols.OfType().Where(g => g.ParentType == ComplexReferenceParentType.Package).ToLookup(g => g.ParentId); + var exePackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var msiPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var mspPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var msuPackages = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var exePackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var msiPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var mspPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + var msuPackagePayloads = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + + var facades = new Dictionary(); + + foreach (var package in this.ChainPackageSymbols) + { + var id = package.Id.Id; + + IntermediateSymbol packagePayload = null; + foreach (var wixGroup in wixGroupPackagesGroupedById[id]) + { + if (wixGroup.ChildType == ComplexReferenceChildType.PackagePayload) + { + IntermediateSymbol tempPackagePayload = null; + if (exePackagePayloads.TryGetValue(wixGroup.ChildId, out var exePackagePayload)) + { + if (package.Type == WixBundlePackageType.Exe) + { + tempPackagePayload = exePackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(exePackagePayload.SourceLineNumbers, "Exe")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else if (msiPackagePayloads.TryGetValue(wixGroup.ChildId, out var msiPackagePayload)) + { + if (package.Type == WixBundlePackageType.Msi) + { + tempPackagePayload = msiPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msiPackagePayload.SourceLineNumbers, "Msi")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else if (mspPackagePayloads.TryGetValue(wixGroup.ChildId, out var mspPackagePayload)) + { + if (package.Type == WixBundlePackageType.Msp) + { + tempPackagePayload = mspPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(mspPackagePayload.SourceLineNumbers, "Msp")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else if (msuPackagePayloads.TryGetValue(wixGroup.ChildId, out var msuPackagePayload)) + { + if (package.Type == WixBundlePackageType.Msu) + { + tempPackagePayload = msuPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msuPackagePayload.SourceLineNumbers, "Msu")); + this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers)); + } + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound(package.Type + "PackagePayload", wixGroup.ChildId)); + } + + if (tempPackagePayload != null) + { + if (packagePayload == null) + { + packagePayload = tempPackagePayload; + } + else + { + this.Messaging.Write(ErrorMessages.MultiplePackagePayloads(tempPackagePayload.SourceLineNumbers, id, packagePayload.Id.Id, tempPackagePayload.Id.Id)); + this.Messaging.Write(ErrorMessages.MultiplePackagePayloads2(packagePayload.SourceLineNumbers)); + this.Messaging.Write(ErrorMessages.MultiplePackagePayloads3(package.SourceLineNumbers)); + } + } + } + } + + if (packagePayload == null) + { + this.Messaging.Write(ErrorMessages.MissingPackagePayload(package.SourceLineNumbers, id, package.Type.ToString())); + } + else + { + package.PayloadRef = packagePayload.Id.Id; + } + + switch (package.Type) + { + case WixBundlePackageType.Exe: + if (exePackages.TryGetValue(id, out var exePackage)) + { + facades.Add(id, new PackageFacade(package, exePackage)); + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleExePackage", id)); + } + break; + + case WixBundlePackageType.Msi: + if (msiPackages.TryGetValue(id, out var msiPackage)) + { + facades.Add(id, new PackageFacade(package, msiPackage)); + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsiPackage", id)); + } + break; + + case WixBundlePackageType.Msp: + if (mspPackages.TryGetValue(id, out var mspPackage)) + { + facades.Add(id, new PackageFacade(package, mspPackage)); + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMspPackage", id)); + } + break; + + case WixBundlePackageType.Msu: + if (msuPackages.TryGetValue(id, out var msuPackage)) + { + facades.Add(id, new PackageFacade(package, msuPackage)); + } + else + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsuPackage", id)); + } + break; + } + } + + this.PackageFacades = facades; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs new file mode 100644 index 00000000..ccf6b1c2 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs @@ -0,0 +1,171 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class OrderPackagesAndRollbackBoundariesCommand + { + public OrderPackagesAndRollbackBoundariesCommand(IMessaging messaging, IntermediateSection section, IDictionary packageFacades) + { + this.Messaging = messaging; + this.Section = section; + this.PackageFacades = packageFacades; + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private IDictionary PackageFacades { get; } + + public IEnumerable OrderedPackageFacades { get; private set; } + + public IEnumerable UsedRollbackBoundaries { get; private set; } + + public void Execute() + { + var groupSymbols = this.Section.Symbols.OfType().ToList(); + var boundariesById = this.Section.Symbols.OfType().ToDictionary(b => b.Id.Id); + + var orderedFacades = new List(); + var usedBoundaries = new List(); + + // Process the chain of packages to add them in the correct order + // and assign the forward rollback boundaries as appropriate. Remember + // rollback boundaries are authored as elements in the chain which + // we re-interpret here to add them as attributes on the next available + // package in the chain. Essentially we mark some packages as being + // the start of a rollback boundary when installing and repairing. + // We handle uninstall (aka: backwards) rollback boundaries after + // we get these install/repair (aka: forward) rollback boundaries + // defined. + var pendingRollbackBoundary = new WixBundleRollbackBoundarySymbol(null, new Identifier(AccessModifier.Section, BurnConstants.BundleDefaultBoundaryId)) { Vital = true }; + var lastRollbackBoundary = pendingRollbackBoundary; + var boundaryHadX86Package = false; + var warnedMsiTransaction = false; + + foreach (var groupSymbol in groupSymbols) + { + if (ComplexReferenceChildType.Package == groupSymbol.ChildType && ComplexReferenceParentType.PackageGroup == groupSymbol.ParentType && BurnConstants.BundleChainPackageGroupId == groupSymbol.ParentId) + { + if (this.PackageFacades.TryGetValue(groupSymbol.ChildId, out var facade)) + { + var insideMsiTransaction = lastRollbackBoundary?.Transaction ?? false; + + if (null != pendingRollbackBoundary) + { + // If we used the default boundary, ensure the symbol is added to the section. + if (pendingRollbackBoundary.Id.Id == BurnConstants.BundleDefaultBoundaryId) + { + this.Section.AddSymbol(pendingRollbackBoundary); + } + + if (insideMsiTransaction && !warnedMsiTransaction) + { + warnedMsiTransaction = true; + this.Messaging.Write(WarningMessages.MsiTransactionLimitations(pendingRollbackBoundary.SourceLineNumbers)); + } + + usedBoundaries.Add(pendingRollbackBoundary); + facade.PackageSymbol.RollbackBoundaryRef = pendingRollbackBoundary.Id.Id; + pendingRollbackBoundary = null; + + boundaryHadX86Package = !facade.PackageSymbol.Win64; + } + + // Error if MSI transaction has x86 package preceding x64 packages + if (insideMsiTransaction && boundaryHadX86Package && facade.PackageSymbol.Win64) + { + this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64(facade.PackageSymbol.SourceLineNumbers)); + } + + boundaryHadX86Package |= !facade.PackageSymbol.Win64; + + orderedFacades.Add(facade); + } + else // must be a rollback boundary. + { + // Discard the next rollback boundary if we have a previously defined boundary. + var nextRollbackBoundary = boundariesById[groupSymbol.ChildId]; + if (null != pendingRollbackBoundary) + { + if (pendingRollbackBoundary.Id.Id != BurnConstants.BundleDefaultBoundaryId) + { + this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id.Id)); + } + } + + lastRollbackBoundary = pendingRollbackBoundary = nextRollbackBoundary; + } + } + } + + if (null != pendingRollbackBoundary) + { + this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(pendingRollbackBoundary.SourceLineNumbers, pendingRollbackBoundary.Id.Id)); + } + + // With the forward rollback boundaries assigned, we can now go + // through the packages with rollback boundaries and assign backward + // rollback boundaries. Backward rollback boundaries are used when + // the chain is going "backwards" which (AFAIK) only happens during + // uninstall. + // + // Consider the scenario with three packages: A, B and C. Packages A + // and C are marked as rollback boundary packages and package B is + // not. The naive implementation would execute the chain like this + // (numbers indicate where rollback boundaries would end up): + // install: 1 A B 2 C + // uninstall: 2 C B 1 A + // + // The uninstall chain is wrong, A and B should be grouped together + // not C and B. The fix is to label packages with a "backwards" + // rollback boundary used during uninstall. The backwards rollback + // boundaries are assigned to the package *before* the next rollback + // boundary. Using our example from above again, I'll mark the + // backwards rollback boundaries prime (aka: with '). + // install: 1 A B 1' 2 C 2' + // uninstall: 2' C 2 1' B A 1 + // + // If the marked boundaries are ignored during install you get the + // same thing as above (good) and if the non-marked boundaries are + // ignored during uninstall then A and B are correctly grouped. + // Here's what it looks like without all the markers: + // install: 1 A B 2 C + // uninstall: 2 C 1 B A + // Woot! + string previousRollbackBoundaryId = null; + PackageFacade previousFacade = null; + + foreach (var package in orderedFacades) + { + if (null != package.PackageSymbol.RollbackBoundaryRef) + { + if (null != previousFacade) + { + previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; + } + + previousRollbackBoundaryId = package.PackageSymbol.RollbackBoundaryRef; + } + + previousFacade = package; + } + + if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) + { + previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId; + } + + this.OrderedPackageFacades = orderedFacades; + this.UsedRollbackBoundaries = usedBoundaries; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs new file mode 100644 index 00000000..f3afd64e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs @@ -0,0 +1,367 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class OrderSearchesCommand + { + public OrderSearchesCommand(IMessaging messaging, IntermediateSection section) + { + this.Messaging = messaging; + this.Section = section; + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public IDictionary> ExtensionSearchSymbolsByExtensionId { get; private set; } + + public IEnumerable OrderedSearchFacades { get; private set; } + + public void Execute() + { + this.ExtensionSearchSymbolsByExtensionId = new Dictionary>(); + this.OrderedSearchFacades = Array.Empty(); + + var searchSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + if (searchSymbols.Count == 0) + { + // Nothing to do! + return; + } + + var constraints = new Constraints(); + + // Add relational info to our data... + foreach (var searchRelationSymbol in this.Section.Symbols.OfType()) + { + constraints.AddConstraint(searchRelationSymbol.Id.Id, searchRelationSymbol.ParentSearchRef); + } + + this.FindCircularReference(constraints); + + if (this.Messaging.EncounteredError) + { + return; + } + + this.FlattenDependentReferences(constraints); + + // Reorder by topographical sort (http://en.wikipedia.org/wiki/Topological_sorting) + // We use a variation of Kahn (1962) algorithm as described in + // Wikipedia, with the additional criteria that start nodes are sorted + // lexicographically at each step to ensure a deterministic ordering + // based on 'after' dependencies and ID. + var sorter = new TopologicalSort(); + var sortedIds = sorter.Sort(searchSymbols.Keys, constraints); + + // Now, create the search facades with the searches in order... + (var orderedSearchFacades, var extensionSearchSymbolsByExtensionId) = this.OrderSearches(sortedIds, searchSymbols); + + this.OrderedSearchFacades = orderedSearchFacades; + this.ExtensionSearchSymbolsByExtensionId = extensionSearchSymbolsByExtensionId; + } + + /// + /// A dictionary of constraints, mapping an id to a list of ids. + /// + private class Constraints : Dictionary> + { + public void AddConstraint(string id, string afterId) + { + if (!this.ContainsKey(id)) + { + this.Add(id, new List()); + } + + // TODO: Show warning if a constraint is seen twice? + if (!this[id].Contains(afterId)) + { + this[id].Add(afterId); + } + } + + // TODO: Hide other Add methods? + } + + /// + /// Finds circular references in the constraints. + /// + /// Constraints to check. + /// This is not particularly performant, but it works. + private void FindCircularReference(Constraints constraints) + { + foreach (var id in constraints.Keys) + { + var seenIds = new List(); + + if (this.FindCircularReference(constraints, id, id, seenIds, out var chain)) + { + // We will show a separate message for every ID that's in + // the loop. We could bail after the first one, but then + // we wouldn't catch disjoint loops in a single run. + this.Messaging.Write(ErrorMessages.CircularSearchReference(chain)); + } + } + } + + /// + /// Recursive function that finds circular references in the constraints. + /// + /// Constraints to check. + /// The identifier currently being looking for. (Fixed across a given run.) + /// The idenifier curently being tested. + /// A list of identifiers seen, to ensure each identifier is only expanded once. + /// If a circular reference is found, will contain the chain of references. + /// True if a circular reference is found, false otherwise. + private bool FindCircularReference(Constraints constraints, string checkId, string currentId, List seenIds, out string chain) + { + chain = null; + if (constraints.TryGetValue(currentId, out var afterList)) + { + foreach (string afterId in afterList) + { + if (afterId == checkId) + { + chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, afterId); + return true; + } + + if (!seenIds.Contains(afterId)) + { + seenIds.Add(afterId); + if (this.FindCircularReference(constraints, checkId, afterId, seenIds, out chain)) + { + chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, chain); + return true; + } + } + } + } + + return false; + } + + /// + /// Flattens any dependency chains to simplify reordering. + /// + /// + private void FlattenDependentReferences(Constraints constraints) + { + foreach (string id in constraints.Keys) + { + var flattenedIds = new List(); + this.AddDependentReferences(constraints, id, flattenedIds); + var constraintList = constraints[id]; + foreach (var flattenedId in flattenedIds) + { + if (!constraintList.Contains(flattenedId)) + { + constraintList.Add(flattenedId); + } + } + } + } + + /// + /// Adds dependent references to a list. + /// + /// + /// + /// + private void AddDependentReferences(Constraints constraints, string currentId, List seenIds) + { + if (constraints.TryGetValue(currentId, out var afterList)) + { + foreach (var afterId in afterList) + { + if (!seenIds.Contains(afterId)) + { + seenIds.Add(afterId); + this.AddDependentReferences(constraints, afterId, seenIds); + } + } + } + } + + /// + /// Reorder by topological sort + /// + /// + /// We use a variation of Kahn (1962) algorithm as described in + /// Wikipedia (http://en.wikipedia.org/wiki/Topological_sorting), with + /// the additional criteria that start nodes are sorted lexicographically + /// at each step to ensure a deterministic ordering based on 'after' + /// dependencies and ID. + /// + private class TopologicalSort + { + private readonly List startIds = new List(); + private Constraints constraints; + + /// + /// Reorder by topological sort + /// + /// The complete list of IDs. + /// Constraints to use. + /// The topologically sorted list of IDs. + internal List Sort(IEnumerable allIds, Constraints constraints) + { + this.startIds.Clear(); + this.CopyConstraints(constraints); + + this.FindInitialStartIds(allIds); + + // We always create a new sortedId list, because we return it + // to the caller and don't know what its lifetime may be. + var sortedIds = new List(); + + while (this.startIds.Count > 0) + { + this.SortStartIds(); + + var currentId = this.startIds[0]; + sortedIds.Add(currentId); + this.startIds.RemoveAt(0); + + this.ResolveConstraint(currentId); + } + + return sortedIds; + } + + /// + /// Copies a Constraints set (to prevent modifying the incoming data). + /// + /// Constraints to copy. + private void CopyConstraints(Constraints constraints) + { + this.constraints = new Constraints(); + foreach (var id in constraints.Keys) + { + foreach (var afterId in constraints[id]) + { + this.constraints.AddConstraint(id, afterId); + } + } + } + + /// + /// Finds initial start IDs. (Those with no constraints.) + /// + /// The complete list of IDs. + private void FindInitialStartIds(IEnumerable allIds) + { + foreach (var id in allIds) + { + if (!this.constraints.ContainsKey(id)) + { + this.startIds.Add(id); + } + } + } + + /// + /// Sorts start IDs. + /// + private void SortStartIds() + { + this.startIds.Sort(); + } + + /// + /// Removes the resolved constraint and updates the list of startIds + /// with any now-valid (all constraints resolved) IDs. + /// + /// The ID to resolve from the set of constraints. + private void ResolveConstraint(string resolvedId) + { + var newStartIds = new List(); + + foreach (var id in this.constraints.Keys) + { + if (this.constraints[id].Contains(resolvedId)) + { + this.constraints[id].Remove(resolvedId); + + // If we just removed the last constraint for this + // ID, it is now a valid start ID. + if (this.constraints[id].Count == 0) + { + newStartIds.Add(id); + } + } + } + + foreach (var id in newStartIds) + { + this.constraints.Remove(id); + } + + this.startIds.AddRange(newStartIds); + } + } + + private (IEnumerable, Dictionary>) OrderSearches(IEnumerable sortedIds, Dictionary searchSymbolDictionary) + { + var orderedSearchFacades = new List(); + var extensionSearchSymbolsByExtensionId = new Dictionary>(); + + // TODO: Although the WixSearch tables are defined in the Util extension, + // the Bundle Binder has to know all about them. We hope to revisit all + // of this in the 4.0 timeframe. + var legacySearchesById = this.Section.Symbols + .Where(t => t.Definition.Type == SymbolDefinitionType.WixComponentSearch || + t.Definition.Type == SymbolDefinitionType.WixFileSearch || + t.Definition.Type == SymbolDefinitionType.WixProductSearch || + t.Definition.Type == SymbolDefinitionType.WixRegistrySearch) + .ToDictionary(t => t.Id.Id); + var setVariablesById = this.Section.Symbols + .OfType() + .ToDictionary(t => t.Id.Id); + var extensionSearchesById = this.Section.Symbols + .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag)) + .ToDictionary(t => t.Id.Id); + + foreach (var searchId in sortedIds) + { + var searchSymbol = searchSymbolDictionary[searchId]; + + if (legacySearchesById.TryGetValue(searchId, out var specificSearchSymbol)) + { + orderedSearchFacades.Add(new LegacySearchFacade(searchSymbol, specificSearchSymbol)); + } + else if (setVariablesById.TryGetValue(searchId, out var setVariableSymbol)) + { + orderedSearchFacades.Add(new SetVariableSearchFacade(searchSymbol, setVariableSymbol)); + } + else if (extensionSearchesById.TryGetValue(searchId, out var extensionSearchSymbol)) + { + orderedSearchFacades.Add(new ExtensionSearchFacade(searchSymbol)); + + if (!extensionSearchSymbolsByExtensionId.TryGetValue(searchSymbol.BundleExtensionRef, out var extensionSearchSymbols)) + { + extensionSearchSymbols = new List(); + extensionSearchSymbolsByExtensionId[searchSymbol.BundleExtensionRef] = extensionSearchSymbols; + } + extensionSearchSymbols.Add(extensionSearchSymbol); + } + else + { + this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchSymbol.SourceLineNumbers, searchId)); + } + } + + return (orderedSearchFacades, extensionSearchSymbolsByExtensionId.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable)kvp.Value)); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs b/src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs new file mode 100644 index 00000000..471262de --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs @@ -0,0 +1,25 @@ +// 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.Burn.Bundles +{ + using System.Diagnostics; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class PackageFacade + { + public PackageFacade(WixBundlePackageSymbol packageSymbol, IntermediateSymbol specificPackageSymbol) + { + Debug.Assert(packageSymbol.Id.Id == specificPackageSymbol.Id.Id); + + this.PackageSymbol = packageSymbol; + this.SpecificPackageSymbol = specificPackageSymbol; + } + + public string PackageId => this.PackageSymbol.Id.Id; + + public WixBundlePackageSymbol PackageSymbol { get; } + + public IntermediateSymbol SpecificPackageSymbol { get; } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs new file mode 100644 index 00000000..8d8ea986 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs @@ -0,0 +1,39 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using WixToolset.Data.Symbols; + + /// + /// Initializes package state from the Exe contents. + /// + internal class ProcessExePackageCommand + { + public ProcessExePackageCommand(PackageFacade facade, Dictionary payloadSymbols) + { + this.AuthoredPayloads = payloadSymbols; + this.Facade = facade; + } + + public Dictionary AuthoredPayloads { get; } + + public PackageFacade Facade { get; } + + /// + /// Processes the Exe packages to add properties and payloads from the Exe packages. + /// + public void Execute() + { + var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) + { + this.Facade.PackageSymbol.CacheId = packagePayload.Hash; + } + + this.Facade.PackageSymbol.Version = packagePayload.Version; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs new file mode 100644 index 00000000..99e2eda5 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs @@ -0,0 +1,558 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Core.Native.Msi; + + /// + /// Initializes package state from the MSI contents. + /// + internal class ProcessMsiPackageCommand + { + private const string PropertySqlQuery = "SELECT `Value` FROM `Property` WHERE `Property` = ?"; + + public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary packagePayloads) + { + this.Messaging = serviceProvider.GetService(); + this.BackendHelper = serviceProvider.GetService(); + this.PathResolver = serviceProvider.GetService(); + + this.BackendExtensions = backendExtensions; + + this.PackagePayloads = packagePayloads; + this.Section = section; + this.Facade = facade; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private IEnumerable BackendExtensions { get; } + + private Dictionary PackagePayloads { get; } + + private PackageFacade Facade { get; } + + private IntermediateSection Section { get; } + + /// + /// Processes the MSI packages to add properties and payloads from the MSI packages. + /// + public void Execute() + { + var packagePayload = this.PackagePayloads[this.Facade.PackageSymbol.PayloadRef]; + + var msiPackage = (WixBundleMsiPackageSymbol)this.Facade.SpecificPackageSymbol; + + var sourcePath = packagePayload.SourceFile.Path; + var longNamesInImage = false; + var compressed = false; + try + { + using (var db = new Database(sourcePath, OpenDatabase.ReadOnly)) + { + // Read data out of the msi database... + using (var sumInfo = new SummaryInformation(db)) + { + var fileAndElevateFlags = sumInfo.GetNumericProperty(SummaryInformation.Package.FileAndElevatedFlags); + var platformsAndLanguages = sumInfo.GetProperty(SummaryInformation.Package.PlatformsAndLanguages); + + // 1 is the Word Count summary information stream bit that means + // the MSI uses short file names when set. We care about long file + // names so check when the bit is not set. + + longNamesInImage = 0 == (fileAndElevateFlags & 1); + + // 2 is the Word Count summary information stream bit that means + // files are compressed in the MSI by default when the bit is set. + compressed = 2 == (fileAndElevateFlags & 2); + + // 8 is the Word Count summary information stream bit that means + // "Elevated privileges are not required to install this package." + // in MSI 4.5 and below, if this bit is 0, elevation is required. + var perMachine = (0 == (fileAndElevateFlags & 8)); + var x64 = platformsAndLanguages.Contains("x64"); + + this.Facade.PackageSymbol.PerMachine = perMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; + this.Facade.PackageSymbol.Win64 = x64; + } + + string packageName = null; + string packageDescription = null; + string allusers = null; + string fastInstall = null; + string systemComponent = null; + + using (var view = db.OpenView(PropertySqlQuery)) + { + packageName = ProcessMsiPackageCommand.GetProperty(view, "ProductName"); + packageDescription = ProcessMsiPackageCommand.GetProperty(view, "ARPCOMMENTS"); + allusers = ProcessMsiPackageCommand.GetProperty(view, "ALLUSERS"); + fastInstall = ProcessMsiPackageCommand.GetProperty(view, "MSIFASTINSTALL"); + systemComponent = ProcessMsiPackageCommand.GetProperty(view, "ARPSYSTEMCOMPONENT"); + + msiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(view, "ProductCode"); + msiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(view, "UpgradeCode"); + msiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(view, "Manufacturer"); + msiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(view, "ProductLanguage"), CultureInfo.InvariantCulture); + msiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(view, "ProductVersion"); + } + + if (!this.BackendHelper.IsValidFourPartVersion(msiPackage.ProductVersion)) + { + // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? + string version = null; + var versionParts = msiPackage.ProductVersion.Split('.'); + var count = versionParts.Length; + if (0 < count) + { + version = versionParts[0]; + for (var i = 1; i < 4 && i < count; ++i) + { + version = String.Concat(version, ".", versionParts[i]); + } + } + + if (!String.IsNullOrEmpty(version) && this.BackendHelper.IsValidFourPartVersion(version)) + { + this.Messaging.Write(WarningMessages.VersionTruncated(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath, version)); + msiPackage.ProductVersion = version; + } + else + { + this.Messaging.Write(ErrorMessages.InvalidProductVersion(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath)); + } + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) + { + this.Facade.PackageSymbol.CacheId = String.Format("{0}v{1}", msiPackage.ProductCode, msiPackage.ProductVersion); + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName)) + { + this.Facade.PackageSymbol.DisplayName = packageName; + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description)) + { + this.Facade.PackageSymbol.Description = packageDescription; + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Version)) + { + this.Facade.PackageSymbol.Version = msiPackage.ProductVersion; + } + + var payloadNames = this.GetPayloadTargetNames(); + + var msiPropertyNames = this.GetMsiPropertyNames(packagePayload.Id.Id); + + this.SetPerMachineAppropriately(allusers, msiPackage, sourcePath); + + // Ensure the MSI package is appropriately marked visible or not. + this.SetPackageVisibility(systemComponent, msiPackage, msiPropertyNames); + + // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. + if (!String.IsNullOrEmpty(fastInstall)) + { + this.AddMsiProperty(msiPackage, "MSIFASTINSTALL", "7"); + } + + this.CreateRelatedPackages(db); + + // If feature selection is enabled, represent the Feature table in the manifest. + if ((msiPackage.Attributes & WixBundleMsiPackageAttributes.EnableFeatureSelection) == WixBundleMsiPackageAttributes.EnableFeatureSelection) + { + this.CreateMsiFeatures(db); + } + + // Add all external cabinets as package payloads. + this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames); + + // Add all external files as package payloads and calculate the total install size as the rollup of + // File table's sizes. + this.Facade.PackageSymbol.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); + + // Add all dependency providers from the MSI. + this.ImportDependencyProviders(db, msiPackage); + } + } + catch (MsiException e) + { + this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, e.Message)); + } + } + + private ISet GetPayloadTargetNames() + { + var payloadNames = this.PackagePayloads.Values.Select(p => p.Name); + + return new HashSet(payloadNames, StringComparer.OrdinalIgnoreCase); + } + + private ISet GetMsiPropertyNames(string packageId) + { + var properties = this.Section.Symbols.OfType() + .Where(p => p.PackageRef == packageId) + .Select(p => p.Name); + + return new HashSet(properties, StringComparer.Ordinal); + } + + private void SetPerMachineAppropriately(string allusers, WixBundleMsiPackageSymbol msiPackage, string sourcePath) + { + if (msiPackage.ForcePerMachine) + { + if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine) + { + this.Messaging.Write(WarningMessages.PerUserButForcingPerMachine(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); + this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. + } + + // Force ALLUSERS=1 via the MSI command-line. + this.AddMsiProperty(msiPackage, "ALLUSERS", "1"); + } + else + { + if (String.IsNullOrEmpty(allusers)) + { + // Not forced per-machine and no ALLUSERS property, flip back to per-user. + if (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) + { + this.Messaging.Write(WarningMessages.ImplicitlyPerUser(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); + this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.No; + } + } + else if (allusers.Equals("1", StringComparison.Ordinal)) + { + if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine) + { + this.Messaging.Write(ErrorMessages.PerUserButAllUsersEquals1(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath)); + } + } + else if (allusers.Equals("2", StringComparison.Ordinal)) + { + this.Messaging.Write(WarningMessages.DiscouragedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) ? "machine" : "user")); + } + else + { + this.Messaging.Write(ErrorMessages.UnsupportedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, allusers)); + } + } + } + + private void SetPackageVisibility(string systemComponent, WixBundleMsiPackageSymbol msiPackage, ISet msiPropertyNames) + { + // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. + if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) + { + var alreadyVisible = String.IsNullOrEmpty(systemComponent); + var visible = (this.Facade.PackageSymbol.Attributes & WixBundlePackageAttributes.Visible) == WixBundlePackageAttributes.Visible; + + // If not already set to the correct visibility. + if (alreadyVisible != visible) + { + this.AddMsiProperty(msiPackage, "ARPSYSTEMCOMPONENT", visible ? String.Empty : "1"); + } + } + } + + private void CreateRelatedPackages(Database db) + { + // Represent the Upgrade table as related packages. + if (db.TableExists("Upgrade")) + { + using (var view = db.OpenExecuteView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) + { + foreach (var record in view.Records) + { + var recordAttributes = record.GetInteger(5); + + var attributes = WixBundleRelatedPackageAttributes.None; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect) == WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect ? WixBundleRelatedPackageAttributes.OnlyDetect : 0; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive ? WixBundleRelatedPackageAttributes.MinInclusive : 0; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive ? WixBundleRelatedPackageAttributes.MaxInclusive : 0; + attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive ? WixBundleRelatedPackageAttributes.LangInclusive : 0; + + this.Section.AddSymbol(new WixBundleRelatedPackageSymbol(this.Facade.PackageSymbol.SourceLineNumbers) + { + PackageRef = this.Facade.PackageId, + RelatedId = record.GetString(1), + MinVersion = record.GetString(2), + MaxVersion = record.GetString(3), + Languages = record.GetString(4), + Attributes = attributes, + }); + } + } + } + } + + private void CreateMsiFeatures(Database db) + { + if (db.TableExists("Feature")) + { + using (var allFeaturesView = db.OpenExecuteView("SELECT * FROM `Feature`")) + using (var featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) + using (var componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) + { + using (var featureRecord = new Record(1)) + using (var componentRecord = new Record(1)) + { + foreach (var allFeaturesResultRecord in allFeaturesView.Records) + { + var featureName = allFeaturesResultRecord.GetString(1); + + // Calculate the Feature size. + featureRecord.SetString(1, featureName); + featureView.Execute(featureRecord); + + // Loop over all the components for the feature to calculate the size of the feature. + long size = 0; + foreach (var componentResultRecord in featureView.Records) + { + var component = componentResultRecord.GetString(1); + componentRecord.SetString(1, component); + componentView.Execute(componentRecord); + + foreach (var fileResultRecord in componentView.Records) + { + var fileSize = fileResultRecord.GetString(1); + size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); + } + } + + this.Section.AddSymbol(new WixBundleMsiFeatureSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, this.Facade.PackageId, featureName)) + { + PackageRef = this.Facade.PackageId, + Name = featureName, + Parent = allFeaturesResultRecord.GetString(2), + Title = allFeaturesResultRecord.GetString(3), + Description = allFeaturesResultRecord.GetString(4), + Display = allFeaturesResultRecord.GetInteger(5), + Level = allFeaturesResultRecord.GetInteger(6), + Directory = allFeaturesResultRecord.GetString(7), + Attributes = allFeaturesResultRecord.GetInteger(8), + Size = size + }); + } + } + } + } + } + + private void ImportExternalCabinetAsPayloads(Database db, WixBundlePayloadSymbol packagePayload, ISet payloadNames) + { + if (db.TableExists("Media")) + { + using (var view = db.OpenExecuteView("SELECT `Cabinet` FROM `Media`")) + { + foreach (var cabinetRecord in view.Records) + { + var cabinet = cabinetRecord.GetString(1); + + if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) + { + // If we didn't find the Payload as an existing child of the package, we need to + // add it. We expect the file to exist on-disk in the same relative location as + // the MSI expects to find it... + var cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); + + if (!payloadNames.Contains(cabinetName)) + { + var generatedId = this.BackendHelper.GenerateIdentifier("cab", packagePayload.Id.Id, cabinet); + var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.PackageSymbol.SourceLineNumbers); + + this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) + { + ParentType = ComplexReferenceParentType.Package, + ParentId = this.Facade.PackageId, + ChildType = ComplexReferenceChildType.Payload, + ChildId = generatedId + }); + + this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) + { + Name = cabinetName, + SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, + Compressed = packagePayload.Compressed, + UnresolvedSourceFile = cabinetName, + ContainerRef = packagePayload.ContainerRef, + ContentFile = true, + Packaging = packagePayload.Packaging, + ParentPackagePayloadRef = packagePayload.Id.Id, + }); + } + } + } + } + } + } + + private long ImportExternalFileAsPayloadsAndReturnInstallSize(Database db, WixBundlePayloadSymbol packagePayload, bool longNamesInImage, bool compressed, ISet payloadNames) + { + long size = 0; + + if (db.TableExists("Component") && db.TableExists("Directory") && db.TableExists("File")) + { + var directories = new Dictionary(); + + // Load up the directory hash table so we will be able to resolve source paths + // for files in the MSI database. + using (var view = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) + { + foreach (var record in view.Records) + { + var sourceName = this.BackendHelper.GetMsiFileName(record.GetString(3), true, longNamesInImage); + + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(record.GetString(2), sourceName); + + directories.Add(record.GetString(1), resolvedDirectory); + } + } + + // Resolve the source paths to external files and add each file size to the total + // install size of the package. + using (var view = db.OpenExecuteView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) + { + foreach (var record in view.Records) + { + // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not + // explicitly marked compressed then this is an external file. + var compressionBit = record.GetInteger(4); + if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) || + (!compressed && 0 == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesCompressed))) + { + var fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); + var name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); + + if (!payloadNames.Contains(name)) + { + var generatedId = this.BackendHelper.GenerateIdentifier("f", packagePayload.Id.Id, record.GetString(2)); + var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.PackageSymbol.SourceLineNumbers); + + this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers) + { + ParentType = ComplexReferenceParentType.Package, + ParentId = this.Facade.PackageId, + ChildType = ComplexReferenceChildType.Payload, + ChildId = generatedId + }); + + this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) + { + Name = name, + SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, + Compressed = packagePayload.Compressed, + UnresolvedSourceFile = name, + ContainerRef = packagePayload.ContainerRef, + ContentFile = true, + Packaging = packagePayload.Packaging, + ParentPackagePayloadRef = packagePayload.Id.Id, + }); + } + } + + size += record.GetInteger(5); + } + } + } + + return size; + } + + private void AddMsiProperty(WixBundleMsiPackageSymbol msiPackage, string name, string value) + { + this.Section.AddSymbol(new WixBundleMsiPropertySymbol(msiPackage.SourceLineNumbers, new Identifier(AccessModifier.Section, msiPackage.Id.Id, name)) + { + PackageRef = msiPackage.Id.Id, + Name = name, + Value = value, + }); + } + + private void ImportDependencyProviders(Database db, WixBundleMsiPackageSymbol msiPackage) + { + if (db.TableExists("WixDependencyProvider")) + { + using (var view = db.OpenExecuteView("SELECT `WixDependencyProvider`, `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`")) + { + foreach (var record in view.Records) + { + var id = new Identifier(AccessModifier.Section, this.BackendHelper.GenerateIdentifier("dep", msiPackage.Id.Id, record.GetString(1))); + + // Import the provider key and attributes. + this.Section.AddSymbol(new WixDependencyProviderSymbol(msiPackage.SourceLineNumbers, id) + { + ParentRef = msiPackage.Id.Id, + ProviderKey = record.GetString(2), + Version = record.GetString(3) ?? msiPackage.ProductVersion, + DisplayName = record.GetString(4) ?? this.Facade.PackageSymbol.DisplayName, + Attributes = WixDependencyProviderAttributes.ProvidesAttributesImported | (WixDependencyProviderAttributes)record.GetInteger(5), + }); + } + } + } + } + + private string ResolveRelatedFile(string resolvedSource, string unresolvedSource, string relatedSource, string type, SourceLineNumber sourceLineNumbers) + { + var checkedPaths = new List(); + + foreach (var extension in this.BackendExtensions) + { + var resolved = extension.ResolveRelatedFile(unresolvedSource, relatedSource, type, sourceLineNumbers); + + if (resolved?.CheckedPaths != null) + { + checkedPaths.AddRange(resolved.CheckedPaths); + } + + if (!String.IsNullOrEmpty(resolved?.Path)) + { + return resolved?.Path; + } + } + + var resolvedPath = Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource); + + if (!File.Exists(resolvedPath)) + { + checkedPaths.Add(resolvedPath); + this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, resolvedPath, type, checkedPaths)); + } + + return resolvedPath; + } + + private static string GetProperty(View view, string property) + { + using (var queryRecord = new Record(1)) + { + queryRecord[1] = property; + + view.Execute(queryRecord); + + using (var record = view.Fetch()) + { + return record?.GetString(1); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs new file mode 100644 index 00000000..5f431b38 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs @@ -0,0 +1,183 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Xml; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + /// + /// Initializes package state from the Msp contents. + /// + internal class ProcessMspPackageCommand + { + private const string PatchMetadataQuery = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = ?"; + private static readonly XmlWriterSettings XmlSettings = new XmlWriterSettings() + { + Encoding = new UTF8Encoding(false), + Indent = false, + NewLineChars = String.Empty, + NewLineHandling = NewLineHandling.Replace, + }; + + public ProcessMspPackageCommand(IMessaging messaging, IntermediateSection section, PackageFacade facade, Dictionary payloadSymbols) + { + this.Messaging = messaging; + + this.AuthoredPayloads = payloadSymbols; + this.Section = section; + this.Facade = facade; + } + + public IMessaging Messaging { get; } + + public Dictionary AuthoredPayloads { private get; set; } + + public PackageFacade Facade { private get; set; } + + public IntermediateSection Section { get; } + + /// + /// Processes the Msp packages to add properties and payloads from the Msp packages. + /// + public void Execute() + { + var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; + + var mspPackage = (WixBundleMspPackageSymbol)this.Facade.SpecificPackageSymbol; + + var sourcePath = packagePayload.SourceFile.Path; + + try + { + using (var db = new Database(sourcePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) + { + // Read data out of the msp database... + using (var sumInfo = new SummaryInformation(db)) + { + var patchCode = sumInfo.GetProperty(SummaryInformation.Patch.PatchCode); + mspPackage.PatchCode = patchCode.Substring(0, 38); + } + + using (var view = db.OpenView(PatchMetadataQuery)) + { + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName)) + { + this.Facade.PackageSymbol.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "DisplayName"); + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description)) + { + this.Facade.PackageSymbol.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "Description"); + } + + mspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "ManufacturerName"); + } + } + + this.ProcessPatchXml(packagePayload, mspPackage, sourcePath); + } + catch (MsiException e) + { + this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); + return; + } + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) + { + this.Facade.PackageSymbol.CacheId = mspPackage.PatchCode; + } + } + + private void ProcessPatchXml(WixBundlePayloadSymbol packagePayload, WixBundleMspPackageSymbol mspPackage, string sourcePath) + { + var uniqueTargetCodes = new HashSet(); + + var patchXml = Installer.ExtractPatchXml(sourcePath); + + var doc = new XmlDocument(); + doc.LoadXml(patchXml); + + var nsmgr = new XmlNamespaceManager(doc.NameTable); + nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); + + // Determine target ProductCodes and/or UpgradeCodes. + foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) + { + // If this patch targets a product code, this is the best case. + var targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); + var attributes = WixBundlePatchTargetCodeAttributes.None; + + if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) + { + attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode; + } + else // maybe targets an upgrade code? + { + targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr); + if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) + { + attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode; + } + else // this patch targets an unknown number of products + { + mspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; + } + } + + var targetCode = targetCodeElement.InnerText; + + if (uniqueTargetCodes.Add(targetCode)) + { + this.Section.AddSymbol(new WixBundlePatchTargetCodeSymbol(packagePayload.SourceLineNumbers) + { + PackageRef = packagePayload.Id.Id, + TargetCode = targetCode, + Attributes = attributes + }); + } + } + + // Suppress patch sequence data for improved performance. + var root = doc.DocumentElement; + foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) + { + root.RemoveChild(node); + } + + // Save the XML as compact as possible. + using (var writer = new StringWriter()) + { + using (var xmlWriter = XmlWriter.Create(writer, XmlSettings)) + { + doc.WriteTo(xmlWriter); + } + + mspPackage.PatchXml = writer.ToString(); + } + } + + private static string GetPatchMetadataProperty(View view, string property) + { + using (var queryRecord = new Record(1)) + { + queryRecord[1] = property; + + view.Execute(queryRecord); + + using (var record = view.Fetch()) + { + return record?.GetString(1); + } + } + } + + private static bool TargetsCode(XmlNode node) => "true" == node?.Attributes["Validate"]?.Value; + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs new file mode 100644 index 00000000..af4ab3a8 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs @@ -0,0 +1,37 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Processes the Msu packages to add properties and payloads from the Msu packages. + /// + internal class ProcessMsuPackageCommand + { + public ProcessMsuPackageCommand(PackageFacade facade, Dictionary payloadSymbols) + { + this.AuthoredPayloads = payloadSymbols; + this.Facade = facade; + } + + public Dictionary AuthoredPayloads { private get; set; } + + public PackageFacade Facade { private get; set; } + + public void Execute() + { + var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef]; + + if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId)) + { + this.Facade.PackageSymbol.CacheId = packagePayload.Hash; + } + + this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs new file mode 100644 index 00000000..fa70251a --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs @@ -0,0 +1,108 @@ +// 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.Burn.Bundles +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using WixToolset.Core.Burn.Interfaces; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ProcessPayloadsCommand + { + public ProcessPayloadsCommand(IBackendHelper backendHelper, IPayloadHarvester payloadHarvester, IEnumerable payloads, PackagingType defaultPackaging, string layoutDirectory) + { + this.BackendHelper = backendHelper; + this.PayloadHarvester = payloadHarvester; + this.Payloads = payloads; + this.DefaultPackaging = defaultPackaging; + this.LayoutDirectory = layoutDirectory; + } + + public IEnumerable FileTransfers { get; private set; } + + public IEnumerable TrackedFiles { get; private set; } + + private IBackendHelper BackendHelper { get; } + + private IPayloadHarvester PayloadHarvester { get; } + + private IEnumerable Payloads { get; } + + private PackagingType DefaultPackaging { get; } + + private string LayoutDirectory { get; } + + public void Execute() + { + var fileTransfers = new List(); + var trackedFiles = new List(); + + foreach (var payload in this.Payloads) + { + payload.Name = this.BackendHelper.GetCanonicalRelativePath(payload.SourceLineNumbers, "Payload", "Name", payload.Name); + + // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden + // in the .wixlib). + var sourceFile = payload.SourceFile; + payload.ContentFile = sourceFile != null && !sourceFile.Embed; + + this.UpdatePayloadPackagingType(payload); + + if (!this.PayloadHarvester.HarvestStandardInformation(payload)) + { + // Remote payloads obviously cannot be embedded. + Debug.Assert(PackagingType.Embedded != payload.Packaging); + } + else // not a remote payload so we have a lot more to update. + { + // External payloads need to be transfered. + if (PackagingType.External == payload.Packaging) + { + var transfer = this.BackendHelper.CreateFileTransfer(sourceFile.Path, Path.Combine(this.LayoutDirectory, payload.Name), false, payload.SourceLineNumbers); + fileTransfers.Add(transfer); + } + + if (payload.ContentFile) + { + trackedFiles.Add(this.BackendHelper.TrackFile(sourceFile.Path, TrackedFileType.Input, payload.SourceLineNumbers)); + } + } + } + + this.FileTransfers = fileTransfers; + this.TrackedFiles = trackedFiles; + } + + private void UpdatePayloadPackagingType(WixBundlePayloadSymbol payload) + { + if (!payload.Packaging.HasValue || PackagingType.Unknown == payload.Packaging) + { + if (!payload.Compressed.HasValue) + { + payload.Packaging = this.DefaultPackaging; + } + else if (payload.Compressed.Value) + { + payload.Packaging = PackagingType.Embedded; + } + else + { + payload.Packaging = PackagingType.External; + } + } + + // Embedded payloads that are not assigned a container already are placed in the default attached + // container. + if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.ContainerRef)) + { + payload.ContainerRef = BurnConstants.BurnDefaultAttachedContainerName; + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs b/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs new file mode 100644 index 00000000..854c84e0 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs @@ -0,0 +1,72 @@ +// 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.Burn +{ + using WixToolset.Data; + + internal static class BurnBackendErrors + { + public static Message BAContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) + { + return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the BA container. When extracting the container at runtime, the file will get overwritten.", payloadId, payloadName); + } + + public static Message BAContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision2, "The location of the payload related to the previous error."); + } + + public static Message DuplicateCacheIds(SourceLineNumber originalLineNumber, string cacheId, string packageId) + { + return Message(originalLineNumber, Ids.DuplicateCacheIds, "The CacheId '{0}' for package '{1}' is duplicated. Each package must have a unique CacheId.", cacheId, packageId); + } + + public static Message DuplicateCacheIds2(SourceLineNumber duplicateLineNumber) + { + return Message(duplicateLineNumber, Ids.DuplicateCacheIds2, "The location of the package related to the previous error."); + } + + public static Message ExternalPayloadCollision(SourceLineNumber sourceLineNumbers, string symbolName, string payloadId, string payloadName) + { + return Message(sourceLineNumbers, Ids.ExternalPayloadCollision, "The external {0} '{1}' has a duplicate Name '{2}'. When building the bundle or laying out the bundle, the file will get overwritten.", symbolName, payloadId, payloadName); + } + + public static Message ExternalPayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.ExternalPayloadCollision2, "The location of the symbol related to the previous error."); + } + + public static Message MultipleAttachedContainersUnsupported(SourceLineNumber sourceLineNumbers, string containerId) + { + return Message(sourceLineNumbers, Ids.MultipleAttachedContainersUnsupported, "Bundles don't currently support having more than one attached container. Either remove all authored attached containers to use the default attached container, or make sure all compressed payloads are included in this Container '{0}'.", containerId); + } + + public static Message PackageCachePayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName, string packageId) + { + return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in package '{2}'. When caching the package, the file will get overwritten.", payloadId, payloadName, packageId); + } + + public static Message PackageCachePayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision2, "The location of the payload related to the previous error."); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + DuplicateCacheIds = 8000, + DuplicateCacheIds2 = 8001, + BAContainerPayloadCollision = 8002, + BAContainerPayloadCollision2 = 8003, + ExternalPayloadCollision = 8004, + ExternalPayloadCollision2 = 8005, + PackageCachePayloadCollision = 8006, + PackageCachePayloadCollision2 = 8007, + MultipleAttachedContainersUnsupported = 8008, + } // last available is 8499. 8500 is BurnBackendWarnings. + } +} diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs b/src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs new file mode 100644 index 00000000..03013a08 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs @@ -0,0 +1,30 @@ +// 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.Burn +{ + using System; + using System.IO; + using WixToolset.Extensibility; + + internal class BurnBackendFactory : IBackendFactory + { + public bool TryCreateBackend(string outputType, string outputFile, out IBackend backend) + { + if (String.IsNullOrEmpty(outputType)) + { + outputType = Path.GetExtension(outputFile); + } + + switch (outputType.ToLowerInvariant()) + { + case "bundle": + case ".exe": + backend = new BundleBackend(); + return true; + } + + backend = null; + return false; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs b/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs new file mode 100644 index 00000000..a0ffa1dc --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs @@ -0,0 +1,36 @@ +// 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.Burn +{ + using WixToolset.Data; + + internal static class BurnBackendWarnings + { + public static Message AttachedContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName) + { + return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision, "The Payload '{0}' has a duplicate Name '{1}' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", payloadId, payloadName); + } + + public static Message AttachedContainerPayloadCollision2(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision2, "The location of the payload related to the previous error."); + } + + public static Message EmptyContainer(SourceLineNumber sourceLineNumbers, string containerId) + { + return Message(sourceLineNumbers, Ids.EmptyContainer, "The Container '{0}' is being ignored because it doesn't have any payloads.", containerId); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); + } + + public enum Ids + { + AttachedContainerPayloadCollision = 8500, + AttachedContainerPayloadCollision2 = 8501, + EmptyContainer = 8502, + } // last available is 8999. 9000 is VerboseMessages. + } +} diff --git a/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs b/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs new file mode 100644 index 00000000..b34d12c1 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs @@ -0,0 +1,22 @@ +// 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.Burn +{ + using System; + using WixToolset.Extensibility; + + internal class BurnExtensionFactory : IExtensionFactory + { + public bool TryCreateExtension(Type extensionType, out object extension) + { + extension = null; + + if (extensionType == typeof(IBackendFactory)) + { + extension = new BurnBackendFactory(); + } + + return extension != null; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs new file mode 100644 index 00000000..e4d2b0c9 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs @@ -0,0 +1,214 @@ +// 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.Burn.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Xml; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class BurnBackendHelper : IInternalBurnBackendHelper + { + public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment }; + public static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment }; + + private readonly IBackendHelper backendHelper; + + private ManifestData BootstrapperApplicationManifestData { get; } = new ManifestData(); + + private Dictionary BundleExtensionDataById { get; } = new Dictionary(); + + public BurnBackendHelper(IServiceProvider serviceProvider) + { + this.backendHelper = serviceProvider.GetService(); + } + + #region IBackendHelper interfaces + + public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); + + public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); + + public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); + + public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); + + public string CreateGuid() => this.backendHelper.CreateGuid(); + + public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); + + public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); + + public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); + + public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); + + public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); + + public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); + + public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); + + public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); + + public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); + + public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); + + public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); + + public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); + + public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); + + public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); + + public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); + + #endregion + + #region IBurnBackendHelper interfaces + + public void AddBootstrapperApplicationData(string xml) + { + this.BootstrapperApplicationManifestData.AddXml(xml); + } + + public void AddBootstrapperApplicationData(IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) + { + this.BootstrapperApplicationManifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BADataNamespace); + } + + public void AddBundleExtensionData(string extensionId, string xml) + { + var manifestData = this.GetBundleExtensionManifestData(extensionId); + manifestData.AddXml(xml); + } + + public void AddBundleExtensionData(string extensionId, IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false) + { + var manifestData = this.GetBundleExtensionManifestData(extensionId); + manifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BundleExtensionDataNamespace); + } + + #endregion + + #region IInternalBurnBackendHelper interfaces + + public void WriteBootstrapperApplicationData(XmlWriter writer) + { + this.BootstrapperApplicationManifestData.Write(writer); + } + + public void WriteBundleExtensionData(XmlWriter writer) + { + foreach (var kvp in this.BundleExtensionDataById) + { + this.WriteExtension(writer, kvp.Key, kvp.Value); + } + } + + #endregion + + private ManifestData GetBundleExtensionManifestData(string extensionId) + { + if (!this.backendHelper.IsValidIdentifier(extensionId)) + { + throw new ArgumentException($"'{extensionId}' is not a valid extensionId"); + } + + if (!this.BundleExtensionDataById.TryGetValue(extensionId, out var manifestData)) + { + manifestData = new ManifestData(); + this.BundleExtensionDataById.Add(extensionId, manifestData); + } + + return manifestData; + } + + private void WriteExtension(XmlWriter writer, string extensionId, ManifestData manifestData) + { + writer.WriteStartElement("BundleExtension"); + + writer.WriteAttributeString("Id", extensionId); + + manifestData.Write(writer); + + writer.WriteEndElement(); + } + + private class ManifestData + { + public ManifestData() + { + this.Builder = new StringBuilder(); + } + + private StringBuilder Builder { get; } + + public void AddSymbol(IntermediateSymbol symbol, bool symbolIdIsIdAttribute, string ns) + { + // There might be a more efficient way to do this, + // but this is an easy way to ensure we're creating valid XML. + var sb = new StringBuilder(); + using (var writer = XmlWriter.Create(sb, WriterSettings)) + { + writer.WriteStartElement(symbol.Definition.Name, ns); + + if (symbolIdIsIdAttribute && symbol.Id != null) + { + writer.WriteAttributeString("Id", symbol.Id.Id); + } + + foreach (var field in symbol.Fields) + { + if (!field.IsNull()) + { + writer.WriteAttributeString(field.Definition.Name, field.AsString()); + } + } + + writer.WriteEndElement(); + } + + this.AddXml(sb.ToString()); + } + + public void AddXml(string xml) + { + // There might be a more efficient way to do this, + // but this is an easy way to ensure we're given valid XML. + var sb = new StringBuilder(); + using (var xmlWriter = XmlWriter.Create(sb, WriterSettings)) + { + AddManifestDataFromString(xmlWriter, xml); + } + this.Builder.Append(sb.ToString()); + } + + public void Write(XmlWriter writer) + { + AddManifestDataFromString(writer, this.Builder.ToString()); + } + + private static void AddManifestDataFromString(XmlWriter xmlWriter, string xml) + { + using (var stringReader = new StringReader(xml)) + using (var xmlReader = XmlReader.Create(stringReader, ReaderSettings)) + { + while (xmlReader.MoveToContent() != XmlNodeType.None) + { + xmlWriter.WriteNode(xmlReader, false); + } + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs new file mode 100644 index 00000000..9ef91028 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs @@ -0,0 +1,68 @@ +// 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.Burn.ExtensibilityServices +{ + using System; + using System.Diagnostics; + using System.IO; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.Interfaces; + using WixToolset.Data.Symbols; + + internal class PayloadHarvester : IPayloadHarvester + { + private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); + + /// + public bool HarvestStandardInformation(WixBundlePayloadSymbol payload) + { + var filePath = payload.SourceFile?.Path; + + if (String.IsNullOrEmpty(filePath)) + { + return false; + } + + this.UpdatePayloadFileInformation(payload, filePath); + + this.UpdatePayloadVersionInformation(payload, filePath); + + return true; + } + + private void UpdatePayloadFileInformation(WixBundlePayloadSymbol payload, string filePath) + { + var fileInfo = new FileInfo(filePath); + + if (null != fileInfo) + { + payload.FileSize = fileInfo.Length; + + payload.Hash = BundleHashAlgorithm.Hash(fileInfo); + } + else + { + payload.FileSize = 0; + } + } + + private void UpdatePayloadVersionInformation(WixBundlePayloadSymbol payload, string filePath) + { + var versionInfo = FileVersionInfo.GetVersionInfo(filePath); + + if (null != versionInfo) + { + // Use the fixed version info block for the file since the resource text may not be a dotted quad. + var version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); + + if (PayloadHarvester.EmptyVersion != version) + { + payload.Version = version.ToString(); + } + + payload.Description = versionInfo.FileDescription; + payload.DisplayName = versionInfo.ProductName; + } + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs b/src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs new file mode 100644 index 00000000..59c4f20f --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs @@ -0,0 +1,14 @@ +// 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.Burn +{ + using System.Xml; + using WixToolset.Extensibility.Services; + + internal interface IInternalBurnBackendHelper : IBurnBackendHelper + { + void WriteBootstrapperApplicationData(XmlWriter writer); + + void WriteBundleExtensionData(XmlWriter writer); + } +} diff --git a/src/wix/WixToolset.Core.Burn/ISearchFacade.cs b/src/wix/WixToolset.Core.Burn/ISearchFacade.cs new file mode 100644 index 00000000..b9ad8649 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/ISearchFacade.cs @@ -0,0 +1,15 @@ +// 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.Burn +{ + using System.Xml; + + internal interface ISearchFacade + { + /// + /// Writes the search to the Burn manifest. + /// + /// + void WriteXml(XmlTextWriter writer); + } +} diff --git a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs new file mode 100644 index 00000000..b466d0de --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs @@ -0,0 +1,54 @@ +// 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.Burn.Inscribe +{ + using System.IO; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Native; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class InscribeBundleCommand + { + public InscribeBundleCommand(IInscribeContext context) + { + this.Context = context; + + this.Messaging = context.ServiceProvider.GetService(); + } + + private IInscribeContext Context { get; } + + public IMessaging Messaging { get; } + + public bool Execute() + { + var inscribed = false; + var tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_signed.exe"); + + using (var reader = BurnReader.Open(this.Context.InputFilePath)) + { + FileSystem.CopyFile(this.Context.SignedEngineFile, tempFile, allowHardlink: false); + + // If there was an attached container on the original (unsigned) bundle, put it back. + if (reader.AttachedContainerSize > 0) + { + reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin); + + using (var writer = BurnWriter.Open(this.Messaging, tempFile)) + { + writer.RememberThenResetSignature(); + writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); + inscribed = true; + } + } + } + + Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile)); + + FileSystem.MoveFile(tempFile, this.Context.OutputFile); + + return inscribed; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs new file mode 100644 index 00000000..a6789796 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs @@ -0,0 +1,63 @@ +// 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.Burn.Inscribe +{ + using System; + using System.IO; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Native; + using WixToolset.Extensibility.Data; + + internal class InscribeBundleEngineCommand + { + public InscribeBundleEngineCommand(IInscribeContext context) + { + this.IntermediateFolder = context.IntermediateFolder; + this.InputFilePath = context.InputFilePath; + this.OutputFile = context.OutputFile; + } + + private string IntermediateFolder { get; } + + private string InputFilePath { get; } + + private string OutputFile { get; } + + public bool Execute() + { + var tempFile = Path.Combine(this.IntermediateFolder, "bundle_engine_unsigned.exe"); + + using (var reader = BurnReader.Open(this.InputFilePath)) + using (var writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) + { + reader.Stream.Seek(0, SeekOrigin.Begin); + + var buffer = new byte[4 * 1024]; + var total = 0; + var read = 0; + do + { + read = Math.Min(buffer.Length, (int)reader.EngineSize - total); + + read = reader.Stream.Read(buffer, 0, read); + writer.Write(buffer, 0, read); + + total += read; + } while (total < reader.EngineSize && 0 < read); + + if (total != reader.EngineSize) + { + throw new InvalidOperationException("Failed to copy engine out of bundle."); + } + + // TODO: update writer with detached container signatures. + } + + Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile)); + + FileSystem.MoveFile(tempFile, this.OutputFile); + + return true; + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs b/src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs new file mode 100644 index 00000000..1bafa46e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs @@ -0,0 +1,23 @@ +// 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.Burn.Interfaces +{ + using System.Diagnostics; + using WixToolset.Data.Symbols; + + /// + /// Service for harvesting payload information. + /// + public interface IPayloadHarvester + { + /// + /// Uses to: + /// update from file contents, + /// update from file size, and + /// update , , and from . + /// + /// The symbol to update. + /// Whether the symbol had a source file specified. + bool HarvestStandardInformation(WixBundlePayloadSymbol payload); + } +} diff --git a/src/wix/WixToolset.Core.Burn/RowIndexedList.cs b/src/wix/WixToolset.Core.Burn/RowIndexedList.cs new file mode 100644 index 00000000..fd762a24 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/RowIndexedList.cs @@ -0,0 +1,299 @@ +// 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.Burn +{ + using System; + using System.Collections.Generic; + using WixToolset.Data.WindowsInstaller; + + /// + /// A list of rows indexed by their primary key. Unlike a RowDictionary + /// this indexed list will track rows in their added order and will allow rows with + /// duplicate keys to be added to the list, although only the first row will be indexed. + /// + internal sealed class RowIndexedList : IList where T : Row + { + private readonly Dictionary index; + private readonly List rows; + private readonly List duplicates; + + /// + /// Creates an empty . + /// + public RowIndexedList() + { + this.index = new Dictionary(StringComparer.InvariantCulture); + this.rows = new List(); + this.duplicates = new List(); + } + + /// + /// Creates and populates a with the rows from the given enumerator. + /// + /// Rows to index. + public RowIndexedList(IEnumerable rows) + : this() + { + foreach (var row in rows) + { + this.Add(row); + } + } + + /// + /// Creates and populates a with the rows from the given . + /// + /// The table to index. + /// + /// Rows added to the index are not automatically added to the given . + /// + public RowIndexedList(Table table) + : this() + { + if (null != table) + { + foreach (T row in table.Rows) + { + this.Add(row); + } + } + } + + /// + /// Gets the duplicates in the list. + /// + public IEnumerable Duplicates { get { return this.duplicates; } } + + /// + /// Gets the row by integer key. + /// + /// Integer key to look up. + /// Row or null if key is not found. + public T Get(int key) + { + return this.Get(key.ToString()); + } + + /// + /// Gets the row by string key. + /// + /// String key to look up. + /// Row or null if key is not found. + public T Get(string key) + { + return this.TryGet(key, out var result) ? result : null; + } + + /// + /// Gets the row by string key if it exists. + /// + /// Key of row to get. + /// Row found. + /// True if key was found otherwise false. + public bool TryGet(string key, out T row) + { + return this.index.TryGetValue(key, out row); + } + + /// + /// Tries to add a row as long as it would not create a duplicate. + /// + /// Row to add. + /// True if the row as added otherwise false. + public bool TryAdd(T row) + { + try + { + this.index.Add(row.GetKey(), row); + } + catch (ArgumentException) // if the key already exists, bail. + { + return false; + } + + this.rows.Add(row); + return true; + } + + /// + /// Adds a row to the list. If a row with the same key is already index, the row is + /// is not in the index but will still be part of the list and added to the duplicates + /// list. + /// + /// + public void Add(T row) + { + this.rows.Add(row); + try + { + this.index.Add(row.GetKey(), row); + } + catch (ArgumentException) // if the key already exists, we have a duplicate. + { + this.duplicates.Add(row); + } + } + + /// + /// Gets the index of a row. + /// + /// Iterates through the list of rows to find the index of a particular row. + /// Index of row or -1 if not found. + public int IndexOf(T row) + { + return this.rows.IndexOf(row); + } + + /// + /// Inserts a row at a particular index of the list. + /// + /// Index to insert the row after. + /// Row to insert. + public void Insert(int index, T row) + { + this.rows.Insert(index, row); + try + { + this.index.Add(row.GetKey(), row); + } + catch (ArgumentException) // if the key already exists, we have a duplicate. + { + this.duplicates.Add(row); + } + } + + /// + /// Removes a row from a particular index. + /// + /// Index to remove the row at. + public void RemoveAt(int index) + { + var row = this.rows[index]; + + this.rows.RemoveAt(index); + + if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row) + { + this.index.Remove(row.GetKey()); + } + else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe). + { + this.duplicates.Remove(row); + } + } + + /// + /// Gets or sets a row at the specified index. + /// + /// Index to get the row. + /// Row at specified index. + public T this[int index] + { + get + { + return this.rows[index]; + } + set + { + this.rows[index] = value; + try + { + this.index.Add(value.GetKey(), value); + } + catch (ArgumentException) // if the key already exists, we have a duplicate. + { + this.duplicates.Add(value); + } + } + } + + /// + /// Empties the list and it's index. + /// + public void Clear() + { + this.index.Clear(); + this.rows.Clear(); + this.duplicates.Clear(); + } + + /// + /// Searches the list for a row without using the index. + /// + /// Row to look for in the list. + /// True if the row is in the list, otherwise false. + public bool Contains(T row) + { + return this.rows.Contains(row); + } + + /// + /// Copies the rows of the list to an array. + /// + /// Array to copy the list into. + /// Index to start copying at. + public void CopyTo(T[] array, int arrayIndex) + { + this.rows.CopyTo(array, arrayIndex); + } + + /// + /// Number of rows in the list. + /// + public int Count + { + get { return this.rows.Count; } + } + + /// + /// Indicates whether the list is read-only. Always false. + /// + public bool IsReadOnly + { + get { return false; } + } + + /// + /// Removes a row from the list. Indexed rows will be removed but the colleciton will NOT + /// promote duplicates to the index automatically. The duplicate would also need to be removed + /// and re-added to be indexed. + /// + /// + /// + public bool Remove(T row) + { + var removed = this.rows.Remove(row); + if (removed) + { + if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row) + { + this.index.Remove(row.GetKey()); + } + else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe). + { + this.duplicates.Remove(row); + } + } + + return removed; + } + + /// + /// Gets an enumerator over the whole list. + /// + /// List enumerator. + public IEnumerator GetEnumerator() + { + return this.rows.GetEnumerator(); + } + + /// + /// Gets an untyped enumerator over the whole list. + /// + /// Untyped list enumerator. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.rows.GetEnumerator(); + } + } +} diff --git a/src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj new file mode 100644 index 00000000..f2da8a50 --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj @@ -0,0 +1,39 @@ + + + + + + netstandard2.0 + $(TargetFrameworks);net461;net472 + Core Burn + WiX Toolset Core Burn + embedded + true + true + + + + + <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + + + + + + + + + + + + + + diff --git a/src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs new file mode 100644 index 00000000..58076d5e --- /dev/null +++ b/src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs @@ -0,0 +1,45 @@ +// 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.Burn +{ + using System; + using System.Collections.Generic; + using WixToolset.Core.Burn.ExtensibilityServices; + using WixToolset.Core.Burn.Interfaces; + using WixToolset.Extensibility.Services; + + /// + /// Extensions methods for adding Burn services. + /// + public static class WixToolsetCoreServiceProviderExtensions + { + /// + /// Adds Burn Services. + /// + /// + /// + public static IWixToolsetCoreServiceProvider AddBundleBackend(this IWixToolsetCoreServiceProvider coreProvider) + { + AddServices(coreProvider); + + var extensionManager = coreProvider.GetService(); + extensionManager.Add(typeof(BurnExtensionFactory).Assembly); + + return coreProvider; + } + + private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) + { + // Singletons. + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new BurnBackendHelper(provider))); + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new PayloadHarvester())); + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, provider.GetService())); + } + + private static T AddSingleton(Dictionary singletons, T service) where T : class + { + singletons.Add(typeof(T), service); + return service; + } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs b/src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs new file mode 100644 index 00000000..5567541c --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs @@ -0,0 +1,20 @@ +// 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.ExtensionCache +{ + internal class CachedExtension + { + public CachedExtension(string id, string version, bool damaged) + { + this.Id = id; + this.Version = version; + this.Damaged = damaged; + } + + public string Id { get; } + + public string Version { get; } + + public bool Damaged { get; } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs new file mode 100644 index 00000000..256eeb0b --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs @@ -0,0 +1,248 @@ +// 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.ExtensionCache +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using NuGet.Common; + using NuGet.Configuration; + using NuGet.Credentials; + using NuGet.Packaging; + using NuGet.Protocol; + using NuGet.Protocol.Core.Types; + using NuGet.Versioning; + + /// + /// Extension cache manager. + /// + internal class ExtensionCacheManager + { + public string CacheFolder(bool global) => global ? this.GlobalCacheFolder() : this.LocalCacheFolder(); + + public string LocalCacheFolder() => Path.Combine(Environment.CurrentDirectory, ".wix", "extensions"); + + public string GlobalCacheFolder() + { + var baseFolder = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + return Path.Combine(baseFolder, ".wix", "extensions"); + } + + public async Task AddAsync(bool global, string extension, CancellationToken cancellationToken) + { + if (String.IsNullOrEmpty(extension)) + { + throw new ArgumentNullException(nameof(extension)); + } + + (var extensionId, var extensionVersion) = ParseExtensionReference(extension); + + var result = await this.DownloadAndExtractAsync(global, extensionId, extensionVersion, cancellationToken); + + return result; + } + + public Task RemoveAsync(bool global, string extension, CancellationToken cancellationToken) + { + if (String.IsNullOrEmpty(extension)) + { + throw new ArgumentNullException(nameof(extension)); + } + + (var extensionId, var extensionVersion) = ParseExtensionReference(extension); + + var cacheFolder = this.CacheFolder(global); + + cacheFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); + + if (Directory.Exists(cacheFolder)) + { + cancellationToken.ThrowIfCancellationRequested(); + + Directory.Delete(cacheFolder, true); + return Task.FromResult(true); + } + + return Task.FromResult(false); + } + + public Task> ListAsync(bool global, string extension, CancellationToken cancellationToken) + { + var found = new List(); + + (var extensionId, var extensionVersion) = ParseExtensionReference(extension); + + var cacheFolder = this.CacheFolder(global); + + var searchFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); + + if (!Directory.Exists(searchFolder)) + { + } + else if (!String.IsNullOrEmpty(extensionVersion)) // looking for an explicit version of an extension. + { + var present = ExtensionFileExists(cacheFolder, extensionId, extensionVersion); + found.Add(new CachedExtension(extensionId, extensionVersion, !present)); + } + else // looking for all versions of an extension or all versions of all extensions. + { + IEnumerable foundExtensionIds; + + if (String.IsNullOrEmpty(extensionId)) + { + // Looking for all versions of all extensions. + foundExtensionIds = Directory.GetDirectories(cacheFolder).Select(folder => Path.GetFileName(folder)).ToList(); + } + else + { + // Looking for all versions of a single extension. + var extensionFolder = Path.Combine(cacheFolder, extensionId); + foundExtensionIds = Directory.Exists(extensionFolder) ? new[] { extensionId } : Array.Empty(); + } + + foreach (var foundExtensionId in foundExtensionIds) + { + var extensionFolder = Path.Combine(cacheFolder, foundExtensionId); + + foreach (var folder in Directory.GetDirectories(extensionFolder)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var foundExtensionVersion = Path.GetFileName(folder); + + if (!NuGetVersion.TryParse(foundExtensionVersion, out _)) + { + continue; + } + + var present = ExtensionFileExists(cacheFolder, foundExtensionId, foundExtensionVersion); + found.Add(new CachedExtension(foundExtensionId, foundExtensionVersion, !present)); + } + } + } + + return Task.FromResult((IEnumerable)found); + } + + private async Task DownloadAndExtractAsync(bool global, string id, string version, CancellationToken cancellationToken) + { + var logger = NullLogger.Instance; + + DefaultCredentialServiceUtility.SetupDefaultCredentialService(logger, nonInteractive: false); + + var settings = Settings.LoadDefaultSettings(root: Environment.CurrentDirectory); + var sources = PackageSourceProvider.LoadPackageSources(settings).Where(s => s.IsEnabled); + + using (var cache = new SourceCacheContext()) + { + PackageSource versionSource = null; + + var nugetVersion = String.IsNullOrEmpty(version) ? null : new NuGetVersion(version); + + if (nugetVersion is null) + { + foreach (var source in sources) + { + var repository = Repository.Factory.GetCoreV3(source.Source); + var resource = await repository.GetResourceAsync(); + + var availableVersions = await resource.GetAllVersionsAsync(id, cache, logger, cancellationToken); + foreach (var availableVersion in availableVersions) + { + if (nugetVersion is null || nugetVersion < availableVersion) + { + nugetVersion = availableVersion; + versionSource = source; + } + } + } + + if (nugetVersion is null) + { + return false; + } + } + + var searchSources = versionSource is null ? sources : new[] { versionSource }; + + var extensionFolder = Path.Combine(this.CacheFolder(global), id, nugetVersion.ToString()); + + foreach (var source in searchSources) + { + var repository = Repository.Factory.GetCoreV3(source.Source); + var resource = await repository.GetResourceAsync(); + + using (var stream = new MemoryStream()) + { + var downloaded = await resource.CopyNupkgToStreamAsync(id, nugetVersion, stream, cache, logger, cancellationToken); + + if (downloaded) + { + stream.Position = 0; + + using (var archive = new PackageArchiveReader(stream)) + { + var files = PackagingConstants.Folders.Known.SelectMany(folder => archive.GetFiles(folder)).Distinct(StringComparer.OrdinalIgnoreCase); + await archive.CopyFilesAsync(extensionFolder, files, this.ExtractProgress, logger, cancellationToken); + } + + return true; + } + } + } + } + + return false; + } + + private string ExtractProgress(string sourceFile, string targetPath, Stream fileStream) => fileStream.CopyToFile(targetPath); + + private static (string extensionId, string extensionVersion) ParseExtensionReference(string extensionReference) + { + var extensionId = extensionReference ?? String.Empty; + var extensionVersion = String.Empty; + + var index = extensionId.LastIndexOf('/'); + if (index > 0) + { + extensionVersion = extensionReference.Substring(index + 1); + extensionId = extensionReference.Substring(0, index); + + if (!NuGetVersion.TryParse(extensionVersion, out _)) + { + throw new ArgumentException($"Invalid extension version in {extensionReference}"); + } + + if (String.IsNullOrEmpty(extensionId)) + { + throw new ArgumentException($"Invalid extension id in {extensionReference}"); + } + } + + return (extensionId, extensionVersion); + } + + private static bool ExtensionFileExists(string baseFolder, string extensionId, string extensionVersion) + { + var toolsFolder = Path.Combine(baseFolder, extensionId, extensionVersion, "tools"); + if (!Directory.Exists(toolsFolder)) + { + return false; + } + + var extensionAssembly = Path.Combine(toolsFolder, extensionId + ".dll"); + + var present = File.Exists(extensionAssembly); + if (!present) + { + extensionAssembly = Path.Combine(toolsFolder, extensionId + ".exe"); + present = File.Exists(extensionAssembly); + } + + return present; + } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs new file mode 100644 index 00000000..94ee4f22 --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs @@ -0,0 +1,181 @@ +// 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.ExtensionCache +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Extension cache manager command. + /// + internal class ExtensionCacheManagerCommand : ICommandLineCommand + { + private enum CacheSubcommand + { + Add, + Remove, + List + } + + public ExtensionCacheManagerCommand(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + this.ExtensionReferences = new List(); + } + + private IMessaging Messaging { get; } + + public bool ShowLogo { get; private set; } + + public bool StopParsing { get; private set; } + + private bool ShowHelp { get; set; } + + private bool Global { get; set; } + + private CacheSubcommand? Subcommand { get; set; } + + private List ExtensionReferences { get; } + + public async Task ExecuteAsync(CancellationToken cancellationToken) + { + if (this.ShowHelp || !this.Subcommand.HasValue) + { + DisplayHelp(); + return 1; + } + + var success = false; + var cacheManager = new ExtensionCacheManager(); + + switch (this.Subcommand) + { + case CacheSubcommand.Add: + success = await this.AddExtensions(cacheManager, cancellationToken); + break; + + case CacheSubcommand.Remove: + success = await this.RemoveExtensions(cacheManager, cancellationToken); + break; + + case CacheSubcommand.List: + success = await this.ListExtensions(cacheManager, cancellationToken); + break; + } + + return success ? 0 : 2; + } + + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + if (!parser.IsSwitch(argument)) + { + if (!this.Subcommand.HasValue) + { + if (!Enum.TryParse(argument, true, out CacheSubcommand subcommand)) + { + return false; + } + + this.Subcommand = subcommand; + } + else + { + this.ExtensionReferences.Add(argument); + } + + return true; + } + + var parameter = argument.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "?": + case "h": + case "-help": + this.ShowHelp = true; + this.ShowLogo = true; + this.StopParsing = true; + return true; + + case "nologo": + case "-nologo": + this.ShowLogo = false; + return true; + + case "g": + case "-global": + this.Global = true; + return true; + } + + return false; + } + + private async Task AddExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) + { + var success = false; + + foreach (var extensionRef in this.ExtensionReferences) + { + var added = await cacheManager.AddAsync(this.Global, extensionRef, cancellationToken); + success |= added; + } + + return success; + } + + private async Task RemoveExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) + { + var success = false; + + foreach (var extensionRef in this.ExtensionReferences) + { + var removed = await cacheManager.RemoveAsync(this.Global, extensionRef, cancellationToken); + success |= removed; + } + + return success; + } + + private async Task ListExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken) + { + var found = false; + var extensionRef = this.ExtensionReferences.FirstOrDefault(); + + var extensions = await cacheManager.ListAsync(this.Global, extensionRef, cancellationToken); + + foreach (var extension in extensions) + { + this.Messaging.Write($"{extension.Id} {extension.Version}{(extension.Damaged ? " (damaged)" : String.Empty)}"); + found = true; + } + + return found; + } + + private static void DisplayHelp() + { + Console.WriteLine(); + Console.WriteLine("Usage: wix extension add|remove|list [extensionRef]"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" -h|--help Show command line help."); + Console.WriteLine(" -g|--global Add/remove the extension for the current user."); + Console.WriteLine(" --nologo Suppress displaying the logo information."); + Console.WriteLine(); + Console.WriteLine("Commands:"); + Console.WriteLine(); + Console.WriteLine(" add Add extension to the cache."); + Console.WriteLine(" list List extensions in the cache."); + Console.WriteLine(" remove Remove extension from the cache."); + Console.WriteLine(); + Console.WriteLine(" extensionRef format: extensionId/version (the version is optional)"); + } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs new file mode 100644 index 00000000..2a603adf --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs @@ -0,0 +1,41 @@ +// 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.ExtensionCache +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Parses the "extension" command-line command. See ExtensionCacheManagerCommand + /// for the bulk of the command-line processing. + /// + internal class ExtensionCacheManagerExtensionCommandLine : BaseExtensionCommandLine + { + public ExtensionCacheManagerExtensionCommandLine(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + private IServiceProvider ServiceProvider { get; } + + public override IReadOnlyCollection CommandLineSwitches => new ExtensionCommandLineSwitch[] + { + new ExtensionCommandLineSwitch { Switch = "extension", Description = "Manage extension cache." }, + }; + + public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) + { + command = null; + + if ("extension".Equals(argument, StringComparison.OrdinalIgnoreCase)) + { + command = new ExtensionCacheManagerCommand(this.ServiceProvider); + } + + return command != null; + } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs new file mode 100644 index 00000000..c38e5c70 --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs @@ -0,0 +1,30 @@ +// 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.ExtensionCache +{ + using System; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class ExtensionCacheManagerExtensionFactory : IExtensionFactory + { + public ExtensionCacheManagerExtensionFactory(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + private IServiceProvider ServiceProvider { get; } + + public bool TryCreateExtension(Type extensionType, out object extension) + { + extension = null; + + if (extensionType == typeof(IExtensionCommandLine)) + { + extension = new ExtensionCacheManagerExtensionCommandLine(this.ServiceProvider); + } + + return extension != null; + } + } +} diff --git a/src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj b/src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj new file mode 100644 index 00000000..1383305c --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj @@ -0,0 +1,29 @@ + + + + + + netstandard2.0 + $(TargetFrameworks);net461;net472 + Extension Cache + WiX Toolset Extension Cache + embedded + true + true + + + + + + + + + + + + + + + + + diff --git a/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs new file mode 100644 index 00000000..424fc469 --- /dev/null +++ b/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs @@ -0,0 +1,36 @@ +// 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.ExtensionCache +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility.Services; + + /// + /// Extensions methods for adding ExtensionCache services. + /// + public static class WixToolsetCoreServiceProviderExtensions + { + /// + /// Adds ExtensionCache services. + /// + /// + /// + public static IWixToolsetCoreServiceProvider AddExtensionCacheManager(this IWixToolsetCoreServiceProvider coreProvider) + { + var extensionManager = coreProvider.GetService(); + extensionManager.Add(typeof(ExtensionCacheManagerExtensionFactory).Assembly); + + coreProvider.AddService(CreateExtensionCacheManager); + return coreProvider; + } + + private static ExtensionCacheManager CreateExtensionCacheManager(IWixToolsetCoreServiceProvider coreProvider, Dictionary singletons) + { + var extensionCacheManager = new ExtensionCacheManager(); + singletons.Add(typeof(ExtensionCacheManager), extensionCacheManager); + + return extensionCacheManager; + } + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs new file mode 100644 index 00000000..8c9f31e6 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs @@ -0,0 +1,139 @@ +// 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.TestPackage +{ + using System.IO; + using System.Xml; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Extensibility.Services; + + /// + /// Class to extract bundle contents for testing. + /// + public class BundleExtractor + { + /// + /// Extracts the BA container. + /// + /// + /// Path to the bundle. + /// Path to extract to. + /// Temp path for extraction. + /// + public static ExtractBAContainerResult ExtractBAContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) + { + var result = new ExtractBAContainerResult(); + Directory.CreateDirectory(tempFolderPath); + using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) + { + result.Success = burnReader.ExtractUXContainer(destinationFolderPath, tempFolderPath); + } + + if (result.Success) + { + result.ManifestDocument = LoadBurnManifest(destinationFolderPath); + result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); + + result.BADataDocument = LoadBAData(destinationFolderPath); + result.BADataNamespaceManager = GetBADataNamespaceManager(result.BADataDocument, "ba"); + + result.BundleExtensionDataDocument = LoadBundleExtensionData(destinationFolderPath); + result.BundleExtensionDataNamespaceManager = GetBundleExtensionDataNamespaceManager(result.BundleExtensionDataDocument, "be"); + } + + return result; + } + + /// + /// Extracts the attached container. + /// + /// + /// Path to the bundle. + /// Path to extract to. + /// Temp path for extraction. + /// True if there was an attached container. + public static bool ExtractAttachedContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) + { + Directory.CreateDirectory(tempFolderPath); + using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) + { + return burnReader.ExtractAttachedContainer(destinationFolderPath, tempFolderPath); + } + } + + /// + /// Gets an for BootstrapperApplicationData.xml with the given prefix assigned to the root namespace. + /// + /// + /// + /// + public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace); + return namespaceManager; + } + + /// + /// Gets an for BundleExtensionData.xml with the given prefix assigned to the root namespace. + /// + /// + /// + /// + public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace); + return namespaceManager; + } + + /// + /// Gets an for the Burn manifest.xml with the given prefix assigned to the root namespace. + /// + /// + /// + /// + public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BurnNamespace); + return namespaceManager; + } + + /// + /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. + /// + /// + /// + public static XmlDocument LoadBAData(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, BurnCommon.BADataFileName)); + return document; + } + + /// + /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. + /// + /// + /// + public static XmlDocument LoadBundleExtensionData(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, BurnCommon.BundleExtensionDataFileName)); + return document; + } + + /// + /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container. + /// + /// + /// + public static XmlDocument LoadBurnManifest(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, "manifest.xml")); + return document; + } + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs b/src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs new file mode 100644 index 00000000..277861ff --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs @@ -0,0 +1,116 @@ +// 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.TestPackage +{ + using System.IO; + using System.Xml; + using Xunit; + + /// + /// The result of extracting the BA container. + /// + public class ExtractBAContainerResult + { + /// + /// for BundleExtensionData.xml. + /// + public XmlDocument BundleExtensionDataDocument { get; set; } + + /// + /// for BundleExtensionData.xml. + /// + public XmlNamespaceManager BundleExtensionDataNamespaceManager { get; set; } + + /// + /// for BootstrapperApplicationData.xml. + /// + public XmlDocument BADataDocument { get; set; } + + /// + /// for BootstrapperApplicationData.xml. + /// + public XmlNamespaceManager BADataNamespaceManager { get; set; } + + /// + /// for the Burn manifest.xml. + /// + public XmlDocument ManifestDocument { get; set; } + + /// + /// for the Burn manifest.xml. + /// + public XmlNamespaceManager ManifestNamespaceManager { get; set; } + + /// + /// Whether extraction succeeded. + /// + public bool Success { get; set; } + + /// + /// + /// + /// + public ExtractBAContainerResult AssertSuccess() + { + Assert.True(this.Success); + return this; + } + + /// + /// Returns the relative path of the BA entry point dll in the given folder. + /// + /// + /// + public string GetBAFilePath(string extractedBAContainerFolderPath) + { + var uxPayloads = this.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload"); + var baPayload = uxPayloads[0]; + var relativeBAPath = baPayload.Attributes["FilePath"].Value; + return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); + } + + /// + /// Returns the relative path of the BundleExtension entry point dll in the given folder. + /// + /// + /// + /// + public string GetBundleExtensionFilePath(string extractedBAContainerFolderPath, string extensionId) + { + var uxPayloads = this.SelectManifestNodes($"/burn:BurnManifest/burn:UX/burn:Payload[@Id='{extensionId}']"); + var bextPayload = uxPayloads[0]; + var relativeBextPath = bextPayload.Attributes["FilePath"].Value; + return Path.Combine(extractedBAContainerFolderPath, relativeBextPath); + } + + /// + /// + /// + /// elements must have the 'ba' prefix + /// + public XmlNodeList SelectBADataNodes(string xpath) + { + return this.BADataDocument.SelectNodes(xpath, this.BADataNamespaceManager); + } + + /// + /// + /// + /// elements must have the 'be' prefix + /// + public XmlNodeList SelectBundleExtensionDataNodes(string xpath) + { + return this.BundleExtensionDataDocument.SelectNodes(xpath, this.BundleExtensionDataNamespaceManager); + } + + /// + /// + /// + /// elements must have the 'burn' prefix + /// + public XmlNodeList SelectManifestNodes(string xpath) + { + return this.ManifestDocument.SelectNodes(xpath, this.ManifestNamespaceManager); + } + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs b/src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs new file mode 100644 index 00000000..7040fe82 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using WixToolset.Data; +using WixToolset.Extensibility; +using WixToolset.Extensibility.Services; + +namespace WixToolset.Core.TestPackage +{ + /// + /// An that simply stores all the messages. + /// + public sealed class TestMessageListener : IMessageListener + { + /// + /// All messages that have been received. + /// + public List Messages { get; } = new List(); + + /// + /// + /// + public string ShortAppName => "TEST"; + + /// + /// + /// + public string LongAppName => "Test"; + + /// + /// Stores the message in . + /// + /// + public void Write(Message message) + { + this.Messages.Add(message); + } + + /// + /// Stores the message in . + /// + /// + public void Write(string message) + { + this.Messages.Add(new Message(null, MessageLevel.Information, 0, message)); + } + + /// + /// Always returns defaultMessageLevel. + /// + /// + /// + /// + /// + public MessageLevel CalculateMessageLevel(IMessaging messaging, Message message, MessageLevel defaultMessageLevel) => defaultMessageLevel; + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/WixRunner.cs b/src/wix/WixToolset.Core.TestPackage/WixRunner.cs new file mode 100644 index 00000000..ed7c49b8 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/WixRunner.cs @@ -0,0 +1,88 @@ +// 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.TestPackage +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Core.Burn; + using WixToolset.Core.WindowsInstaller; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + /// + /// Utility class to emulate wix.exe with standard backends. + /// + public static class WixRunner + { + /// + /// Emulates calling wix.exe with standard backends. + /// + /// + /// + /// + /// + public static int Execute(string[] args, out List messages, bool warningsAsErrors = true) + { + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var task = Execute(args, serviceProvider, out messages, warningsAsErrors: warningsAsErrors); + return task.Result; + } + + /// + /// Emulates calling wix.exe with standard backends. + /// This overload always treats warnings as errors. + /// + /// + /// + public static WixRunnerResult Execute(params string[] args) + { + return Execute(true, args); + } + + /// + /// Emulates calling wix.exe with standard backends. + /// + /// + /// + /// + public static WixRunnerResult Execute(bool warningsAsErrors, params string[] args) + { + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var exitCode = Execute(args, serviceProvider, out var messages, warningsAsErrors: warningsAsErrors); + return new WixRunnerResult { ExitCode = exitCode.Result, Messages = messages.ToArray() }; + } + + /// + /// Emulates calling wix.exe with standard backends. + /// + /// + /// + /// + /// + /// + public static Task Execute(string[] args, IWixToolsetCoreServiceProvider coreProvider, out List messages, bool warningsAsErrors = true) + { + coreProvider.AddWindowsInstallerBackend() + .AddBundleBackend(); + + var listener = new TestMessageListener(); + + messages = listener.Messages; + + var messaging = coreProvider.GetService(); + messaging.SetListener(listener); + + var arguments = new List(args); + if (warningsAsErrors) + { + arguments.Add("-wx"); + } + + var commandLine = coreProvider.GetService(); + var command = commandLine.CreateCommand(arguments.ToArray()); + return command?.ExecuteAsync(CancellationToken.None) ?? Task.FromResult(1); + } + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs b/src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs new file mode 100644 index 00000000..6a3d714c --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs @@ -0,0 +1,62 @@ +// 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.TestPackage +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using Xunit; + + /// + /// The result of an Execute method of . + /// + public class WixRunnerResult + { + /// + /// ExitCode for the operation. + /// + public int ExitCode { get; set; } + + /// + /// Messages from the operation. + /// + public Message[] Messages { get; set; } + + /// + /// + /// + /// + public WixRunnerResult AssertSuccess() + { + AssertSuccess(this.ExitCode, this.Messages); + return this; + } + + /// + /// + /// + /// + /// + public static void AssertSuccess(int exitCode, IEnumerable messages) + { + Assert.True(0 == exitCode, $"\r\n\r\nWixRunner failed with exit code: {exitCode}\r\n Output: {String.Join("\r\n ", FormatMessages(messages))}\r\n"); + } + + private static IEnumerable FormatMessages(IEnumerable messages) + { + foreach (var message in messages) + { + var filename = message.SourceLineNumbers?.FileName ?? "TEST"; + var line = message.SourceLineNumbers?.LineNumber ?? -1; + var type = message.Level.ToString().ToLowerInvariant(); + + if (line > 0) + { + filename = String.Concat(filename, "(", line, ")"); + } + + yield return String.Format("{0} : {1} {2}{3:0000}: {4}", filename, type, "TEST", message.Id, message.ToString()); + } + } + } +} diff --git a/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj b/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj new file mode 100644 index 00000000..b64b4075 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj @@ -0,0 +1,34 @@ + + + + + + netstandard2.0 + $(TargetFrameworks);net461;net472 + Internal WiX Toolset Test Package + embedded + true + true + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs b/src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs new file mode 100644 index 00000000..f4966f74 --- /dev/null +++ b/src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs @@ -0,0 +1,90 @@ +// 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.TestPackage +{ + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using System.Xml; + + /// + /// Utility class to help compare XML in tests using string comparisons by using single quotes and stripping all namespaces. + /// + public static class XmlNodeExtensions + { + /// + /// Returns the node's outer XML using single quotes and stripping all namespaces. + /// + /// + /// Attributes for which the value should be set to '*'. + /// + public static string GetTestXml(this XmlNode node, Dictionary> ignoredAttributesByElementName = null) + { + return node.OuterXml.GetTestXml(ignoredAttributesByElementName); + } + + /// + /// Returns the XML using single quotes and stripping all namespaces. + /// + /// + /// Attributes for which the value should be set to '*'. + /// + public static string GetTestXml(this string xml, Dictionary> ignoredAttributesByElementName = null) + { + string formattedXml; + using (var sw = new StringWriter()) + using (var writer = new TestXmlWriter(sw)) + { + var doc = new XmlDocument(); + doc.LoadXml(xml); + + if (ignoredAttributesByElementName != null) + { + HandleIgnoredAttributes(doc, ignoredAttributesByElementName); + } + + doc.Save(writer); + formattedXml = sw.ToString(); + } + + return Regex.Replace(formattedXml, " xmlns(:[^=]+)?='[^']*'", ""); + } + + private static void HandleIgnoredAttributes(XmlNode node, Dictionary> ignoredAttributesByElementName) + { + if (node.Attributes != null && ignoredAttributesByElementName.TryGetValue(node.LocalName, out var ignoredAttributes)) + { + foreach (var ignoredAttribute in ignoredAttributes) + { + var attribute = node.Attributes[ignoredAttribute]; + if (attribute != null) + { + attribute.Value = "*"; + } + } + } + + if (node.ChildNodes != null) + { + foreach (XmlNode childNode in node.ChildNodes) + { + HandleIgnoredAttributes(childNode, ignoredAttributesByElementName); + } + } + } + + private class TestXmlWriter : XmlTextWriter + { + public TestXmlWriter(TextWriter w) + : base(w) + { + this.QuoteChar = '\''; + } + + public override void WriteStartDocument() + { + //OmitXmlDeclaration + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs new file mode 100644 index 00000000..cbba6030 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs @@ -0,0 +1,52 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + + /// + /// Add back possibly suppressed sequence tables since all sequence tables must be present + /// for the merge process to work. We'll drop the suppressed sequence tables again as + /// necessary. + /// + internal class AddBackSuppressedSequenceTablesCommand + { + public AddBackSuppressedSequenceTablesCommand(WindowsInstallerData output, TableDefinitionCollection tableDefinitions) + { + this.Output = output; + this.TableDefinitions = tableDefinitions; + } + + private WindowsInstallerData Output { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + public IEnumerable SuppressedTableNames { get; private set; } + + public IEnumerable Execute() + { + var suppressedTableNames = new HashSet(); + + foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) + { + var sequenceTableName = sequence.WindowsInstallerTableName(); + var sequenceTable = this.Output.Tables[sequenceTableName]; + + if (null == sequenceTable) + { + sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); + } + + if (0 == sequenceTable.Rows.Count) + { + suppressedTableNames.Add(sequenceTableName); + } + } + + return this.SuppressedTableNames = suppressedTableNames; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs new file mode 100644 index 00000000..c4fddb3e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs @@ -0,0 +1,38 @@ +// 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.Bind +{ + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Add CreateFolder symbols, if not already present, for null-keypath components. + /// + internal class AddCreateFoldersCommand + { + internal AddCreateFoldersCommand(IntermediateSection section) + { + this.Section = section; + } + + private IntermediateSection Section { get; } + + public void Execute() + { + var createFolderSymbolsByComponentRef = new HashSet(this.Section.Symbols.OfType().Select(t => t.ComponentRef)); + foreach (var componentSymbol in this.Section.Symbols.OfType().Where(t => t.KeyPathType == ComponentKeyPathType.Directory).ToList()) + { + if (!createFolderSymbolsByComponentRef.Contains(componentSymbol.Id.Id)) + { + this.Section.AddSymbol(new CreateFolderSymbol(componentSymbol.SourceLineNumbers) + { + DirectoryRef = componentSymbol.DirectoryRef, + ComponentRef = componentSymbol.Id.Id, + }); + } + } + } + } +} \ No newline at end of file diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs new file mode 100644 index 00000000..ee3bcc91 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs @@ -0,0 +1,95 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + + /// + /// Add referenced standard directory symbols, if not already present. + /// + internal class AddRequiredStandardDirectories + { + internal AddRequiredStandardDirectories(IntermediateSection section, Platform platform) + { + this.Section = section; + this.Platform = platform; + } + + private IntermediateSection Section { get; } + + private Platform Platform { get; } + + public void Execute() + { + var directories = this.Section.Symbols.OfType().ToList(); + var directoryIds = new SortedSet(directories.Select(d => d.Id.Id)); + + foreach (var directory in directories) + { + var parentDirectoryId = directory.ParentDirectoryRef; + + if (String.IsNullOrEmpty(parentDirectoryId)) + { + if (directory.Id.Id != "TARGETDIR") + { + directory.ParentDirectoryRef = "TARGETDIR"; + } + } + else + { + this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, directory.SourceLineNumbers); + } + } + + if (!directoryIds.Contains("TARGETDIR") && WindowsInstallerStandard.TryGetStandardDirectory("TARGETDIR", out var targetDir)) + { + directoryIds.Add(targetDir.Id.Id); + this.Section.AddSymbol(targetDir); + } + } + + private void EnsureStandardDirectoryAdded(ISet directoryIds, string directoryId, SourceLineNumber sourceLineNumbers) + { + if (!directoryIds.Contains(directoryId) && WindowsInstallerStandard.TryGetStandardDirectory(directoryId, out var standardDirectory)) + { + var parentDirectoryId = this.GetStandardDirectoryParent(directoryId); + + var directory = new DirectorySymbol(sourceLineNumbers, standardDirectory.Id) + { + Name = standardDirectory.Name, + ParentDirectoryRef = parentDirectoryId, + }; + + directoryIds.Add(directory.Id.Id); + this.Section.AddSymbol(directory); + + if (!String.IsNullOrEmpty(parentDirectoryId)) + { + this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, sourceLineNumbers); + } + } + } + + private string GetStandardDirectoryParent(string directoryId) + { + switch (directoryId) + { + case "TARGETDIR": + return null; + + case "CommonFiles6432Folder": + case "ProgramFiles6432Folder": + case "System6432Folder": + return WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directoryId, this.Platform); + + default: + return "TARGETDIR"; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs new file mode 100644 index 00000000..759ba303 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs @@ -0,0 +1,60 @@ +// 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.Bind +{ + using System; + using System.Text; + + internal class AssemblyName + { + public AssemblyName(string name, string culture, string version, string fileVersion, string architecture, string publicKeyToken, string type) + { + this.Name = name; + this.Culture = culture ?? "neutral"; + this.Version = version; + this.FileVersion = fileVersion; + this.Architecture = architecture; + + this.StrongNamedSigned = !String.IsNullOrEmpty(publicKeyToken); + this.PublicKeyToken = publicKeyToken; + this.Type = type; + } + + public string Name { get; } + + public string Culture { get; } + + public string Version { get; } + + public string FileVersion { get; } + + public string Architecture { get; } + + public string PublicKeyToken { get; } + + public bool StrongNamedSigned { get; } + + public string Type { get; } + + public string GetFullName() + { + var assemblyName = new StringBuilder(); + + assemblyName.Append(this.Name); + assemblyName.Append(", Version="); + assemblyName.Append(this.Version); + assemblyName.Append(", Culture="); + assemblyName.Append(this.Culture); + assemblyName.Append(", PublicKeyToken="); + assemblyName.Append(this.PublicKeyToken ?? "null"); + + if (!String.IsNullOrEmpty(this.Architecture)) + { + assemblyName.Append(", ProcessorArchitecture="); + assemblyName.Append(this.Architecture); + } + + return assemblyName.ToString(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs new file mode 100644 index 00000000..2103cd32 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs @@ -0,0 +1,214 @@ +// 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.Bind +{ + using System; + using System.IO; + using System.Reflection.Metadata; + using System.Reflection.PortableExecutable; + using System.Security.Cryptography; + using System.Text; + using System.Xml; + using System.Xml.XPath; + using WixToolset.Data; + + internal static class AssemblyNameReader + { + public static AssemblyName ReadAssembly(SourceLineNumber sourceLineNumbers, string assemblyPath, string fileVersion) + { + try + { + using (var stream = File.OpenRead(assemblyPath)) + using (var peReader = new PEReader(stream)) + { + var reader = peReader.GetMetadataReader(); + var headers = peReader.PEHeaders; + + var assembly = reader.GetAssemblyDefinition(); + var attributes = assembly.GetCustomAttributes(); + + var name = ReadString(reader, assembly.Name); + var culture = ReadString(reader, assembly.Culture); + var architecture = ArchitectureFromHeaders(headers); + var version = assembly.Version.ToString(); + var publicKeyToken = ReadPublicKeyToken(reader, assembly.PublicKey); + + // There is a bug in v1 fusion that requires the assembly's "version" attribute + // to be equal to or longer than the "fileVersion" in length when its present; + // the workaround is to prepend zeroes to the last version number in the assembly + // version. + var targetNetfx1 = (headers.CorHeader.MajorRuntimeVersion == 2) && (headers.CorHeader.MinorRuntimeVersion == 0); + if (targetNetfx1 && !String.IsNullOrEmpty(fileVersion) && fileVersion.Length > version.Length) + { + var versionParts = version.Split('.'); + + if (versionParts.Length > 0) + { + var padding = new string('0', fileVersion.Length - version.Length); + + versionParts[versionParts.Length - 1] = String.Concat(padding, versionParts[versionParts.Length - 1]); + version = String.Join(".", versionParts); + } + } + + return new AssemblyName(name, culture, version, fileVersion, architecture, publicKeyToken, null); + } + } + catch (Exception e) when (e is FileNotFoundException || e is BadImageFormatException || e is InvalidOperationException) + { + throw new WixException(ErrorMessages.InvalidAssemblyFile(sourceLineNumbers, assemblyPath, $"{e.GetType().Name}: {e.Message}")); + } + } + + public static AssemblyName ReadAssemblyManifest(SourceLineNumber sourceLineNumbers, string manifestPath) + { + string win32Type = null; + string win32Name = null; + string win32Version = null; + string win32ProcessorArchitecture = null; + string win32PublicKeyToken = null; + + // Loading the dom is expensive we want more performant APIs than the DOM + // Navigator is cheaper than dom. Perhaps there is a cheaper API still. + try + { + var doc = new XPathDocument(manifestPath); + var nav = doc.CreateNavigator(); + nav.MoveToRoot(); + + // This assumes a particular schema for a win32 manifest and does not + // provide error checking if the file does not conform to schema. + // The fallback case here is that nothing is added to the MsiAssemblyName + // table for an out of tolerance Win32 manifest. Perhaps warnings needed. + if (nav.MoveToFirstChild()) + { + while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") + { + nav.MoveToNext(); + } + + if (nav.MoveToFirstChild()) + { + var hasNextSibling = true; + while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) + { + hasNextSibling = nav.MoveToNext(); + } + + if (!hasNextSibling) + { + throw new WixException(ErrorMessages.InvalidManifestContent(sourceLineNumbers, manifestPath)); + } + + if (nav.MoveToAttribute("type", String.Empty)) + { + win32Type = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("name", String.Empty)) + { + win32Name = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("version", String.Empty)) + { + win32Version = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("processorArchitecture", String.Empty)) + { + win32ProcessorArchitecture = nav.Value; + nav.MoveToParent(); + } + + if (nav.MoveToAttribute("publicKeyToken", String.Empty)) + { + win32PublicKeyToken = nav.Value; + nav.MoveToParent(); + } + } + } + } + catch (FileNotFoundException fe) + { + throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, fe.FileName, "AssemblyManifest")); + } + catch (XmlException xe) + { + throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "manifest", xe.Message)); + } + + return new AssemblyName(win32Name, null, win32Version, null, win32ProcessorArchitecture, win32PublicKeyToken, win32Type); + } + + private static string ArchitectureFromHeaders(PEHeaders headers) + { + if (headers.PEHeader.Magic == PEMagic.PE32Plus) + { + return "AMD64"; + } + else if ((headers.CorHeader.Flags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit) + { + return "x86"; + } + else if ((headers.CorHeader.Flags & CorFlags.ILOnly) == CorFlags.ILOnly) + { + return "MSIL"; + } + else + { + // We return "x86" here because that seems to best match the Fusion-based + // GetAssemblyIdentityFromFile() method of acquiring the assembly identity. + return "x86"; + } + } + + private static string ReadString(MetadataReader reader, StringHandle handle) + { + return handle.IsNil ? null : reader.GetString(handle); + } + + private static string ReadPublicKeyToken(MetadataReader reader, BlobHandle handle) + { + if (handle.IsNil) + { + return null; + } + + var bytes = reader.GetBlobBytes(handle); + if (bytes.Length == 0) + { + return null; + } + + var result = new StringBuilder(); + + // If we have the full public key, calculate the public key token from the + // last 8 bytes (in reverse order) of the public key's SHA1 hash. + if (bytes.Length > 8) + { + using (var sha1 = SHA1.Create()) + { + var hash = sha1.ComputeHash(bytes); + + for (var i = 1; i <= 8; ++i) + { + result.Append(hash[hash.Length - i].ToString("X2")); + } + } + } + else + { + foreach (var b in bytes) + { + result.Append(b.ToString("X2")); + } + } + + return result.ToString(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs new file mode 100644 index 00000000..cfa84629 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs @@ -0,0 +1,302 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. + /// + internal class AssignMediaCommand + { + private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB + + public AssignMediaCommand(IntermediateSection section, IMessaging messaging, IEnumerable fileFacades, bool compressed) + { + this.CabinetNameTemplate = "Cab{0}.cab"; + this.Section = section; + this.Messaging = messaging; + this.FileFacades = fileFacades; + this.FilesCompressed = compressed; + } + + private IntermediateSection Section { get; } + + private IMessaging Messaging { get; } + + private IEnumerable FileFacades { get; } + + private bool FilesCompressed { get; } + + private string CabinetNameTemplate { get; set; } + + /// + /// Gets cabinets with their file rows. + /// + public Dictionary> FileFacadesByCabinetMedia { get; private set; } + + /// + /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no. + /// This contains all the files when Package element is marked with compression=no + /// + public IEnumerable UncompressedFileFacades { get; private set; } + + public void Execute() + { + var mediaSymbols = this.Section.Symbols.OfType().ToList(); + var mediaTemplateSymbols = this.Section.Symbols.OfType().ToList(); + + // If both symbols are authored, it is an error. + if (mediaTemplateSymbols.Count > 0 && mediaSymbols.Count > 1) + { + throw new WixException(ErrorMessages.MediaTableCollision(null)); + } + + // If neither symbol is authored, default to a media template. + if (SectionType.Product == this.Section.Type && mediaTemplateSymbols.Count == 0 && mediaSymbols.Count == 0) + { + var mediaTemplate = new WixMediaTemplateSymbol() + { + CabinetTemplate = "cab{0}.cab", + }; + + this.Section.AddSymbol(mediaTemplate); + mediaTemplateSymbols.Add(mediaTemplate); + } + + // When building merge module, all the files go to "#MergeModule.CABinet". + if (SectionType.Module == this.Section.Type) + { + var mergeModuleMediaSymbol = this.Section.AddSymbol(new MediaSymbol + { + Cabinet = "#MergeModule.CABinet", + }); + + this.FileFacadesByCabinetMedia = new Dictionary> + { + { mergeModuleMediaSymbol, this.FileFacades } + }; + + this.UncompressedFileFacades = Array.Empty(); + } + else + { + var filesByCabinetMedia = new Dictionary>(); + var uncompressedFiles = new List(); + + if (mediaTemplateSymbols.Count > 0) + { + this.AutoAssignFiles(mediaTemplateSymbols, mediaSymbols, filesByCabinetMedia, uncompressedFiles); + } + else + { + this.ManuallyAssignFiles(mediaSymbols, filesByCabinetMedia, uncompressedFiles); + } + + this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable)kvp.Value); + + this.UncompressedFileFacades = uncompressedFiles; + } + } + + /// + /// Assign files to cabinets based on MediaTemplate authoring. + /// + private void AutoAssignFiles(List mediaTemplateTable, List mediaSymbols, Dictionary> filesByCabinetMedia, List uncompressedFiles) + { + const int MaxCabIndex = 999; + + ulong currentPreCabSize = 0; + ulong maxPreCabSizeInBytes; + var maxPreCabSizeInMB = 0; + var currentCabIndex = 0; + + MediaSymbol currentMediaRow = null; + + // Remove all previous media symbols since they will be replaced with + // media template. + foreach (var mediaSymbol in mediaSymbols) + { + this.Section.RemoveSymbol(mediaSymbol); + } + + // Auto assign files to cabinets based on maximum uncompressed media size + var mediaTemplateRow = mediaTemplateTable.Single(); + + if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) + { + this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate; + } + + var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); + + try + { + // Override authored mums value if environment variable is authored. + if (!String.IsNullOrEmpty(mumsString)) + { + maxPreCabSizeInMB = Int32.Parse(mumsString); + } + else + { + maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; + } + + maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; + } + catch (FormatException) + { + throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); + } + catch (OverflowException) + { + throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); + } + + var mediaSymbolsByDiskId = new Dictionary(); + + foreach (var facade in this.FileFacades) + { + // When building a product, if the current file is not to be compressed or if + // the package set not to be compressed, don't cab it. + if (SectionType.Product == this.Section.Type && (facade.Uncompressed || !this.FilesCompressed)) + { + uncompressedFiles.Add(facade); + continue; + } + + if (currentCabIndex == MaxCabIndex) + { + // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. + } + else + { + // Update current cab size. + currentPreCabSize += (ulong)facade.FileSize; + + // Overflow due to current file + if (currentPreCabSize > maxPreCabSizeInBytes) + { + currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex); + mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); + filesByCabinetMedia.Add(currentMediaRow, new List()); + + // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize + currentPreCabSize = (ulong)facade.FileSize; + } + else // file fits in the current cab. + { + if (currentMediaRow == null) + { + // Create new cab and MediaRow + currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex); + mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow); + filesByCabinetMedia.Add(currentMediaRow, new List()); + } + } + } + + // Associate current file with current cab. + var cabinetFiles = filesByCabinetMedia[currentMediaRow]; + facade.DiskId = currentCabIndex; + cabinetFiles.Add(facade); + } + + // If there are uncompressed files and no MediaRow, create a default one. + if (uncompressedFiles.Count > 0 && mediaSymbolsByDiskId.Count == 0) + { + var defaultMediaRow = this.Section.AddSymbol(new MediaSymbol(null, new Identifier(AccessModifier.Section, 1)) + { + DiskId = 1, + }); + + mediaSymbolsByDiskId.Add(1, defaultMediaRow); + } + } + + /// + /// Assign files to cabinets based on Media authoring. + /// + private void ManuallyAssignFiles(List mediaSymbols, Dictionary> filesByCabinetMedia, List uncompressedFiles) + { + var mediaSymbolsByDiskId = new Dictionary(); + + if (mediaSymbols.Any()) + { + var cabinetMediaSymbols = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var mediaSymbol in mediaSymbols) + { + // If the Media row has a cabinet, make sure it is unique across all Media rows. + if (!String.IsNullOrEmpty(mediaSymbol.Cabinet)) + { + if (cabinetMediaSymbols.TryGetValue(mediaSymbol.Cabinet, out var existingRow)) + { + this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet)); + this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); + } + else + { + cabinetMediaSymbols.Add(mediaSymbol.Cabinet, mediaSymbol); + } + + filesByCabinetMedia.Add(mediaSymbol, new List()); + } + + mediaSymbolsByDiskId.Add(mediaSymbol.DiskId, mediaSymbol); + } + } + + foreach (var facade in this.FileFacades) + { + if (!mediaSymbolsByDiskId.TryGetValue(facade.DiskId, out var mediaSymbol)) + { + this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId)); + continue; + } + + // When building a product, if the current file is to be uncompressed or if + // the package set not to be compressed, don't cab it. + var compressed = facade.Compressed; + var uncompressed = facade.Uncompressed; + if (SectionType.Product == this.Section.Type && (uncompressed || (!compressed && !this.FilesCompressed))) + { + uncompressedFiles.Add(facade); + } + else // file is marked compressed. + { + if (filesByCabinetMedia.TryGetValue(mediaSymbol, out var cabinetFiles)) + { + cabinetFiles.Add(facade); + } + else + { + this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.SourceLineNumber, facade.Id, facade.DiskId)); + } + } + } + } + + /// + /// Adds a symbol to the section with cab name template filled in. + /// + /// + /// + /// + private MediaSymbol AddMediaSymbol(WixMediaTemplateSymbol mediaTemplateSymbol, int cabIndex) + { + return this.Section.AddSymbol(new MediaSymbol(mediaTemplateSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, cabIndex)) + { + DiskId = cabIndex, + Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex), + CompressionLevel = mediaTemplateSymbol.CompressionLevel, + }); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs new file mode 100644 index 00000000..76bcd532 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs @@ -0,0 +1,1305 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Text.RegularExpressions; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Services; + + /// + /// Include transforms in a patch. + /// + internal class AttachPatchTransformsCommand + { + private static readonly string[] PatchUninstallBreakingTables = new[] + { + "AppId", + "BindImage", + "Class", + "Complus", + "CreateFolder", + "DuplicateFile", + "Environment", + "Extension", + "Font", + "IniFile", + "IsolatedComponent", + "LockPermissions", + "MIME", + "MoveFile", + "MsiLockPermissionsEx", + "MsiServiceConfig", + "MsiServiceConfigFailureActions", + "ODBCAttribute", + "ODBCDataSource", + "ODBCDriver", + "ODBCSourceAttribute", + "ODBCTranslator", + "ProgId", + "PublishComponent", + "RemoveIniFile", + "SelfReg", + "ServiceControl", + "ServiceInstall", + "TypeLib", + "Verb", + }; + + private readonly TableDefinitionCollection tableDefinitions; + + public AttachPatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, IEnumerable transforms) + { + this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Intermediate = intermediate; + this.Transforms = transforms; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private Intermediate Intermediate { get; } + + private IEnumerable Transforms { get; } + + public IEnumerable SubStorages { get; private set; } + + public IEnumerable Execute() + { + var subStorages = new List(); + + if (this.Transforms == null || !this.Transforms.Any()) + { + this.Messaging.Write(ErrorMessages.PatchWithoutTransforms()); + return subStorages; + } + + var summaryInfo = this.ExtractPatchSummaryInfo(); + + var section = this.Intermediate.Sections.First(); + + var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList(); + + // Get the patch id from the WixPatchId symbol. + var patchSymbol = symbols.OfType().FirstOrDefault(); + + if (String.IsNullOrEmpty(patchSymbol.Id?.Id)) + { + this.Messaging.Write(ErrorMessages.ExpectedPatchIdInWixMsp()); + return subStorages; + } + + if (String.IsNullOrEmpty(patchSymbol.ClientPatchId)) + { + this.Messaging.Write(ErrorMessages.ExpectedClientPatchIdInWixMsp()); + return subStorages; + } + + // enumerate patch.Media to map diskId to Media row + var patchMediaByDiskId = symbols.OfType().ToDictionary(t => t.DiskId); + + if (patchMediaByDiskId.Count == 0) + { + this.Messaging.Write(ErrorMessages.ExpectedMediaRowsInWixMsp()); + return subStorages; + } + + // populate MSP summary information + var patchMetadata = this.PopulateSummaryInformation(summaryInfo, symbols, patchSymbol); + + // enumerate transforms + var productCodes = new SortedSet(); + var transformNames = new List(); + var validTransform = new List>(); + + var baselineSymbolsById = symbols.OfType().ToDictionary(t => t.Id.Id); + + foreach (var mainTransform in this.Transforms) + { + var baselineSymbol = baselineSymbolsById[mainTransform.Baseline]; + + var patchRefSymbols = symbols.OfType().ToList(); + if (patchRefSymbols.Count > 0) + { + if (!this.ReduceTransform(mainTransform.Transform, patchRefSymbols)) + { + // transform has none of the content authored into this patch + continue; + } + } + + // Validate the transform doesn't break any patch specific rules. + this.Validate(mainTransform); + + // ensure consistent File.Sequence within each Media + var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId]; + + // Ensure that files are sequenced after the last file in any transform. + var transformMediaTable = mainTransform.Transform.Tables["Media"]; + if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) + { + foreach (MediaRow transformMediaRow in transformMediaTable.Rows) + { + if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < transformMediaRow.LastSequence) + { + // The Binder will pre-increment the sequence. + mediaSymbol.LastSequence = transformMediaRow.LastSequence; + } + } + } + + // Use the Media/@DiskId if greater than the last sequence for backward compatibility. + if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < mediaSymbol.DiskId) + { + mediaSymbol.LastSequence = mediaSymbol.DiskId; + } + + // Ignore media table in the transform. + mainTransform.Transform.Tables.Remove("Media"); + mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); + + var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchSymbol, mainTransform.Transform, mediaSymbol, baselineSymbol, out var productCode); + + productCode = productCode.ToUpperInvariant(); + productCodes.Add(productCode); + validTransform.Add(Tuple.Create(productCode, mainTransform.Transform)); + + // attach these transforms to the patch object + // TODO: is this an acceptable way to auto-generate transform stream names? + var transformName = mainTransform.Baseline + "." + validTransform.Count.ToString(CultureInfo.InvariantCulture); + subStorages.Add(new SubStorage(transformName, mainTransform.Transform)); + subStorages.Add(new SubStorage("#" + transformName, pairedTransform)); + + transformNames.Add(":" + transformName); + transformNames.Add(":#" + transformName); + } + + if (validTransform.Count == 0) + { + this.Messaging.Write(ErrorMessages.PatchWithoutValidTransforms()); + return subStorages; + } + + // Validate that a patch authored as removable is actually removable + if (patchMetadata.TryGetValue("AllowRemoval", out var allowRemoval) && allowRemoval.Value == "1") + { + var uninstallable = true; + + foreach (var entry in validTransform) + { + uninstallable &= this.CheckUninstallableTransform(entry.Item1, entry.Item2); + } + + if (!uninstallable) + { + this.Messaging.Write(ErrorMessages.PatchNotRemovable()); + return subStorages; + } + } + + // Finish filling tables with transform-dependent data. + productCodes = FinalizePatchProductCodes(symbols, productCodes); + + // Semicolon delimited list of the product codes that can accept the patch. + summaryInfo.Add(SummaryInformationType.PatchProductCodes, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) + { + PropertyId = SummaryInformationType.PatchProductCodes, + Value = String.Join(";", productCodes) + }); + + // Semicolon delimited list of transform substorage names in the order they are applied. + summaryInfo.Add(SummaryInformationType.TransformNames, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers) + { + PropertyId = SummaryInformationType.TransformNames, + Value = String.Join(";", transformNames) + }); + + // Put the summary information that was extracted back in now that it is updated. + foreach (var readSummaryInfo in summaryInfo.Values.OrderBy(s => s.PropertyId)) + { + section.AddSymbol(readSummaryInfo); + } + + this.SubStorages = subStorages; + + return subStorages; + } + + private Dictionary ExtractPatchSummaryInfo() + { + var result = new Dictionary(); + + foreach (var section in this.Intermediate.Sections) + { + // Remove all summary information from the symbols and remember those that + // are not calculated or reserved. + foreach (var patchSummaryInfo in section.Symbols.OfType().ToList()) + { + section.RemoveSymbol(patchSummaryInfo); + + if (patchSummaryInfo.PropertyId != SummaryInformationType.PatchProductCodes && + patchSummaryInfo.PropertyId != SummaryInformationType.PatchCode && + patchSummaryInfo.PropertyId != SummaryInformationType.PatchInstallerRequirement && + patchSummaryInfo.PropertyId != SummaryInformationType.Reserved11 && + patchSummaryInfo.PropertyId != SummaryInformationType.Reserved14 && + patchSummaryInfo.PropertyId != SummaryInformationType.Reserved16) + { + result.Add(patchSummaryInfo.PropertyId, patchSummaryInfo); + } + } + } + + return result; + } + + private Dictionary PopulateSummaryInformation(Dictionary summaryInfo, List symbols, WixPatchSymbol patchSymbol) + { + // PID_CODEPAGE + if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage)) + { + // Set the code page by default to the same code page for the + // string pool in the database. + AddSummaryInformation(SummaryInformationType.Codepage, patchSymbol.Codepage?.ToString(CultureInfo.InvariantCulture) ?? "0", patchSymbol.SourceLineNumbers); + } + + // GUID patch code for the patch. + AddSummaryInformation(SummaryInformationType.PatchCode, patchSymbol.Id.Id, patchSymbol.SourceLineNumbers); + + // Indicates the minimum Windows Installer version that is required to install the patch. + AddSummaryInformation(SummaryInformationType.PatchInstallerRequirement, ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture), patchSymbol.SourceLineNumbers); + + if (!summaryInfo.ContainsKey(SummaryInformationType.Security)) + { + AddSummaryInformation(SummaryInformationType.Security, "4", patchSymbol.SourceLineNumbers); // Read-only enforced; + } + + // Use authored comments or default to display name. + MsiPatchMetadataSymbol commentsSymbol = null; + + var metadataSymbols = symbols.OfType().Where(t => String.IsNullOrEmpty(t.Company)).ToDictionary(t => t.Property); + + if (!summaryInfo.ContainsKey(SummaryInformationType.Title) && + metadataSymbols.TryGetValue("DisplayName", out var displayName)) + { + AddSummaryInformation(SummaryInformationType.Title, displayName.Value, displayName.SourceLineNumbers); + + // Default comments to use display name as-is. + commentsSymbol = displayName; + } + + // TODO: This code below seems unnecessary given the codepage is set at the top of this method. + //if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage) && + // metadataValues.TryGetValue("CodePage", out var codepage)) + //{ + // AddSummaryInformation(SummaryInformationType.Codepage, codepage); + //} + + if (!summaryInfo.ContainsKey(SummaryInformationType.PatchPackageName) && + metadataSymbols.TryGetValue("Description", out var description)) + { + AddSummaryInformation(SummaryInformationType.PatchPackageName, description.Value, description.SourceLineNumbers); + } + + if (!summaryInfo.ContainsKey(SummaryInformationType.Author) && + metadataSymbols.TryGetValue("ManufacturerName", out var manufacturer)) + { + AddSummaryInformation(SummaryInformationType.Author, manufacturer.Value, manufacturer.SourceLineNumbers); + } + + // Special metadata marshalled through the build. + //var wixMetadataValues = symbols.OfType().ToDictionary(t => t.Id.Id, t => t.Value); + + //if (wixMetadataValues.TryGetValue("Comments", out var wixComments)) + if (metadataSymbols.TryGetValue("Comments", out var wixComments)) + { + commentsSymbol = wixComments; + } + + // Write the package comments to summary info. + if (!summaryInfo.ContainsKey(SummaryInformationType.Comments) && + commentsSymbol != null) + { + AddSummaryInformation(SummaryInformationType.Comments, commentsSymbol.Value, commentsSymbol.SourceLineNumbers); + } + + return metadataSymbols; + + void AddSummaryInformation(SummaryInformationType type, string value, SourceLineNumber sourceLineNumber) + { + summaryInfo.Add(type, new SummaryInformationSymbol(sourceLineNumber) + { + PropertyId = type, + Value = value + }); + } + } + + /// + /// Ensure transform is uninstallable. + /// + /// Product code in transform. + /// Transform generated by torch. + /// True if the transform is uninstallable + private bool CheckUninstallableTransform(string productCode, WindowsInstallerData transform) + { + var success = true; + + foreach (var tableName in PatchUninstallBreakingTables) + { + if (transform.TryGetTable(tableName, out var table)) + { + foreach (var row in table.Rows) + { + if (row.Operation == RowOperation.Add) + { + success = false; + + var primaryKey = row.GetPrimaryKey('/') ?? String.Empty; + + this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey)); + } + } + } + } + + return success; + } + + /// + /// Reduce the transform according to the patch references. + /// + /// transform generated by torch. + /// Table contains patch family filter. + /// true if the transform is not empty + private bool ReduceTransform(WindowsInstallerData transform, IEnumerable patchRefSymbols) + { + // identify sections to keep + var oldSections = new Dictionary(); + var newSections = new Dictionary(); + var tableKeyRows = new Dictionary>(); + var sequenceList = new List
(); + var componentFeatureAddsIndex = new Dictionary>(); + var customActionTable = new Dictionary(); + var directoryTableAdds = new Dictionary(); + var featureTableAdds = new Dictionary(); + var keptComponents = new Dictionary(); + var keptDirectories = new Dictionary(); + var keptFeatures = new Dictionary(); + var keptLockPermissions = new HashSet(); + var keptMsiLockPermissionExs = new HashSet(); + + var componentCreateFolderIndex = new Dictionary>(); + var directoryLockPermissionsIndex = new Dictionary>(); + var directoryMsiLockPermissionsExIndex = new Dictionary>(); + + foreach (var patchRefSymbol in patchRefSymbols) + { + var tableName = patchRefSymbol.Table; + var key = patchRefSymbol.PrimaryKeys; + + // Short circuit filtering if all changes should be included. + if ("*" == tableName && "*" == key) + { + RemoveProductCodeFromTransform(transform); + return true; + } + + if (!transform.Tables.TryGetTable(tableName, out var table)) + { + // Table not found. + continue; + } + + // Index the table. + if (!tableKeyRows.TryGetValue(tableName, out var keyRows)) + { + keyRows = new Dictionary(); + tableKeyRows.Add(tableName, keyRows); + + foreach (var newRow in table.Rows) + { + var primaryKey = newRow.GetPrimaryKey(); + keyRows.Add(primaryKey, newRow); + } + } + + if (!keyRows.TryGetValue(key, out var row)) + { + // Row not found. + continue; + } + + // Differ.sectionDelimiter + var sections = row.SectionId.Split('/'); + oldSections[sections[0]] = row; + newSections[sections[1]] = row; + } + + // throw away sections not referenced + var keptRows = 0; + Table directoryTable = null; + Table featureTable = null; + Table lockPermissionsTable = null; + Table msiLockPermissionsTable = null; + + foreach (var table in transform.Tables) + { + if ("_SummaryInformation" == table.Name) + { + continue; + } + + if (table.Name == "AdminExecuteSequence" + || table.Name == "AdminUISequence" + || table.Name == "AdvtExecuteSequence" + || table.Name == "InstallUISequence" + || table.Name == "InstallExecuteSequence") + { + sequenceList.Add(table); + continue; + } + + for (var i = 0; i < table.Rows.Count; i++) + { + var row = table.Rows[i]; + + if (table.Name == "CreateFolder") + { + var createFolderComponentId = row.FieldAsString(1); + + if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList)) + { + directoryList = new List(); + componentCreateFolderIndex.Add(createFolderComponentId, directoryList); + } + + directoryList.Add(row.FieldAsString(0)); + } + + if (table.Name == "CustomAction") + { + customActionTable.Add(row.FieldAsString(0), row); + } + + if (table.Name == "Directory") + { + directoryTable = table; + if (RowOperation.Add == row.Operation) + { + directoryTableAdds.Add(row.FieldAsString(0), row); + } + } + + if (table.Name == "Feature") + { + featureTable = table; + if (RowOperation.Add == row.Operation) + { + featureTableAdds.Add(row.FieldAsString(0), row); + } + } + + if (table.Name == "FeatureComponents") + { + if (RowOperation.Add == row.Operation) + { + var featureId = row.FieldAsString(0); + var componentId = row.FieldAsString(1); + + if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList)) + { + featureList = new List(); + componentFeatureAddsIndex.Add(componentId, featureList); + } + + featureList.Add(featureId); + } + } + + if (table.Name == "LockPermissions") + { + lockPermissionsTable = table; + if ("CreateFolder" == row.FieldAsString(1)) + { + var directoryId = row.FieldAsString(0); + + if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList)) + { + rowList = new List(); + directoryLockPermissionsIndex.Add(directoryId, rowList); + } + + rowList.Add(row); + } + } + + if (table.Name == "MsiLockPermissionsEx") + { + msiLockPermissionsTable = table; + if ("CreateFolder" == row.FieldAsString(1)) + { + var directoryId = row.FieldAsString(0); + + if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList)) + { + rowList = new List(); + directoryMsiLockPermissionsExIndex.Add(directoryId, rowList); + } + + rowList.Add(row); + } + } + + if (null == row.SectionId) + { + table.Rows.RemoveAt(i); + i--; + } + else + { + var sections = row.SectionId.Split('/'); + // ignore the row without section id. + if (0 == sections[0].Length && 0 == sections[1].Length) + { + table.Rows.RemoveAt(i); + i--; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + if ("Component" == table.Name) + { + keptComponents.Add(row.FieldAsString(0), row); + } + + if ("Directory" == table.Name) + { + keptDirectories.Add(row.FieldAsString(0), row); + } + + if ("Feature" == table.Name) + { + keptFeatures.Add(row.FieldAsString(0), row); + } + + keptRows++; + } + else + { + table.Rows.RemoveAt(i); + i--; + } + } + } + } + + keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); + + if (null != directoryTable) + { + foreach (var componentRow in keptComponents.Values) + { + var componentId = componentRow.FieldAsString(0); + + if (RowOperation.Add == componentRow.Operation) + { + // Make sure each added component has its required directory and feature heirarchy. + var directoryId = componentRow.FieldAsString(2); + while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow)) + { + if (!keptDirectories.ContainsKey(directoryId)) + { + directoryTable.Rows.Add(directoryRow); + keptDirectories.Add(directoryId, directoryRow); + keptRows++; + } + + directoryId = directoryRow.FieldAsString(1); + } + + if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds)) + { + foreach (var featureId in componentFeatureIds) + { + var currentFeatureId = featureId; + while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow)) + { + if (!keptFeatures.ContainsKey(currentFeatureId)) + { + featureTable.Rows.Add(featureRow); + keptFeatures.Add(currentFeatureId, featureRow); + keptRows++; + } + + currentFeatureId = featureRow.FieldAsString(1); + } + } + } + } + + // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept. + foreach (var keptComponentId in keptComponents.Keys) + { + if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList)) + { + foreach (var directoryId in directoryList) + { + if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList)) + { + foreach (var lockPermissionsRow in lockPermissionsRowList) + { + var key = lockPermissionsRow.GetPrimaryKey('/'); + if (keptLockPermissions.Add(key)) + { + lockPermissionsTable.Rows.Add(lockPermissionsRow); + keptRows++; + } + } + } + + if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList)) + { + foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList) + { + var key = msiLockPermissionsExRow.GetPrimaryKey('/'); + if (keptMsiLockPermissionExs.Add(key)) + { + msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow); + keptRows++; + } + } + } + } + } + } + } + } + + keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); + + // Delete tables that are empty. + var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList(); + + foreach (var tableName in tablesToDelete) + { + transform.Tables.Remove(tableName); + } + + return keptRows > 0; + } + + private void Validate(PatchTransform patchTransform) + { + var transformPath = patchTransform.Baseline; // TODO: this is used in error messages, how best to set it? + var transform = patchTransform.Transform; + + // Changing the ProdocutCode in a patch transform is not recommended. + if (transform.TryGetTable("Property", out var propertyTable)) + { + foreach (var row in propertyTable.Rows) + { + // Only interested in modified rows; fast check. + if (RowOperation.Modify == row.Operation && + "ProductCode".Equals(row.FieldAsString(0), StringComparison.Ordinal)) + { + this.Messaging.Write(WarningMessages.MajorUpgradePatchNotRecommended()); + } + } + } + + // If there is nothing in the component table we can return early because the remaining checks are component based. + if (!transform.TryGetTable("Component", out var componentTable)) + { + return; + } + + // Index Feature table row operations + var featureOps = new Dictionary(); + if (transform.TryGetTable("Feature", out var featureTable)) + { + foreach (var row in featureTable.Rows) + { + featureOps[row.FieldAsString(0)] = row.Operation; + } + } + + // Index Component table and check for keypath modifications + var componentKeyPath = new Dictionary(); + var deletedComponent = new Dictionary(); + foreach (var row in componentTable.Rows) + { + var id = row.FieldAsString(0); + var keypath = row.FieldAsString(5) ?? String.Empty; + + componentKeyPath.Add(id, keypath); + + if (RowOperation.Delete == row.Operation) + { + deletedComponent.Add(id, row); + } + else if (RowOperation.Modify == row.Operation) + { + if (row.Fields[1].Modified) + { + // Changing the guid of a component is equal to deleting the old one and adding a new one. + deletedComponent.Add(id, row); + } + + // If the keypath is modified its an error + if (row.Fields[5].Modified) + { + this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, id, transformPath)); + } + } + } + + // Verify changes in the file table + if (transform.TryGetTable("File", out var fileTable)) + { + var componentWithChangedKeyPath = new Dictionary(); + foreach (FileRow row in fileTable.Rows) + { + if (RowOperation.None == row.Operation) + { + continue; + } + + var fileId = row.File; + var componentId = row.Component; + + // If this file is the keypath of a component + if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal)) + { + if (row.Fields[2].Modified) + { + // You can't change the filename of a file that is the keypath of a component. + this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, componentId, transformPath)); + } + + if (!componentWithChangedKeyPath.ContainsKey(componentId)) + { + componentWithChangedKeyPath.Add(componentId, fileId); + } + } + + if (RowOperation.Delete == row.Operation) + { + // If the file is removed from a component that is not deleted. + if (!deletedComponent.ContainsKey(componentId)) + { + var foundRemoveFileEntry = false; + var filename = this.BackendHelper.GetMsiFileName(row.FieldAsString(2), false, true); + + if (transform.TryGetTable("RemoveFile", out var removeFileTable)) + { + foreach (var removeFileRow in removeFileTable.Rows) + { + if (RowOperation.Delete == removeFileRow.Operation) + { + continue; + } + + if (componentId == removeFileRow.FieldAsString(1)) + { + // Check if there is a RemoveFile entry for this file + if (null != removeFileRow[2]) + { + var removeFileName = this.BackendHelper.GetMsiFileName(removeFileRow.FieldAsString(2), false, true); + + // Convert the MSI format for a wildcard string to Regex format. + removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); + + var regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + if (regex.IsMatch(filename)) + { + foundRemoveFileEntry = true; + break; + } + } + } + } + } + + if (!foundRemoveFileEntry) + { + this.Messaging.Write(WarningMessages.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId)); + } + } + } + } + } + + var featureComponentsTable = transform.Tables["FeatureComponents"]; + + if (0 < deletedComponent.Count) + { + // Index FeatureComponents table. + var featureComponents = new Dictionary>(); + + if (null != featureComponentsTable) + { + foreach (var row in featureComponentsTable.Rows) + { + var componentId = row.FieldAsString(1); + + if (!featureComponents.TryGetValue(componentId, out var features)) + { + features = new List(); + featureComponents.Add(componentId, features); + } + + features.Add(row.FieldAsString(0)); + } + } + + // Check to make sure if a component was deleted, the feature was too. + foreach (var entry in deletedComponent) + { + if (featureComponents.TryGetValue(entry.Key, out var features)) + { + foreach (var featureId in features) + { + if (!featureOps.TryGetValue(featureId, out var op) || op != RowOperation.Delete) + { + // The feature was not deleted. + this.Messaging.Write(ErrorMessages.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, transformPath)); + } + } + } + } + } + + // Warn if new components are added to existing features + if (null != featureComponentsTable) + { + foreach (var row in featureComponentsTable.Rows) + { + if (RowOperation.Add == row.Operation) + { + // Check if the feature is in the Feature table + var feature_ = row.FieldAsString(0); + var component_ = row.FieldAsString(1); + + // Features may not be present if not referenced + if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_]) + { + this.Messaging.Write(WarningMessages.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, transformPath)); + } + } + } + } + } + + /// + /// Remove the ProductCode property from the transform. + /// + /// The transform. + /// + /// Changing the ProductCode is not supported in a patch. + /// + private static void RemoveProductCodeFromTransform(WindowsInstallerData transform) + { + if (transform.Tables.TryGetTable("Property", out var propertyTable)) + { + for (var i = 0; i < propertyTable.Rows.Count; ++i) + { + var propertyRow = propertyTable.Rows[i]; + var property = (string)propertyRow[0]; + + if ("ProductCode" == property) + { + propertyTable.Rows.RemoveAt(i); + break; + } + } + } + } + + /// + /// Check if the section is in a PatchFamily. + /// + /// Section id in target wixout + /// Section id in upgrade wixout + /// Dictionary contains section id should be kept in the baseline wixout. + /// Dictionary contains section id should be kept in the upgrade wixout. + /// true if section in patch family + private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary oldSections, Dictionary newSections) + { + var result = false; + + if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection))) + { + result = true; + } + else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(newSection))) + { + result = true; + } + + return result; + } + + /// + /// Reduce the transform sequence tables. + /// + /// ArrayList of tables to be reduced + /// Hashtable contains section id should be kept in the baseline wixout. + /// Hashtable contains section id should be kept in the target wixout. + /// Hashtable contains all the rows in the CustomAction table. + /// Number of rows left + private static int ReduceTransformSequenceTable(List
sequenceList, Dictionary oldSections, Dictionary newSections, Dictionary customAction) + { + var keptRows = 0; + + foreach (var currentTable in sequenceList) + { + for (var i = 0; i < currentTable.Rows.Count; i++) + { + var row = currentTable.Rows[i]; + var actionName = row.Fields[0].Data.ToString(); + var sections = row.SectionId.Split('/'); + var isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0); + + if (row.Operation == RowOperation.None) + { + // Ignore the rows without section id. + if (isSectionIdEmpty) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + currentTable.Rows.RemoveAt(i); + i--; + } + } + else if (row.Operation == RowOperation.Modify) + { + var sequenceChanged = row.Fields[2].Modified; + var conditionChanged = row.Fields[1].Modified; + + if (sequenceChanged && !conditionChanged) + { + keptRows++; + } + else if (!sequenceChanged && conditionChanged) + { + if (isSectionIdEmpty) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + currentTable.Rows.RemoveAt(i); + i--; + } + } + else if (sequenceChanged && conditionChanged) + { + if (isSectionIdEmpty) + { + row.Fields[1].Modified = false; + keptRows++; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + row.Fields[1].Modified = false; + keptRows++; + } + } + } + else if (row.Operation == RowOperation.Delete) + { + if (isSectionIdEmpty) + { + // it is a stardard action which is added by wix, we should keep this action. + row.Operation = RowOperation.None; + keptRows++; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + if (customAction.ContainsKey(actionName)) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else + { + // it is a stardard action, we should keep this action. + row.Operation = RowOperation.None; + keptRows++; + } + } + } + else if (row.Operation == RowOperation.Add) + { + if (isSectionIdEmpty) + { + keptRows++; + } + else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) + { + keptRows++; + } + else + { + if (customAction.ContainsKey(actionName)) + { + currentTable.Rows.RemoveAt(i); + i--; + } + else + { + keptRows++; + } + } + } + } + } + + return keptRows; + } + + /// + /// Create the #transform for the given main transform. + /// + private WindowsInstallerData BuildPairedTransform(Dictionary summaryInfo, Dictionary patchMetadata, WixPatchSymbol patchIdSymbol, WindowsInstallerData mainTransform, MediaSymbol mediaSymbol, WixPatchBaselineSymbol baselineSymbol, out string productCode) + { + productCode = null; + + var pairedTransform = new WindowsInstallerData(null) + { + Type = OutputType.Transform, + Codepage = mainTransform.Codepage + }; + + // lookup productVersion property to correct summaryInformation + var newProductVersion = mainTransform.Tables["Property"]?.Rows.FirstOrDefault(r => r.FieldAsString(0) == "ProductVersion")?.FieldAsString(1); + + var mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; + var mainSummaryRows = mainSummaryTable.Rows.ToDictionary(r => r.FieldAsInteger(0)); + + var baselineValidationFlags = ((int)baselineSymbol.ValidationFlags).ToString(CultureInfo.InvariantCulture); + + if (!mainSummaryRows.ContainsKey((int)SummaryInformationType.TransformValidationFlags)) + { + var mainSummaryRow = mainSummaryTable.CreateRow(baselineSymbol.SourceLineNumbers); + mainSummaryRow[0] = (int)SummaryInformationType.TransformValidationFlags; + mainSummaryRow[1] = baselineValidationFlags; + } + + // copy summary information from core transform + var pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); + + foreach (var mainSummaryRow in mainSummaryTable.Rows) + { + var type = (SummaryInformationType)mainSummaryRow.FieldAsInteger(0); + var value = mainSummaryRow.FieldAsString(1); + switch (type) + { + case SummaryInformationType.TransformProductCodes: + var propertyData = value.Split(';'); + var oldProductVersion = propertyData[0].Substring(38); + var upgradeCode = propertyData[2]; + productCode = propertyData[0].Substring(0, 38); + + if (newProductVersion == null) + { + newProductVersion = oldProductVersion; + } + + // Force mainTranform to 'old;new;upgrade' and pairedTransform to 'new;new;upgrade' + mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); + value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); + break; + case SummaryInformationType.TransformValidationFlags: // use validation flags authored into the patch XML. + value = baselineValidationFlags; + mainSummaryRow[1] = value; + break; + } + + var pairedSummaryRow = pairedSummaryTable.CreateRow(mainSummaryRow.SourceLineNumbers); + pairedSummaryRow[0] = mainSummaryRow[0]; + pairedSummaryRow[1] = value; + } + + if (productCode == null) + { + this.Messaging.Write(ErrorMessages.CouldNotDetermineProductCodeFromTransformSummaryInfo()); + return null; + } + + // Copy File table + if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count) + { + var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); + + foreach (FileRow mainFileRow in mainFileTable.Rows) + { + // Set File.Sequence to non null to satisfy transform bind. + mainFileRow.Sequence = 1; + + // Delete's don't need rows in the paired transform. + if (mainFileRow.Operation == RowOperation.Delete) + { + continue; + } + + var pairedFileRow = (FileRow)pairedFileTable.CreateRow(mainFileRow.SourceLineNumbers); + pairedFileRow.Operation = RowOperation.Modify; + mainFileRow.CopyTo(pairedFileRow); + + // Override authored media for patch bind. + mainFileRow.DiskId = mediaSymbol.DiskId; + + // Suppress any change to File.Sequence to avoid bloat. + mainFileRow.Fields[7].Modified = false; + + // Force File row to appear in the transform. + switch (mainFileRow.Operation) + { + case RowOperation.Modify: + case RowOperation.Add: + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = mainFileRow.Operation; + break; + default: + pairedFileRow.Fields[6].Modified = false; + break; + } + } + } + + // Add Media row to pairedTransform + var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); + var pairedMediaRow = (MediaRow)pairedMediaTable.CreateRow(mediaSymbol.SourceLineNumbers); + pairedMediaRow.Operation = RowOperation.Add; + pairedMediaRow.DiskId = mediaSymbol.DiskId; + pairedMediaRow.LastSequence = mediaSymbol.LastSequence ?? 0; + pairedMediaRow.DiskPrompt = mediaSymbol.DiskPrompt; + pairedMediaRow.Cabinet = mediaSymbol.Cabinet; + pairedMediaRow.VolumeLabel = mediaSymbol.VolumeLabel; + pairedMediaRow.Source = mediaSymbol.Source; + + // Add PatchPackage for this Media + var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); + pairedPackageTable.Operation = TableOperation.Add; + var pairedPackageRow = pairedPackageTable.CreateRow(mediaSymbol.SourceLineNumbers); + pairedPackageRow.Operation = RowOperation.Add; + pairedPackageRow[0] = patchIdSymbol.Id.Id; + pairedPackageRow[1] = mediaSymbol.DiskId; + + // Add the property to the patch transform's Property table. + var pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); + pairedPropertyTable.Operation = TableOperation.Add; + + // Add property to both identify client patches and whether those patches are removable or not + patchMetadata.TryGetValue("AllowRemoval", out var allowRemovalSymbol); + + var pairedPropertyRow = pairedPropertyTable.CreateRow(allowRemovalSymbol?.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".AllowRemoval"); + pairedPropertyRow[1] = allowRemovalSymbol?.Value ?? "0"; + + // Add this patch code GUID to the patch transform to identify + // which patches are installed, including in multi-patch + // installations. + pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".PatchCode"); + pairedPropertyRow[1] = patchIdSymbol.Id.Id; + + // Add PATCHNEWPACKAGECODE to apply to admin layouts. + pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; + pairedPropertyRow[1] = patchIdSymbol.Id.Id; + + // Add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts. + if (summaryInfo.TryGetValue(SummaryInformationType.Subject, out var subjectSymbol)) + { + pairedPropertyRow = pairedPropertyTable.CreateRow(subjectSymbol.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; + pairedPropertyRow[1] = subjectSymbol.Value; + } + + if (summaryInfo.TryGetValue(SummaryInformationType.Comments, out var commentsSymbol)) + { + pairedPropertyRow = pairedPropertyTable.CreateRow(commentsSymbol.SourceLineNumbers); + pairedPropertyRow.Operation = RowOperation.Add; + pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; + pairedPropertyRow[1] = commentsSymbol.Value; + } + + return pairedTransform; + } + + private static SortedSet FinalizePatchProductCodes(List symbols, SortedSet productCodes) + { + var patchTargetSymbols = symbols.OfType().ToList(); + + if (patchTargetSymbols.Any()) + { + var targets = new SortedSet(); + var replace = true; + foreach (var wixPatchTargetRow in patchTargetSymbols) + { + var target = wixPatchTargetRow.ProductCode.ToUpperInvariant(); + if (target == "*") + { + replace = false; + } + else + { + targets.Add(target); + } + } + + // Replace the target ProductCodes with the authored list. + if (replace) + { + productCodes = targets; + } + else + { + // Copy the authored target ProductCodes into the list. + foreach (var target in targets) + { + productCodes.Add(target); + } + } + } + + return productCodes; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs new file mode 100644 index 00000000..9f36cd78 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -0,0 +1,646 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Binds a databse. + /// + internal class BindDatabaseCommand + { + // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. + internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); + + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, string cubeFile) : this(context, backendExtension, null, cubeFile) + { + } + + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, string cubeFile) + { + this.ServiceProvider = context.ServiceProvider; + + this.Messaging = context.ServiceProvider.GetService(); + + this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService(); + + this.PathResolver = this.ServiceProvider.GetService(); + + this.CabbingThreadCount = context.CabbingThreadCount; + this.CabCachePath = context.CabCachePath; + this.DefaultCompressionLevel = context.DefaultCompressionLevel; + this.DelayedFields = context.DelayedFields; + this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; + this.FileSystemManager = new FileSystemManager(context.FileSystemExtensions); + this.Intermediate = context.IntermediateRepresentation; + this.IntermediateFolder = context.IntermediateFolder; + this.OutputPath = context.OutputPath; + this.OutputPdbPath = context.PdbPath; + this.PdbType = context.PdbType; + this.ResolvedCodepage = context.ResolvedCodepage; + this.ResolvedSummaryInformationCodepage = context.ResolvedSummaryInformationCodepage; + this.ResolvedLcid = context.ResolvedLcid; + this.SuppressLayout = context.SuppressLayout; + + this.SubStorages = subStorages; + + this.SuppressValidation = context.SuppressValidation; + this.Ices = context.Ices; + this.SuppressedIces = context.SuppressIces; + this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile }; + + this.BackendExtensions = backendExtension; + } + + public IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private int CabbingThreadCount { get; } + + private string CabCachePath { get; } + + private CompressionLevel? DefaultCompressionLevel { get; } + + public IEnumerable DelayedFields { get; } + + public IEnumerable ExpectedEmbeddedFiles { get; } + + public FileSystemManager FileSystemManager { get; } + + public bool DeltaBinaryPatch { get; set; } + + private IEnumerable BackendExtensions { get; } + + private IEnumerable SubStorages { get; } + + private Intermediate Intermediate { get; } + + private string OutputPath { get; } + + public PdbType PdbType { get; set; } + + private string OutputPdbPath { get; } + + private int? ResolvedCodepage { get; } + + private int? ResolvedSummaryInformationCodepage { get; } + + private int? ResolvedLcid { get; } + + private bool SuppressAddingValidationRows { get; } + + private bool SuppressLayout { get; } + + private string IntermediateFolder { get; } + + private bool SuppressValidation { get; } + + private IEnumerable Ices { get; } + + private IEnumerable SuppressedIces { get; } + + private IEnumerable CubeFiles { get; } + + public IBindResult Execute() + { + if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) + { + this.Messaging.Write(ErrorMessages.IntermediatesMustBeResolved(this.Intermediate.Id)); + } + + var section = this.Intermediate.Sections.Single(); + + var packageSymbol = (section.Type == SectionType.Product) ? this.GetSingleSymbol(section) : null; + var moduleSymbol = (section.Type == SectionType.Module) ? this.GetSingleSymbol(section) : null; + var patchSymbol = (section.Type == SectionType.Patch) ? this.GetSingleSymbol(section) : null; + + var fileTransfers = new List(); + var trackedFiles = new List(); + + var containsMergeModules = false; + + // Load standard tables, authored custom tables, and extension custom tables. + TableDefinitionCollection tableDefinitions; + { + var command = new LoadTableDefinitionsCommand(this.Messaging, section, this.BackendExtensions); + command.Execute(); + + tableDefinitions = command.TableDefinitions; + } + + // Calculate codepage + var codepage = this.CalculateCodepage(packageSymbol, moduleSymbol, patchSymbol); + + // Process properties and create the delayed variable cache if needed. + Dictionary variableCache = null; + string productLanguage = null; + { + var command = new ProcessPropertiesCommand(section, packageSymbol, this.ResolvedLcid ?? 0, this.DelayedFields.Any(), this.WindowsInstallerBackendHelper); + command.Execute(); + + variableCache = command.DelayedVariablesCache; + productLanguage = command.ProductLanguage; + } + + // Process the summary information table after properties are processed. + bool compressed; + bool longNames; + int installerVersion; + Platform platform; + string modularizationSuffix; + { + var branding = this.ServiceProvider.GetService(); + + var command = new BindSummaryInfoCommand(section, this.ResolvedSummaryInformationCodepage, productLanguage, this.WindowsInstallerBackendHelper, branding); + command.Execute(); + + compressed = command.Compressed; + longNames = command.LongNames; + installerVersion = command.InstallerVersion; + platform = command.Platform; + modularizationSuffix = command.ModularizationSuffix; + } + + // Sequence all the actions. + { + var command = new SequenceActionsCommand(this.Messaging, section); + command.Execute(); + } + + if (section.Type == SectionType.Product || section.Type == SectionType.Module) + { + var command = new AddRequiredStandardDirectories(section, platform); + command.Execute(); + } + + { + var command = new CreateSpecialPropertiesCommand(section); + command.Execute(); + } + +#if TODO_PATCHING + ////if (OutputType.Patch == this.Output.Type) + ////{ + //// foreach (SubStorage substorage in this.Output.SubStorages) + //// { + //// Output transform = substorage.Data; + + //// ResolveFieldsCommand command = new ResolveFieldsCommand(); + //// command.Tables = transform.Tables; + //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; + //// command.FileManagerCore = this.FileManagerCore; + //// command.FileManagers = this.FileManagers; + //// command.SupportDelayedResolution = false; + //// command.TempFilesLocation = this.TempFilesLocation; + //// command.WixVariableResolver = this.WixVariableResolver; + //// command.Execute(); + + //// this.MergeUnrealTables(transform.Tables); + //// } + ////} +#endif + + if (this.Messaging.EncounteredError) + { + return null; + } + + this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); + this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); + + // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). + { + var extractedFiles = this.WindowsInstallerBackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles); + + trackedFiles.AddRange(extractedFiles); + } + + // This must occur after all variables and source paths have been resolved. + List fileFacades; + if (SectionType.Patch == section.Type) + { + var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.SubStorages); + command.Execute(); + + fileFacades = command.FileFacades; + } + else + { + var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper); + command.Execute(); + + fileFacades = command.FileFacades; + } + + // Retrieve file information from merge modules. + if (SectionType.Product == section.Type) + { + var wixMergeSymbols = section.Symbols.OfType().ToList(); + + if (wixMergeSymbols.Any()) + { + containsMergeModules = true; + + var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacades, installerVersion, this.IntermediateFolder, this.SuppressLayout); + command.Execute(); + + fileFacades.AddRange(command.MergeModulesFileFacades); + } + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // Process SoftwareTags in MSI packages. + if (SectionType.Product == section.Type) + { + var softwareTags = section.Symbols.OfType().ToList(); + + if (softwareTags.Any()) + { + var command = new ProcessPackageSoftwareTagsCommand(section, softwareTags, this.IntermediateFolder); + command.Execute(); + } + } + + // Gather information about files that do not come from merge modules. + { + var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true); + command.Execute(); + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // Now that the variable cache is populated, resolve any delayed fields. + if (this.DelayedFields.Any()) + { + this.WindowsInstallerBackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache); + } + + // Update symbols that reference text files on disk. + { + var command = new UpdateFromTextFilesCommand(this.Messaging, section); + command.Execute(); + } + + // Add missing CreateFolder symbols to null-keypath components. + { + var command = new AddCreateFoldersCommand(section); + command.Execute(); + } + + // Process dependency references. + if (SectionType.Product == section.Type || SectionType.Module == section.Type) + { + var dependencyRefs = section.Symbols.OfType().ToList(); + + if (dependencyRefs.Any()) + { + var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs); + command.Execute(); + } + } + + // If there are any backend extensions, give them the opportunity to process + // the section now that the fields have all be resolved. + // + if (this.BackendExtensions.Any()) + { + using (new IntermediateFieldContext("wix.bind.finalize")) + { + foreach (var extension in this.BackendExtensions) + { + extension.SymbolsFinalized(section); + } + + var reresolvedFiles = section.Symbols + .OfType() + .Where(s => s.Fields.Any(f => f?.Context == "wix.bind.finalize")) + .ToList(); + + if (reresolvedFiles.Any()) + { + var updatedFacades = reresolvedFiles.Select(f => fileFacades.First(ff => ff.Id == f.Id?.Id)); + + var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, updatedFacades, variableCache, overwriteHash: false); + command.Execute(); + } + } + + if (this.Messaging.EncounteredError) + { + return null; + } + } + + // Set generated component guids and validate all guids. + { + var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); + command.Execute(); + } + + // Assign files to media and update file sequences. + Dictionary> filesByCabinetMedia; + IEnumerable uncompressedFiles; + { + var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, fileFacades); + order.Execute(); + + fileFacades = order.FileFacades; + + var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed); + assign.Execute(); + + filesByCabinetMedia = assign.FileFacadesByCabinetMedia; + uncompressedFiles = assign.UncompressedFileFacades; + + var update = new UpdateMediaSequencesCommand(section, fileFacades); + update.Execute(); + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better. + WindowsInstallerData data; + { + var command = new CreateWindowsInstallerDataFromIRCommand(this.Messaging, section, tableDefinitions, codepage, this.BackendExtensions, this.WindowsInstallerBackendHelper); + data = command.Execute(); + } + + IEnumerable suppressedTableNames = null; + if (data.Type == OutputType.Module) + { + // Modularize identifiers. + var modularize = new ModularizeCommand(this.WindowsInstallerBackendHelper, data, modularizationSuffix, section.Symbols.OfType()); + modularize.Execute(); + + // Ensure all sequence tables in place because, mergemod.dll requires them. + var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions); + suppressedTableNames = unsuppress.Execute(); + } + else if (data.Type == OutputType.Patch) + { + foreach (var storage in this.SubStorages) + { + data.SubStorages.Add(storage); + } + } + + // Stop processing if an error previously occurred. + if (this.Messaging.EncounteredError) + { + return null; + } + + // Ensure the intermediate folder is created since delta patches will be + // created there. + Directory.CreateDirectory(this.IntermediateFolder); + + if (SectionType.Patch == section.Type && this.DeltaBinaryPatch) + { + var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType().FirstOrDefault()); + command.Execute(); + } + + // create cabinet files and process uncompressed files + var layoutDirectory = Path.GetDirectoryName(this.OutputPath); + if (!this.SuppressLayout || OutputType.Module == data.Type) + { + this.Messaging.Write(VerboseMessages.CreatingCabinetFiles()); + + var mediaTemplate = section.Symbols.OfType().FirstOrDefault(); + + var command = new CreateCabinetsCommand(this.ServiceProvider, this.WindowsInstallerBackendHelper, mediaTemplate); + command.CabbingThreadCount = this.CabbingThreadCount; + command.CabCachePath = this.CabCachePath; + command.DefaultCompressionLevel = this.DefaultCompressionLevel; + command.Data = data; + command.Messaging = this.Messaging; + command.BackendExtensions = this.BackendExtensions; + command.LayoutDirectory = layoutDirectory; + command.Compressed = compressed; + command.ModularizationSuffix = modularizationSuffix; + command.FileFacadesByCabinet = filesByCabinetMedia; + command.ResolveMedia = this.ResolveMedia; + command.TableDefinitions = tableDefinitions; + command.IntermediateFolder = this.IntermediateFolder; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // We can create instance transforms since Component Guids and Outputs are created. + if (data.Type == OutputType.Product) + { + var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper); + command.Execute(); + } + else if (data.Type == OutputType.Patch) + { + // Copy output data back into the transforms. + var command = new UpdateTransformsWithFileFacades(this.Messaging, data, this.SubStorages, tableDefinitions, fileFacades); + command.Execute(); + } + + // Generate database file. + { + this.Messaging.Write(VerboseMessages.GeneratingDatabase()); + + var trackMsi = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final); + trackedFiles.Add(trackMsi); + + var command = new GenerateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, data, trackMsi.Path, tableDefinitions, this.IntermediateFolder, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false); + command.Execute(); + + trackedFiles.AddRange(command.GeneratedTemporaryFiles); + } + + // Stop processing if an error previously occurred. + if (this.Messaging.EncounteredError) + { + return null; + } + + // Merge modules. + if (containsMergeModules) + { + this.Messaging.Write(VerboseMessages.MergingModules()); + + var command = new MergeModulesCommand(this.Messaging, fileFacades, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return null; + } + + // Validate the output if there are CUBe files and we're not explicitly suppressing validation. + if (this.CubeFiles != null && !this.SuppressValidation) + { + var command = new ValidateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces); + command.Execute(); + + trackedFiles.AddRange(command.TrackedFiles); + } + + if (this.Messaging.EncounteredError) + { + return null; + } + + // Process uncompressed files. + if (!this.SuppressLayout && uncompressedFiles.Any()) + { + var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver); + command.Compressed = compressed; + command.FileFacades = uncompressedFiles; + command.LayoutDirectory = layoutDirectory; + command.LongNamesInImage = longNames; + command.ResolveMedia = this.ResolveMedia; + command.DatabasePath = this.OutputPath; + command.Execute(); + + fileTransfers.AddRange(command.FileTransfers); + trackedFiles.AddRange(command.TrackedFiles); + } + + // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables). + trackedFiles.AddRange(fileFacades.Select(f => this.WindowsInstallerBackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber))); + + var result = this.ServiceProvider.GetService(); + result.FileTransfers = fileTransfers; + result.TrackedFiles = trackedFiles; + result.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, data); + + return result; + } + + private int CalculateCodepage(WixPackageSymbol packageSymbol, WixModuleSymbol moduleSymbol, WixPatchSymbol patchSymbol) + { + var codepage = packageSymbol?.Codepage ?? moduleSymbol?.Codepage ?? patchSymbol?.Codepage; + + if (String.IsNullOrEmpty(codepage)) + { + codepage = this.ResolvedCodepage?.ToString() ?? "65001"; + + if (packageSymbol != null) + { + packageSymbol.Codepage = codepage; + } + else if (moduleSymbol != null) + { + moduleSymbol.Codepage = codepage; + } + else if (patchSymbol != null) + { + patchSymbol.Codepage = codepage; + } + } + + return this.WindowsInstallerBackendHelper.GetValidCodePage(codepage); + } + + private T GetSingleSymbol(IntermediateSection section) where T : IntermediateSymbol + { + var symbols = section.Symbols.OfType().ToList(); + + if (1 != symbols.Count) + { + throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T))); + } + + return symbols[0]; + } + + private WixOutput CreateWixout(List trackedFiles, Intermediate intermediate, WindowsInstallerData data) + { + WixOutput wixout; + + if (String.IsNullOrEmpty(this.OutputPdbPath)) + { + wixout = WixOutput.Create(); + } + else + { + var trackPdb = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final); + trackedFiles.Add(trackPdb); + + wixout = WixOutput.Create(trackPdb.Path); + } + + intermediate.Save(wixout); + + data.Save(wixout); + + wixout.Reopen(); + + return wixout; + } + + private string ResolveMedia(MediaSymbol media, string mediaLayoutDirectory, string layoutDirectory) + { + string layout = null; + + foreach (var extension in this.BackendExtensions) + { + layout = extension.ResolveMedia(media, mediaLayoutDirectory, layoutDirectory); + if (!String.IsNullOrEmpty(layout)) + { + break; + } + } + + // If no binder file manager resolved the layout, do the default behavior. + if (String.IsNullOrEmpty(layout)) + { + if (String.IsNullOrEmpty(mediaLayoutDirectory)) + { + layout = layoutDirectory; + } + else if (Path.IsPathRooted(mediaLayoutDirectory)) + { + layout = mediaLayoutDirectory; + } + else + { + layout = Path.Combine(layoutDirectory, mediaLayoutDirectory); + } + } + + return layout; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs new file mode 100644 index 00000000..41da2a13 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs @@ -0,0 +1,211 @@ +// 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.Bind +{ + using System; + using System.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + /// + /// Binds the summary information table of a database. + /// + internal class BindSummaryInfoCommand + { + public BindSummaryInfoCommand(IntermediateSection section, int? summaryInformationCodepage, string productLanguage, IBackendHelper backendHelper, IWixBranding branding) + { + this.Section = section; + this.SummaryInformationCodepage = summaryInformationCodepage; + this.ProductLanguage = productLanguage; + this.BackendHelper = backendHelper; + this.Branding = branding; + } + + private IntermediateSection Section { get; } + + private int? SummaryInformationCodepage { get; } + + private string ProductLanguage { get; } + + private IBackendHelper BackendHelper { get; } + + private IWixBranding Branding { get; } + + /// + /// Returns a flag indicating if files are compressed by default. + /// + public bool Compressed { get; private set; } + + /// + /// Returns a flag indicating if uncompressed files use long filenames. + /// + public bool LongNames { get; private set; } + + public int InstallerVersion { get; private set; } + + public Platform Platform { get; private set; } + + /// + /// Modularization guid, or null if the output is not a module. + /// + public string ModularizationSuffix { get; private set; } + + public void Execute() + { + this.Compressed = false; + this.LongNames = false; + this.InstallerVersion = 0; + this.ModularizationSuffix = null; + + SummaryInformationSymbol summaryInformationCodepageSymbol = null; + SummaryInformationSymbol platformAndLanguageSymbol = null; + var foundCreateDateTime = false; + var foundLastSaveDataTime = false; + var foundCreatingApplication = false; + var foundPackageCode = false; + var now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); + + foreach (var summaryInformationSymbol in this.Section.Symbols.OfType()) + { + switch (summaryInformationSymbol.PropertyId) + { + case SummaryInformationType.Codepage: // PID_CODEPAGE + summaryInformationCodepageSymbol = summaryInformationSymbol; + break; + + case SummaryInformationType.PlatformAndLanguage: + platformAndLanguageSymbol = summaryInformationSymbol; + break; + + case SummaryInformationType.PackageCode: // PID_REVNUMBER + foundPackageCode = true; + var packageCode = summaryInformationSymbol.Value; + + if (SectionType.Module == this.Section.Type) + { + this.ModularizationSuffix = "." + packageCode.Substring(1, 36).Replace('-', '_'); + } + break; + case SummaryInformationType.Created: + foundCreateDateTime = true; + break; + case SummaryInformationType.LastSaved: + foundLastSaveDataTime = true; + break; + case SummaryInformationType.WindowsInstallerVersion: + this.InstallerVersion = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber(); + break; + case SummaryInformationType.WordCount: + if (SectionType.Patch == this.Section.Type) + { + this.LongNames = true; + this.Compressed = true; + } + else + { + var attributes = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber(); + this.LongNames = (0 == (attributes & 1)); + this.Compressed = (2 == (attributes & 2)); + } + break; + case SummaryInformationType.CreatingApplication: // PID_APPNAME + foundCreatingApplication = true; + break; + } + } + + // Ensure the codepage is set properly. + if (summaryInformationCodepageSymbol == null) + { + summaryInformationCodepageSymbol = this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.Codepage + }); + } + + var codepage = summaryInformationCodepageSymbol.Value; + + if (String.IsNullOrEmpty(codepage)) + { + codepage = this.SummaryInformationCodepage?.ToString(CultureInfo.InvariantCulture) ?? "1252"; + } + + summaryInformationCodepageSymbol.Value = this.BackendHelper.GetValidCodePage(codepage, onlyAnsi: true).ToString(CultureInfo.InvariantCulture); + + // Ensure the language is set properly and figure out what platform we are targeting. + if (platformAndLanguageSymbol != null) + { + this.Platform = EnsureLanguageAndGetPlatformFromSummaryInformation(platformAndLanguageSymbol, this.ProductLanguage); + } + + // Set the revision number (package/patch code) if it should be automatically generated. + if (!foundPackageCode) + { + this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.PackageCode, + Value = this.BackendHelper.CreateGuid(), + }); + } + + // add a summary information row for the create time/date property if its not already set + if (!foundCreateDateTime) + { + this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.Created, + Value = now, + }); + } + + // add a summary information row for the last save time/date property if its not already set + if (!foundLastSaveDataTime) + { + this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.LastSaved, + Value = now, + }); + } + + // add a summary information row for the creating application property if its not already set + if (!foundCreatingApplication) + { + this.Section.AddSymbol(new SummaryInformationSymbol(null) + { + PropertyId = SummaryInformationType.CreatingApplication, + Value = this.Branding.GetCreatingApplication(), + }); + } + } + + private static Platform EnsureLanguageAndGetPlatformFromSummaryInformation(SummaryInformationSymbol symbol, string language) + { + var value = symbol.Value; + var separatorIndex = value.IndexOf(';'); + var platformValue = separatorIndex > 0 ? value.Substring(0, separatorIndex) : value; + + // If the language was provided and there was language value after the separator + // (or the separator was absent) then use the provided language. + if (!String.IsNullOrEmpty(language) && (separatorIndex < 0 || separatorIndex + 1 == value.Length)) + { + symbol.Value = platformValue + ';' + language; + } + + switch (platformValue) + { + case "x64": + return Platform.X64; + + case "Arm64": + return Platform.ARM64; + + case "Intel": + default: + return Platform.X86; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs new file mode 100644 index 00000000..3379ec5d --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs @@ -0,0 +1,445 @@ +// 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.Bind +{ + using System; + using System.Globalization; + using System.IO; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class BindTransformCommand + { + public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.FileSystemManager = fileSystemManager; + this.IntermediateFolder = intermediateFolder; + this.Transform = transform; + this.OutputPath = outputPath; + this.TableDefinitions = tableDefinitions; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private FileSystemManager FileSystemManager { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private string IntermediateFolder { get; } + + private WindowsInstallerData Transform { get; } + + private string OutputPath { get; } + + public void Execute() + { + var transformFlags = 0; + + var targetOutput = new WindowsInstallerData(null); + var updatedOutput = new WindowsInstallerData(null); + + // TODO: handle added columns + + // to generate a localized transform, both the target and updated + // databases need to have the same code page. the only reason to + // set different code pages is to support localized primary key + // columns, but that would only support deleting rows. if this + // becomes necessary, define a PreviousCodepage property on the + // Output class and persist this throughout transform generation. + targetOutput.Codepage = this.Transform.Codepage; + updatedOutput.Codepage = this.Transform.Codepage; + + // remove certain Property rows which will be populated from summary information values + string targetUpgradeCode = null; + string updatedUpgradeCode = null; + + if (this.Transform.TryGetTable("Property", out var propertyTable)) + { + for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) + { + Row row = propertyTable.Rows[i]; + + if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) + { + propertyTable.Rows.RemoveAt(i); + + if ("UpgradeCode" == (string)row[0]) + { + updatedUpgradeCode = (string)row[1]; + } + } + } + } + + var targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + var updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + var targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); + var updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); + + // process special summary information values + foreach (var row in this.Transform.Tables["_SummaryInformation"].Rows) + { + var summaryId = row.FieldAsInteger(0); + var summaryData = row.FieldAsString(1); + + if ((int)SummaryInformation.Transform.CodePage == summaryId) + { + // convert from a web name if provided + var codePage = summaryData; + if (null == codePage) + { + codePage = "0"; + } + else + { + codePage = this.BackendHelper.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); + } + + var previousCodePage = row.Fields[1].PreviousData; + if (null == previousCodePage) + { + previousCodePage = "0"; + } + else + { + previousCodePage = this.BackendHelper.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); + } + + var targetCodePageRow = targetSummaryInfo.CreateRow(null); + targetCodePageRow[0] = 1; // PID_CODEPAGE + targetCodePageRow[1] = previousCodePage; + + var updatedCodePageRow = updatedSummaryInfo.CreateRow(null); + updatedCodePageRow[0] = 1; // PID_CODEPAGE + updatedCodePageRow[1] = codePage; + } + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId || + (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == summaryId) + { + // the target language + var propertyData = summaryData.Split(';'); + var lang = 2 == propertyData.Length ? propertyData[1] : "0"; + + var tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetSummaryInfo : updatedSummaryInfo; + var tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetPropertyTable : updatedPropertyTable; + + var productLanguageRow = tempPropertyTable.CreateRow(null); + productLanguageRow[0] = "ProductLanguage"; + productLanguageRow[1] = lang; + + // set the platform;language on the MSI to be generated + var templateRow = tempSummaryInfo.CreateRow(null); + templateRow[0] = 7; // PID_TEMPLATE + templateRow[1] = summaryData; + } + else if ((int)SummaryInformation.Transform.ProductCodes == summaryId) + { + var propertyData = summaryData.Split(';'); + + var targetProductCodeRow = targetPropertyTable.CreateRow(null); + targetProductCodeRow[0] = "ProductCode"; + targetProductCodeRow[1] = propertyData[0].Substring(0, 38); + + var targetProductVersionRow = targetPropertyTable.CreateRow(null); + targetProductVersionRow[0] = "ProductVersion"; + targetProductVersionRow[1] = propertyData[0].Substring(38); + + var updatedProductCodeRow = updatedPropertyTable.CreateRow(null); + updatedProductCodeRow[0] = "ProductCode"; + updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); + + var updatedProductVersionRow = updatedPropertyTable.CreateRow(null); + updatedProductVersionRow[0] = "ProductVersion"; + updatedProductVersionRow[1] = propertyData[1].Substring(38); + + // UpgradeCode is optional and may not exists in the target + // or upgraded databases, so do not include a null-valued + // UpgradeCode property. + + targetUpgradeCode = propertyData[2]; + if (!String.IsNullOrEmpty(targetUpgradeCode)) + { + var targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); + targetUpgradeCodeRow[0] = "UpgradeCode"; + targetUpgradeCodeRow[1] = targetUpgradeCode; + + // If the target UpgradeCode is specified, an updated + // UpgradeCode is required. + if (String.IsNullOrEmpty(updatedUpgradeCode)) + { + updatedUpgradeCode = targetUpgradeCode; + } + } + + if (!String.IsNullOrEmpty(updatedUpgradeCode)) + { + var updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); + updatedUpgradeCodeRow[0] = "UpgradeCode"; + updatedUpgradeCodeRow[1] = updatedUpgradeCode; + } + } + else if ((int)SummaryInformation.Transform.ValidationFlags == summaryId) + { + transformFlags = Convert.ToInt32(summaryData, CultureInfo.InvariantCulture); + } + else if ((int)SummaryInformation.Transform.Reserved11 == summaryId) + { + // PID_LASTPRINTED should be null for transforms + row.Operation = RowOperation.None; + } + else + { + // add everything else as is + var targetRow = targetSummaryInfo.CreateRow(null); + targetRow[0] = row[0]; + targetRow[1] = row[1]; + + var updatedRow = updatedSummaryInfo.CreateRow(null); + updatedRow[0] = row[0]; + updatedRow[1] = row[1]; + } + } + + // Validate that both databases have an UpgradeCode if the + // authoring transform will validate the UpgradeCode; otherwise, + // MsiCreateTransformSummaryinfo() will fail with 1620. + if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && + (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) + { + this.Messaging.Write(ErrorMessages.BothUpgradeCodesRequired()); + } + + string emptyFile = null; + + foreach (var table in this.Transform.Tables) + { + // Ignore unreal tables when building transforms except the _Stream table. + // These tables are ignored when generating the database so there is no reason + // to process them here. + if (table.Definition.Unreal && "_Streams" != table.Name) + { + continue; + } + + // process table operations + switch (table.Operation) + { + case TableOperation.Add: + updatedOutput.EnsureTable(table.Definition); + break; + case TableOperation.Drop: + targetOutput.EnsureTable(table.Definition); + continue; + default: + targetOutput.EnsureTable(table.Definition); + updatedOutput.EnsureTable(table.Definition); + break; + } + + // process row operations + foreach (var row in table.Rows) + { + switch (row.Operation) + { + case RowOperation.Add: + var updatedTable = updatedOutput.EnsureTable(table.Definition); + updatedTable.Rows.Add(row); + continue; + + case RowOperation.Delete: + var targetTable = targetOutput.EnsureTable(table.Definition); + targetTable.Rows.Add(row); + + // fill-in non-primary key values + foreach (var field in row.Fields) + { + if (!field.Column.PrimaryKey) + { + if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) + { + field.Data = field.Column.MinValue; + } + else if (ColumnType.Object == field.Column.Type) + { + if (null == emptyFile) + { + emptyFile = Path.Combine(this.IntermediateFolder, "empty"); + } + + field.Data = emptyFile; + } + else + { + field.Data = "0"; + } + } + } + continue; + } + + // Assure that the file table's sequence is populated + if ("File" == table.Name) + { + foreach (var fileRow in table.Rows) + { + if (null == fileRow[7]) + { + if (RowOperation.Add == fileRow.Operation) + { + this.Messaging.Write(ErrorMessages.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); + break; + } + + // Set to 1 to prevent invalid IDT file from being generated + fileRow[7] = 1; + } + } + } + + // process modified and unmodified rows + var modifiedRow = false; + var targetRow = table.Definition.CreateRow(null); + var updatedRow = row; + for (var i = 0; i < row.Fields.Length; i++) + { + var updatedField = row.Fields[i]; + + if (updatedField.Modified) + { + // set a different value in the target row to ensure this value will be modified during transform generation + if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) + { + var data = updatedField.AsNullableInteger(); + targetRow[i] = (data == 1) ? 2 : 1; + } + else if (ColumnType.Object == updatedField.Column.Type) + { + if (null == emptyFile) + { + emptyFile = Path.Combine(this.IntermediateFolder, "empty"); + } + + targetRow[i] = emptyFile; + } + else + { + var data = updatedField.AsString(); + targetRow[i] = (data == "0") ? "1" : "0"; + } + + modifiedRow = true; + } + else if (ColumnType.Object == updatedField.Column.Type) + { + var objectField = (ObjectField)updatedField; + + // create an empty file for comparing against + if (null == objectField.PreviousData) + { + if (null == emptyFile) + { + emptyFile = Path.Combine(this.IntermediateFolder, "empty"); + } + + targetRow[i] = emptyFile; + modifiedRow = true; + } + else if (!this.FileSystemManager.CompareFiles(objectField.PreviousData, (string)objectField.Data)) + { + targetRow[i] = objectField.PreviousData; + modifiedRow = true; + } + } + else // unmodified + { + if (null != updatedField.Data) + { + targetRow[i] = updatedField.Data; + } + } + } + + // modified rows and certain special rows go in the target and updated msi databases + if (modifiedRow || + ("Property" == table.Name && + ("ProductCode" == (string)row[0] || + "ProductLanguage" == (string)row[0] || + "ProductVersion" == (string)row[0] || + "UpgradeCode" == (string)row[0]))) + { + var targetTable = targetOutput.EnsureTable(table.Definition); + targetTable.Rows.Add(targetRow); + + var updatedTable = updatedOutput.EnsureTable(table.Definition); + updatedTable.Rows.Add(updatedRow); + } + } + } + + //foreach (BinderExtension extension in this.Extensions) + //{ + // extension.PostBind(this.Context); + //} + + // Any errors encountered up to this point can cause errors during generation. + if (this.Messaging.EncounteredError) + { + return; + } + + var transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); + var targetDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_target.msi")); + var updatedDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_updated.msi")); + + try + { + if (!String.IsNullOrEmpty(emptyFile)) + { + using (var fileStream = File.Create(emptyFile)) + { + } + } + + this.GenerateDatabase(targetOutput, targetDatabaseFile, keepAddedColumns: false); + this.GenerateDatabase(updatedOutput, updatedDatabaseFile, keepAddedColumns: true); + + // make sure the directory exists + Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); + + // create the transform file + using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) + using (var updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) + { + if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) + { + updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); + } + else + { + this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); + } + } + } + finally + { + if (!String.IsNullOrEmpty(emptyFile)) + { + File.Delete(emptyFile); + } + } + } + + private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns) + { + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true); + command.Execute(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs new file mode 100644 index 00000000..13b079ad --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs @@ -0,0 +1,171 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + /// + /// Builds cabinets using multiple threads. This implements a thread pool that generates cabinets with multiple + /// threads. Unlike System.Threading.ThreadPool, it waits until all threads are finished. + /// + internal sealed class CabinetBuilder + { + private readonly Queue cabinetWorkItems; + private int threadCount; + + // Address of Binder's callback function for Cabinet Splitting + private readonly IntPtr newCabNamesCallBackAddress; + + /// + /// Instantiate a new CabinetBuilder. + /// + /// + /// number of threads to use + /// Address of Binder's callback function for Cabinet Splitting + public CabinetBuilder(IMessaging messaging, int threadCount, IntPtr newCabNamesCallBackAddress) + { + if (0 >= threadCount) + { + throw new ArgumentOutOfRangeException(nameof(threadCount)); + } + + this.cabinetWorkItems = new Queue(); + this.Messaging = messaging; + this.threadCount = threadCount; + + // Set Address of Binder's callback function for Cabinet Splitting + this.newCabNamesCallBackAddress = newCabNamesCallBackAddress; + } + + private IMessaging Messaging { get; } + + public int MaximumCabinetSizeForLargeFileSplitting { get; set; } + + public int MaximumUncompressedMediaSize { get; set; } + + /// + /// Enqueues a CabinetWorkItem to the queue. + /// + /// cabinet work item + public void Enqueue(CabinetWorkItem cabinetWorkItem) => this.cabinetWorkItems.Enqueue(cabinetWorkItem); + + /// + /// Create the queued cabinets. + /// + /// error message number (zero if no error) + public void CreateQueuedCabinets() + { + // don't create more threads than the number of cabinets to build + var numberOfThreads = Math.Min(this.threadCount, this.cabinetWorkItems.Count); + + if (0 < numberOfThreads) + { + var threads = new Thread[numberOfThreads]; + + for (var i = 0; i < threads.Length; i++) + { + threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems)); + threads[i].Start(); + } + + // wait for all threads to finish + foreach (var thread in threads) + { + thread.Join(); + } + } + } + + /// + /// This function gets called by multiple threads to do actual work. + /// It takes one work item at a time and calls this.CreateCabinet(). + /// It does not return until cabinetWorkItems queue is empty + /// + private void ProcessWorkItems() + { + try + { + while (true) + { + CabinetWorkItem cabinetWorkItem; + + lock (this.cabinetWorkItems) + { + // check if there are any more cabinets to create + if (0 == this.cabinetWorkItems.Count) + { + break; + } + + cabinetWorkItem = this.cabinetWorkItems.Dequeue(); + } + + // create a cabinet + this.CreateCabinet(cabinetWorkItem); + } + } + catch (WixException we) + { + this.Messaging.Write(we.Error); + } + catch (Exception e) + { + this.Messaging.Write(ErrorMessages.UnexpectedException(e)); + } + } + + /// + /// Creates a cabinet using the wixcab.dll interop layer. + /// + /// CabinetWorkItem containing information about the cabinet to create. + private void CreateCabinet(CabinetWorkItem cabinetWorkItem) + { + this.Messaging.Write(VerboseMessages.CreateCabinet(cabinetWorkItem.CabinetFile)); + + var maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting + ulong maxPreCompressedSizeInBytes = 0; + + if (this.MaximumCabinetSizeForLargeFileSplitting != 0) + { + // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize + // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file + if (1 == cabinetWorkItem.FileFacades.Count()) + { + // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs + // Get the Value for Max Uncompressed Media Size + maxPreCompressedSizeInBytes = (ulong)this.MaximumUncompressedMediaSize * 1024 * 1024; + + var facade = cabinetWorkItem.FileFacades.First(); + + // If the file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting + if ((ulong)facade.FileSize >= maxPreCompressedSizeInBytes) + { + maxCabinetSize = this.MaximumCabinetSizeForLargeFileSplitting; + } + } + } + + // create the cabinet file + var cabinetPath = Path.GetFullPath(cabinetWorkItem.CabinetFile); + + var files = cabinetWorkItem.FileFacades + .OrderBy(f => f.Sequence) + .Select(facade => facade.Hash == null ? + new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix) : + new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4)) + .ToList(); + + var cab = new Cabinet(cabinetPath); + cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold); + + // TODO: Handle newCabNamesCallBackAddress from compression. + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs new file mode 100644 index 00000000..875b46c2 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs @@ -0,0 +1,132 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CabinetResolver + { + public CabinetResolver(IServiceProvider serviceProvider, string cabCachePath, IEnumerable backendExtensions) + { + this.ServiceProvider = serviceProvider; + + this.CabCachePath = cabCachePath; + + this.BackendExtensions = backendExtensions; + } + + private IServiceProvider ServiceProvider { get; } + + private string CabCachePath { get; } + + private IEnumerable BackendExtensions { get; } + + public IResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable fileFacades) + { + var filesWithPath = fileFacades.Select(this.CreateBindFileWithPath).ToList(); + + IResolvedCabinet resolved = null; + + foreach (var extension in this.BackendExtensions) + { + resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); + + if (null != resolved) + { + return resolved; + } + } + + // By default cabinet should be built and moved to the suggested location. + resolved = this.ServiceProvider.GetService(); + resolved.BuildOption = CabinetBuildOption.BuildAndMove; + resolved.Path = cabinetPath; + + // If a cabinet cache path was provided, change the location for the cabinet + // to be built to and check if there is a cabinet that can be reused. + if (!String.IsNullOrEmpty(this.CabCachePath)) + { + var cabinetName = Path.GetFileName(cabinetPath); + resolved.Path = Path.Combine(this.CabCachePath, cabinetName); + + if (CheckFileExists(resolved.Path)) + { + // Assume that none of the following are true: + // 1. any files are added or removed + // 2. order of files changed or names changed + // 3. modified time changed + var cabinetValid = true; + + var cabinet = new Cabinet(resolved.Path); + var fileList = cabinet.Enumerate(); + + if (filesWithPath.Count() != fileList.Count) + { + cabinetValid = false; + } + else + { + var i = 0; + foreach (var file in filesWithPath) + { + // First check that the file identifiers match because that is quick and easy. + var cabFileInfo = fileList[i]; + cabinetValid = (cabFileInfo.FileId == file.Id); + if (cabinetValid) + { + // Still valid so ensure the file sizes are the same. + var fileInfo = new FileInfo(file.Path); + cabinetValid = (cabFileInfo.Size == fileInfo.Length); + if (cabinetValid) + { + // Still valid so ensure the source time stamp hasn't changed. + cabinetValid = cabFileInfo.SameAsDateTime(fileInfo.LastWriteTime); + } + } + + if (!cabinetValid) + { + break; + } + + i++; + } + } + + resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy; + } + } + + return resolved; + } + + private IBindFileWithPath CreateBindFileWithPath(IFileFacade facade) + { + var result = this.ServiceProvider.GetService(); + result.Id = facade.Id; + result.Path = facade.SourcePath; + + return result; + } + + private static bool CheckFileExists(string path) + { + try + { + return File.Exists(path); + } + catch (ArgumentException) + { + throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs new file mode 100644 index 00000000..1990ea78 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs @@ -0,0 +1,68 @@ +// 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.Bind +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + /// + /// A cabinet builder work item. + /// + internal sealed class CabinetWorkItem + { + /// + /// Instantiate a new CabinetWorkItem. + /// + /// The collection of files in this cabinet. + /// The cabinet file. + /// Maximum threshold for each cabinet. + /// The compression level of the cabinet. + /// Modularization suffix used when building a Merge Module. + /// + public CabinetWorkItem(IEnumerable fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix /*, BinderFileManager binderFileManager*/) + { + this.CabinetFile = cabinetFile; + this.CompressionLevel = compressionLevel; + this.ModularizationSuffix = modularizationSuffix; + this.FileFacades = fileFacades; + //this.BinderFileManager = binderFileManager; + this.MaxThreshold = maxThreshold; + } + + /// + /// Gets the cabinet file. + /// + /// The cabinet file. + public string CabinetFile { get; } + + /// + /// Gets the compression level of the cabinet. + /// + /// The compression level of the cabinet. + public CompressionLevel CompressionLevel { get; } + + /// + /// Gets the modularization suffix used when building a Merge Module. + /// + public string ModularizationSuffix { get; } + + /// + /// Gets the collection of files in this cabinet. + /// + /// The collection of files in this cabinet. + public IEnumerable FileFacades { get; } + + // + // Gets the binder file manager. + // + // The binder file manager. + //public BinderFileManager BinderFileManager { get; private set; } + + /// + /// Gets the max threshold. + /// + /// The maximum threshold for a folder in a cabinet. + public int MaxThreshold { get; } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs new file mode 100644 index 00000000..83a4949e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs @@ -0,0 +1,455 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Creates cabinet files. + /// + internal class CreateCabinetsCommand + { + public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB + public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) + + private readonly List fileTransfers; + + private readonly List trackedFiles; + + private readonly FileSplitCabNamesCallback newCabNamesCallBack; + + private Dictionary lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence + + public CreateCabinetsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, WixMediaTemplateSymbol mediaTemplate) + { + this.fileTransfers = new List(); + + this.trackedFiles = new List(); + + this.newCabNamesCallBack = this.NewCabNamesCallBack; + + this.ServiceProvider = serviceProvider; + + this.BackendHelper = backendHelper; + + this.MediaTemplate = mediaTemplate; + } + + private IServiceProvider ServiceProvider { get; } + + private IBackendHelper BackendHelper { get; } + + private WixMediaTemplateSymbol MediaTemplate { get; } + + /// + /// Sets the number of threads to use for cabinet creation. + /// + public int CabbingThreadCount { private get; set; } + + public string CabCachePath { private get; set; } + + public IMessaging Messaging { private get; set; } + + public string IntermediateFolder { private get; set; } + + /// + /// Sets the default compression level to use for cabinets + /// that don't have their compression level explicitly set. + /// + public CompressionLevel? DefaultCompressionLevel { private get; set; } + + public IEnumerable BackendExtensions { private get; set; } + + public WindowsInstallerData Data { private get; set; } + + public string LayoutDirectory { private get; set; } + + public bool Compressed { private get; set; } + + public string ModularizationSuffix { private get; set; } + + public Dictionary> FileFacadesByCabinet { private get; set; } + + public Func ResolveMedia { private get; set; } + + public TableDefinitionCollection TableDefinitions { private get; set; } + + public IEnumerable FileTransfers => this.fileTransfers; + + public IEnumerable TrackedFiles => this.trackedFiles; + + public void Execute() + { + this.lastCabinetAddedToMediaTable = new Dictionary(); + + // If the cabbing thread count wasn't provided, default the number of cabbing threads to the number of processors. + if (this.CabbingThreadCount <= 0) + { + this.CabbingThreadCount = this.CalculateCabbingThreadCount(); + + this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString())); + } + + // Send Binder object to Facilitate NewCabNamesCallBack Callback + var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); + + // Supply Compile MediaTemplate Attributes to Cabinet Builder + this.GetMediaTemplateAttributes(out var maximumCabinetSizeForLargeFileSplitting, out var maximumUncompressedMediaSize); + cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting; + cabinetBuilder.MaximumUncompressedMediaSize = maximumUncompressedMediaSize; + + foreach (var entry in this.FileFacadesByCabinet) + { + var mediaSymbol = entry.Key; + var files = entry.Value; + var compressionLevel = mediaSymbol.CompressionLevel ?? this.DefaultCompressionLevel ?? CompressionLevel.Medium; + var cabinetDir = this.ResolveMedia(mediaSymbol, mediaSymbol.Layout, this.LayoutDirectory); + + var cabinetWorkItem = this.CreateCabinetWorkItem(this.Data, cabinetDir, mediaSymbol, compressionLevel, files); + if (null != cabinetWorkItem) + { + cabinetBuilder.Enqueue(cabinetWorkItem); + } + } + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return; + } + + // create queued cabinets with multiple threads + cabinetBuilder.CreateQueuedCabinets(); + if (this.Messaging.EncounteredError) + { + return; + } + } + + private int CalculateCabbingThreadCount() + { + var cabbingThreadCount = Environment.ProcessorCount; + + if (cabbingThreadCount <= 0) + { + cabbingThreadCount = 1; // reset to 1 when the environment variable is invalid. + + this.Messaging.Write(WarningMessages.InvalidEnvironmentVariable("NUMBER_OF_PROCESSORS", Environment.ProcessorCount.ToString(), cabbingThreadCount.ToString())); + } + + return cabbingThreadCount; + } + + /// + /// Creates a work item to create a cabinet. + /// + /// Windows Installer data for the current database. + /// Directory to create cabinet in. + /// Media symbol containing information about the cabinet. + /// Desired compression level. + /// Collection of files in this cabinet. + /// created CabinetWorkItem object + private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable fileFacades) + { + CabinetWorkItem cabinetWorkItem = null; + var tempCabinetFileX = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet); + + // check for an empty cabinet + if (!fileFacades.Any()) + { + // Remove the leading '#' from the embedded cabinet name to make the warning easier to understand + var cabinetName = mediaSymbol.Cabinet.TrimStart('#'); + + // If building a patch, remind them to run -p for torch. + if (OutputType.Patch == data.Type) + { + this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, true)); + } + else + { + this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName)); + } + } + + var cabinetResolver = new CabinetResolver(this.ServiceProvider, this.CabCachePath, this.BackendExtensions); + + var resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades); + + // create a cabinet work item if it's not being skipped + if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption) + { + // Default to the threshold for best smartcabbing (makes smallest cabinet). + cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold: 0, compressionLevel, this.ModularizationSuffix /*, this.FileManager*/); + } + else // reuse the cabinet from the cabinet cache. + { + this.Messaging.Write(VerboseMessages.ReusingCabCache(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet, resolvedCabinet.Path)); + + try + { + // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The + // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that + // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from + // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output) + // causing the project to look like it perpetually needs a rebuild until all of the reused + // cabinets get newer timestamps. + File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now); + } + catch (Exception e) + { + this.Messaging.Write(WarningMessages.CannotUpdateCabCache(mediaSymbol.SourceLineNumbers, resolvedCabinet.Path, e.Message)); + } + } + + var trackResolvedCabinet = this.BackendHelper.TrackFile(resolvedCabinet.Path, TrackedFileType.Intermediate, mediaSymbol.SourceLineNumbers); + this.trackedFiles.Add(trackResolvedCabinet); + + if (mediaSymbol.Cabinet.StartsWith("#", StringComparison.Ordinal)) + { + var streamsTable = data.EnsureTable(this.TableDefinitions["_Streams"]); + + var streamRow = streamsTable.CreateRow(mediaSymbol.SourceLineNumbers); + streamRow[0] = mediaSymbol.Cabinet.Substring(1); + streamRow[1] = resolvedCabinet.Path; + } + else + { + var trackDestination = this.BackendHelper.TrackFile(Path.Combine(cabinetDir, mediaSymbol.Cabinet), TrackedFileType.Final, mediaSymbol.SourceLineNumbers); + this.trackedFiles.Add(trackDestination); + + var transfer = this.BackendHelper.CreateFileTransfer(resolvedCabinet.Path, trackDestination.Path, resolvedCabinet.BuildOption == CabinetBuildOption.BuildAndMove, mediaSymbol.SourceLineNumbers); + this.fileTransfers.Add(transfer); + } + + return cabinetWorkItem; + } + + //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable fileFacades) + //{ + // ResolvedCabinet resolved = null; + + // List filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList(); + + // foreach (var extension in this.BackendExtensions) + // { + // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath); + // if (null != resolved) + // { + // break; + // } + // } + + // return resolved; + //} + + /// + /// Delegate for Cabinet Split Callback + /// + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken); + + /// + /// Call back to Add File Transfer for new Cab and add new Cab to Media table + /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe + /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored + /// + /// The name of splitting cabinet without extention e.g. "cab1". + /// The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab" + /// The file token of the first file present in the splitting cabinet + internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabinetName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken) + { + throw new NotImplementedException(); +#if TODO_CAB_SPANNING + // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads + var mutex = new Mutex(false, "WixCabinetSplitBinderCallback"); + try + { + if (!mutex.WaitOne(0, false)) // Check if you can get the lock + { + // Cound not get the Lock + this.Messaging.Write(VerboseMessages.CabinetsSplitInParallel()); + mutex.WaitOne(); // Wait on other thread + } + + var firstCabinetName = firstCabName + ".cab"; + var transferAdded = false; // Used for Error Handling + + // Create File Transfer for new Cabinet using transfer of Base Cabinet + foreach (var transfer in this.FileTransfers) + { + if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase)) + { + var newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName); + var newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName); + + var trackSource = this.BackendHelper.TrackFile(newCabSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers); + this.trackedFiles.Add(trackSource); + + var trackTarget = this.BackendHelper.TrackFile(newCabTargetPath, TrackedFileType.Final, transfer.SourceLineNumbers); + this.trackedFiles.Add(trackTarget); + + var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers); + this.fileTransfers.Add(newTransfer); + + transferAdded = true; + break; + } + } + + // Check if File Transfer was added + if (!transferAdded) + { + throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName)); + } + + // Add the new Cabinets to media table using LastSequence of Base Cabinet + var mediaTable = this.Output.Tables["Media"]; + var wixFileTable = this.Output.Tables["WixFile"]; + var diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain + var lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain + var lastSplitCabinetFound = false; // Used for Error Handling + + var lastCabinetOfThisSequence = String.Empty; + // Get the Value of Last Cabinet Added in this split Sequence from Dictionary + if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence)) + { + // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence + lastCabinetOfThisSequence = firstCabinetName; + } + + foreach (MediaRow mediaRow in mediaTable.Rows) + { + // Get details for the Last Cabinet Added in this Split Sequence + if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) + { + lastSequenceForLastSplitCabAdded = mediaRow.LastSequence; + diskIDForLastSplitCabAdded = mediaRow.DiskId; + lastSplitCabinetFound = true; + } + + // Check for Name Collision for the new Cabinet added + if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) + { + // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row + throw new WixException(ErrorMessages.SplitCabinetNameCollision(newCabinetName, firstCabinetName)); + } + } + + // Check if the last Split Cabinet was found in the Media Table + if (!lastSplitCabinetFound) + { + throw new WixException(ErrorMessages.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence)); + } + + // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort + // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with + // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction + MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null); + newMediaRow.Cabinet = newCabinetName; + newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion + newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded; + + // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique + foreach (MediaRow mediaRow in mediaTable.Rows) + { + // Check if this row comes after inserted row and it is not the new cabinet inserted row + if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase)) + { + mediaRow.DiskId++; // Increment DiskID + } + } + + // Now Increment DiskID for All files Rows so that they refer to the right Media Row + foreach (WixFileRow wixFileRow in wixFileTable.Rows) + { + // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet + // This check will work as we have only one large file in every splitting cabinet + // If we want to support splitting cabinet with more large files we need to update this code + if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase)) + { + wixFileRow.DiskId++; // Increment DiskID + } + } + + // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback + this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName; + + mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails + } + finally + { + // Releasing the Mutex here + mutex.ReleaseMutex(); + } +#endif + } + + + /// + /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides + /// + private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize) + { + // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size + var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS"); + var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); + + // Supply Compile MediaTemplate Attributes to Cabinet Builder + if (this.MediaTemplate != null) + { + // Get the Value for Max Cab Size for File Splitting + var maxCabSizeForLargeFileInMB = 0; + try + { + // Override authored mcslfs value if environment variable is authored. + maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : this.MediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting; + + var testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024; + maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB; + } + catch (FormatException) + { + throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString)); + } + catch (OverflowException) + { + throw new WixException(ErrorMessages.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, MaxValueOfMaxCabSizeForLargeFileSplitting)); + } + + var maxPreCompressedSizeInMB = 0; + try + { + // Override authored mums value if environment variable is authored. + maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : this.MediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize; + + var testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024; + maxUncompressedMediaSize = maxPreCompressedSizeInMB; + } + catch (FormatException) + { + throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); + } + catch (OverflowException) + { + throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB)); + } + } + else + { + maxCabSizeForLargeFileSplitting = 0; + maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs new file mode 100644 index 00000000..47d8399f --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs @@ -0,0 +1,81 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + + /// + /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. + /// + internal class CreateDeltaPatchesCommand + { + public CreateDeltaPatchesCommand(List fileFacades, string intermediateFolder, WixPatchSymbol wixPatchId) + { + this.FileFacades = fileFacades; + this.IntermediateFolder = intermediateFolder; + this.WixPatchId = wixPatchId; + } + + private IEnumerable FileFacades { get; } + + private WixPatchSymbol WixPatchId { get; } + + private string IntermediateFolder { get; } + + public void Execute() + { + var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false; + var apiPatchingSymbolFlags = (PatchSymbolFlags)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0); + +#if TODO_PATCHING_DELTA + foreach (FileFacade facade in this.FileFacades) + { + if (RowOperation.Modify == facade.File.Operation && + 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile)) + { + string deltaBase = String.Concat("delta_", facade.File.File); + string deltaFile = Path.Combine(this.IntermediateFolder, String.Concat(deltaBase, ".dpf")); + string headerFile = Path.Combine(this.IntermediateFolder, String.Concat(deltaBase, ".phd")); + + bool retainRangeWarning = false; + + if (PatchAPI.PatchInterop.CreateDelta( + deltaFile, + facade.WixFile.Source, + facade.DeltaPatchFile.Symbols, + facade.DeltaPatchFile.RetainOffsets, + new[] { facade.WixFile.PreviousSource }, + facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }), + facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }), + facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }), + facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }), + facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }), + apiPatchingSymbolFlags, + optimizePatchSizeForLargeFiles, + out retainRangeWarning)) + { + PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile); + + facade.WixFile.Source = deltaFile; + facade.WixFile.DeltaPatchHeaderSource = headerFile; + } + + if (retainRangeWarning) + { + // TODO: get patch family to add to warning message for PatchWiz parity. + Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File)); + } + } + } +#endif + + throw new NotImplementedException(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs new file mode 100644 index 00000000..ff03413c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs @@ -0,0 +1,250 @@ +// 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.Bind +{ + using System; + using System.Globalization; + using System.IO; + using System.Text; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class CreateIdtFileCommand + { + public CreateIdtFileCommand(IMessaging messaging, Table table, int codepage, string intermediateFolder, bool keepAddedColumns) + { + this.Messaging = messaging; + this.Table = table; + this.Codepage = codepage; + this.IntermediateFolder = intermediateFolder; + this.KeepAddedColumns = keepAddedColumns; + } + + private IMessaging Messaging { get; } + + private Table Table { get; } + + private int Codepage { get; set; } + + private string IntermediateFolder { get; } + + private bool KeepAddedColumns { get; } + + public string IdtPath { get; private set; } + + public void Execute() + { + // write out the table to an IDT file + var encoding = GetCodepageEncoding(this.Codepage); + + this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt")); + + using (var idtWriter = new StreamWriter(this.IdtPath, false, encoding)) + { + this.TableToIdtDefinition(this.Table, idtWriter, this.KeepAddedColumns); + } + } + + private void TableToIdtDefinition(Table table, StreamWriter writer, bool keepAddedColumns) + { + if (table.Definition.Unreal) + { + return; + } + + if (TableDefinition.MaxColumnsInRealTable < table.Definition.Columns.Length) + { + throw new WixException(ErrorMessages.TooManyColumnsInRealTable(table.Definition.Name, table.Definition.Columns.Length, TableDefinition.MaxColumnsInRealTable)); + } + + // Tack on the table header, and flush before we start writing bytes directly to the stream. + var header = this.TableDefinitionToIdtDefinition(table.Definition, keepAddedColumns); + writer.Write(header); + writer.Flush(); + + using (var binary = new BinaryWriter(writer.BaseStream, writer.Encoding, true)) + { + // Create an encoding that replaces characters with question marks, and doesn't throw. We'll + // use this in case of errors + Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage); + + foreach (Row row in table.Rows) + { + if (row.Redundant) + { + continue; + } + + string rowString = this.RowToIdtDefinition(row, keepAddedColumns); + byte[] rowBytes; + + try + { + // GetBytes will throw an exception if any character doesn't match our current encoding + rowBytes = writer.Encoding.GetBytes(rowString); + } + catch (EncoderFallbackException) + { + this.Messaging.Write(ErrorMessages.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture))); + + rowBytes = convertEncoding.GetBytes(rowString); + } + + binary.Write(rowBytes, 0, rowBytes.Length); + } + } + } + + private string TableDefinitionToIdtDefinition(TableDefinition definition, bool keepAddedColumns) + { + var first = true; + var columnString = new StringBuilder(); + var dataString = new StringBuilder(); + var tableString = new StringBuilder(); + + tableString.Append(definition.Name); + foreach (var column in definition.Columns) + { + // Conditionally keep columns added in a transform; otherwise, + // break because columns can only be added at the end. + if (column.Added && !keepAddedColumns) + { + break; + } + + if (column.Unreal) + { + continue; + } + + if (!first) + { + columnString.Append('\t'); + dataString.Append('\t'); + } + + columnString.Append(column.Name); + dataString.Append(ColumnIdtType(column)); + + if (column.PrimaryKey) + { + tableString.AppendFormat("\t{0}", column.Name); + } + + first = false; + } + columnString.Append("\r\n"); + columnString.Append(dataString); + columnString.Append("\r\n"); + columnString.Append(tableString); + columnString.Append("\r\n"); + + return columnString.ToString(); + } + + private string RowToIdtDefinition(Row row, bool keepAddedColumns) + { + var first = true; + var sb = new StringBuilder(); + + foreach (var field in row.Fields) + { + // Conditionally keep columns added in a transform; otherwise, + // break because columns can only be added at the end. + if (field.Column.Added && !keepAddedColumns) + { + break; + } + + if (field.Column.Unreal) + { + continue; + } + + if (first) + { + first = false; + } + else + { + sb.Append('\t'); + } + + sb.Append(this.FieldToIdtValue(field)); + } + sb.Append("\r\n"); + + return sb.ToString(); + } + + private string FieldToIdtValue(Field field) + { + var data = field.AsString(); + + if (String.IsNullOrEmpty(data)) + { + return data; + } + + // Special field value idt-specific escaping. + return data.Replace('\t', '\x10') + .Replace('\r', '\x11') + .Replace('\n', '\x19'); + } + + private static Encoding GetCodepageEncoding(int codepage) + { + Encoding encoding; + + // If UTF8 encoding, use the UTF8-specific constructor to avoid writing + // the byte order mark at the beginning of the file + if (codepage == Encoding.UTF8.CodePage) + { + encoding = new UTF8Encoding(false, true); + } + else + { + if (codepage == 0) + { + codepage = Encoding.ASCII.CodePage; + } + + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); + } + + return encoding; + } + + /// + /// Gets the type of the column in IDT format. + /// + /// IDT format for column type. + private static string ColumnIdtType(ColumnDefinition column) + { + char typeCharacter; + switch (column.Type) + { + case ColumnType.Number: + typeCharacter = column.Nullable ? 'I' : 'i'; + break; + case ColumnType.Preserved: + case ColumnType.String: + typeCharacter = column.Nullable ? 'S' : 's'; + break; + case ColumnType.Localized: + typeCharacter = column.Nullable ? 'L' : 'l'; + break; + case ColumnType.Object: + typeCharacter = column.Nullable ? 'V' : 'v'; + break; + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_UnknownColumnType, column.Type)); + } + + return String.Concat(typeCharacter, column.Length); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs new file mode 100644 index 00000000..d0e25571 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs @@ -0,0 +1,260 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Services; + + internal class CreateInstanceTransformsCommand + { + public CreateInstanceTransformsCommand(IntermediateSection section, WindowsInstallerData output, TableDefinitionCollection tableDefinitions, IBackendHelper backendHelper) + { + this.Section = section; + this.Output = output; + this.TableDefinitions = tableDefinitions; + this.BackendHelper = backendHelper; + } + + private IntermediateSection Section { get; } + + private WindowsInstallerData Output { get; } + + public TableDefinitionCollection TableDefinitions { get; } + + private IBackendHelper BackendHelper { get; } + + public void Execute() + { + // Create and add substorages for instance transforms. + var wixInstanceTransformsSymbols = this.Section.Symbols.OfType(); + + if (wixInstanceTransformsSymbols.Any()) + { + string targetProductCode = null; + string targetUpgradeCode = null; + string targetProductVersion = null; + + var targetSummaryInformationTable = this.Output.Tables["_SummaryInformation"]; + var targetPropertyTable = this.Output.Tables["Property"]; + + // Get the data from target database + foreach (var propertyRow in targetPropertyTable.Rows) + { + if ("ProductCode" == (string)propertyRow[0]) + { + targetProductCode = (string)propertyRow[1]; + } + else if ("ProductVersion" == (string)propertyRow[0]) + { + targetProductVersion = (string)propertyRow[1]; + } + else if ("UpgradeCode" == (string)propertyRow[0]) + { + targetUpgradeCode = (string)propertyRow[1]; + } + } + + // Index the Instance Component Rows, we'll get the Components rows from the real Component table. + var targetInstanceComponentTable = this.Section.Symbols.OfType(); + var instanceComponentGuids = targetInstanceComponentTable.ToDictionary(t => t.Id.Id, t => (ComponentRow)null); + + if (instanceComponentGuids.Any()) + { + var targetComponentTable = this.Output.Tables["Component"]; + foreach (ComponentRow componentRow in targetComponentTable.Rows) + { + var component = (string)componentRow[0]; + if (instanceComponentGuids.ContainsKey(component)) + { + instanceComponentGuids[component] = componentRow; + } + } + } + + // Generate the instance transforms + foreach (var instanceSymbol in wixInstanceTransformsSymbols) + { + var instanceId = instanceSymbol.Id.Id; + + var instanceTransform = new WindowsInstallerData(instanceSymbol.SourceLineNumbers); + instanceTransform.Type = OutputType.Transform; + instanceTransform.Codepage = this.Output.Codepage; + + var instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + string targetPlatformAndLanguage = null; + + foreach (var summaryInformationRow in targetSummaryInformationTable.Rows) + { + if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE + { + targetPlatformAndLanguage = (string)summaryInformationRow[1]; + } + + // Copy the row's data to the transform. + var copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(summaryInformationRow.SourceLineNumbers); + copyOfSummaryRow[0] = summaryInformationRow[0]; + copyOfSummaryRow[1] = summaryInformationRow[1]; + } + + // Modify the appropriate properties. + var propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]); + + // Change the ProductCode property + var productCode = instanceSymbol.ProductCode; + if ("*" == productCode) + { + productCode = this.BackendHelper.CreateGuid(); + } + + var productCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); + productCodeRow.Operation = RowOperation.Modify; + productCodeRow.Fields[1].Modified = true; + productCodeRow[0] = "ProductCode"; + productCodeRow[1] = productCode; + + // Change the instance property + var instanceIdRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); + instanceIdRow.Operation = RowOperation.Modify; + instanceIdRow.Fields[1].Modified = true; + instanceIdRow[0] = instanceSymbol.PropertyId; + instanceIdRow[1] = instanceId; + + if (!String.IsNullOrEmpty(instanceSymbol.ProductName)) + { + // Change the ProductName property + var productNameRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); + productNameRow.Operation = RowOperation.Modify; + productNameRow.Fields[1].Modified = true; + productNameRow[0] = "ProductName"; + productNameRow[1] = instanceSymbol.ProductName; + } + + if (!String.IsNullOrEmpty(instanceSymbol.UpgradeCode)) + { + // Change the UpgradeCode property + var upgradeCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers); + upgradeCodeRow.Operation = RowOperation.Modify; + upgradeCodeRow.Fields[1].Modified = true; + upgradeCodeRow[0] = "UpgradeCode"; + upgradeCodeRow[1] = instanceSymbol.UpgradeCode; + + // Change the Upgrade table + var targetUpgradeTable = this.Output.Tables["Upgrade"]; + if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) + { + var upgradeId = instanceSymbol.UpgradeCode; + var upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]); + foreach (var row in targetUpgradeTable.Rows) + { + // In case they are upgrading other codes to this new product, leave the ones that don't match the + // Product.UpgradeCode intact. + if (targetUpgradeCode == (string)row[0]) + { + var upgradeRow = upgradeTable.CreateRow(row.SourceLineNumbers); + upgradeRow.Operation = RowOperation.Add; + upgradeRow.Fields[0].Modified = true; + // I was hoping to be able to RowOperation.Modify, but that didn't appear to function. + // upgradeRow.Fields[0].PreviousData = (string)row[0]; + + // Inserting a new Upgrade record with the updated UpgradeCode + upgradeRow[0] = upgradeId; + upgradeRow[1] = row[1]; + upgradeRow[2] = row[2]; + upgradeRow[3] = row[3]; + upgradeRow[4] = row[4]; + upgradeRow[5] = row[5]; + upgradeRow[6] = row[6]; + + // Delete the old row + var upgradeRemoveRow = upgradeTable.CreateRow(row.SourceLineNumbers); + upgradeRemoveRow.Operation = RowOperation.Delete; + upgradeRemoveRow[0] = row[0]; + upgradeRemoveRow[1] = row[1]; + upgradeRemoveRow[2] = row[2]; + upgradeRemoveRow[3] = row[3]; + upgradeRemoveRow[4] = row[4]; + upgradeRemoveRow[5] = row[5]; + upgradeRemoveRow[6] = row[6]; + } + } + } + } + + // If there are instance Components generate new GUIDs for them. + if (0 < instanceComponentGuids.Count) + { + var componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]); + foreach (var targetComponentRow in instanceComponentGuids.Values) + { + var guid = targetComponentRow.Guid; + if (!String.IsNullOrEmpty(guid)) + { + var instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); + instanceComponentRow.Operation = RowOperation.Modify; + instanceComponentRow.Fields[1].Modified = true; + instanceComponentRow[0] = targetComponentRow[0]; + instanceComponentRow[1] = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)); + instanceComponentRow[2] = targetComponentRow[2]; + instanceComponentRow[3] = targetComponentRow[3]; + instanceComponentRow[4] = targetComponentRow[4]; + instanceComponentRow[5] = targetComponentRow[5]; + } + } + } + + // Update the summary information + var summaryRows = new Dictionary(instanceSummaryInformationTable.Rows.Count); + foreach (var row in instanceSummaryInformationTable.Rows) + { + summaryRows[(int)row[0]] = row; + + if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) + { + row[1] = targetPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) + { + row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode); + } + else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) + { + row[1] = 0; + } + else if ((int)SummaryInformation.Transform.Security == (int)row[0]) + { + row[1] = "4"; + } + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) + { + var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); + summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; + summaryRow[1] = targetPlatformAndLanguage; + } + else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) + { + var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); + summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; + summaryRow[1] = "0"; + } + else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) + { + var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers); + summaryRow[0] = (int)SummaryInformation.Transform.Security; + summaryRow[1] = "4"; + } + + this.Output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs new file mode 100644 index 00000000..5c993f63 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs @@ -0,0 +1,93 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class CreatePatchTransformsCommand + { + public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, string intermediateFolder) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Intermediate = intermediate; + this.IntermediateFolder = intermediateFolder; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private Intermediate Intermediate { get; } + + private string IntermediateFolder { get; } + + public IEnumerable PatchTransforms { get; private set; } + + public IEnumerable Execute() + { + var patchTransforms = new List(); + + var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).OfType(); + + foreach (var symbol in symbols) + { + WindowsInstallerData transform; + + if (symbol.TransformFile is null) + { + var baselineData = this.GetData(symbol.BaselineFile.Path); + var updateData = this.GetData(symbol.UpdateFile.Path); + + var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false); + transform = command.Execute(); + } + else + { + var exportBasePath = Path.Combine(this.IntermediateFolder, "_trans"); // TODO: come up with a better path. + + var command = new UnbindTransformCommand(this.Messaging, this.BackendHelper, symbol.TransformFile.Path, exportBasePath, this.IntermediateFolder); + transform = command.Execute(); + } + + patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform)); + } + + this.PatchTransforms = patchTransforms; + + return this.PatchTransforms; + } + + private WindowsInstallerData GetData(string path) + { + var ext = Path.GetExtension(path); + + if (".msi".Equals(ext, StringComparison.OrdinalIgnoreCase)) + { + using (var database = new Database(path, OpenDatabase.ReadOnly)) + { + var exportBasePath = Path.Combine(this.IntermediateFolder, "_msi"); // TODO: come up with a better path. + + var isAdminImage = false; // TODO: need a better way to set this + + var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, database, path, OutputType.Product, exportBasePath, this.IntermediateFolder, isAdminImage, suppressDemodularization: true, skipSummaryInfo: true); + return command.Execute(); + } + } + else // assume .wixpdb (or .wixout) + { + var data = WindowsInstallerData.Load(path, true); + return data; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs new file mode 100644 index 00000000..ba7c03a0 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs @@ -0,0 +1,83 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class CreateSpecialPropertiesCommand + { + public CreateSpecialPropertiesCommand(IntermediateSection section) + { + this.Section = section; + } + + private IntermediateSection Section { get; } + + public void Execute() + { + // Create lists of the properties that contribute to the special lists of properties. + var adminProperties = new SortedSet(); + var secureProperties = new SortedSet(); + var hiddenProperties = new SortedSet(); + + foreach (var wixPropertyRow in this.Section.Symbols.OfType()) + { + if (wixPropertyRow.Admin) + { + adminProperties.Add(wixPropertyRow.PropertyRef); + } + + if (wixPropertyRow.Hidden) + { + hiddenProperties.Add(wixPropertyRow.PropertyRef); + } + + if (wixPropertyRow.Secure) + { + secureProperties.Add(wixPropertyRow.PropertyRef); + } + } + + // Hide properties for in-script custom actions that have HideTarget set. + var hideTargetCustomActions = this.Section.Symbols.OfType().Where( + ca => ca.Hidden + && (ca.ExecutionType == CustomActionExecutionType.Deferred + || ca.ExecutionType == CustomActionExecutionType.Commit + || ca.ExecutionType == CustomActionExecutionType.Rollback)) + .Select(ca => ca.Id.Id); + hiddenProperties.UnionWith(hideTargetCustomActions); + + // Ensure upgrade action properties are secure. + var actionProperties = this.Section.Symbols.OfType().Select(u => u.ActionProperty); + secureProperties.UnionWith(actionProperties); + + if (0 < adminProperties.Count) + { + this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "AdminProperties")) + { + Value = String.Join(";", adminProperties), + }); + } + + if (0 < secureProperties.Count) + { + this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "SecureCustomProperties")) + { + Value = String.Join(";", secureProperties), + }); + } + + if (0 < hiddenProperties.Count) + { + this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "MsiHiddenProperties")) + { + Value = String.Join(";", hiddenProperties) + }); + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs new file mode 100644 index 00000000..d34ca3fe --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs @@ -0,0 +1,1621 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class CreateWindowsInstallerDataFromIRCommand + { + private static readonly char[] PathSeparatorChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; + + public CreateWindowsInstallerDataFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, int codepage, IEnumerable backendExtensions, IWindowsInstallerBackendHelper backendHelper) + { + this.Messaging = messaging; + this.Section = section; + this.TableDefinitions = tableDefinitions; + this.Codepage = codepage; + this.BackendExtensions = backendExtensions; + this.BackendHelper = backendHelper; + this.GeneratedShortNames = new Dictionary>(); + } + + private IEnumerable BackendExtensions { get; } + + private IWindowsInstallerBackendHelper BackendHelper { get; } + + private IMessaging Messaging { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private int Codepage { get; } + + private IntermediateSection Section { get; } + + private Dictionary> GeneratedShortNames { get; } + + public WindowsInstallerData Data { get; private set; } + + public WindowsInstallerData Execute() + { + this.Data = new WindowsInstallerData(this.Section.Symbols.First().SourceLineNumbers) + { + Codepage = this.Codepage, + Type = SectionTypeToOutputType(this.Section.Type) + }; + + this.AddSectionToData(); + + return this.Data; + } + + private void AddSectionToData() + { + var cellsByTableAndRowId = new Dictionary>(); + + foreach (var symbol in this.Section.Symbols) + { + var unknownSymbol = false; + switch (symbol.Definition.Type) + { + case SymbolDefinitionType.AppSearch: + this.AddSymbolDefaultly(symbol); + this.Data.EnsureTable(this.TableDefinitions["Signature"]); + break; + + case SymbolDefinitionType.Assembly: + this.AddAssemblySymbol((AssemblySymbol)symbol); + break; + + case SymbolDefinitionType.BBControl: + this.AddBBControlSymbol((BBControlSymbol)symbol); + break; + + case SymbolDefinitionType.Class: + this.AddClassSymbol((ClassSymbol)symbol); + break; + + case SymbolDefinitionType.Control: + this.AddControlSymbol((ControlSymbol)symbol); + break; + + case SymbolDefinitionType.ControlEvent: + this.AddControlEventSymbol((ControlEventSymbol)symbol); + break; + + case SymbolDefinitionType.Component: + this.AddComponentSymbol((ComponentSymbol)symbol); + break; + + case SymbolDefinitionType.CustomAction: + this.AddCustomActionSymbol((CustomActionSymbol)symbol); + break; + + case SymbolDefinitionType.Dialog: + this.AddDialogSymbol((DialogSymbol)symbol); + break; + + case SymbolDefinitionType.Directory: + this.AddDirectorySymbol((DirectorySymbol)symbol); + break; + + case SymbolDefinitionType.DuplicateFile: + this.AddDuplicateFileSymbol((DuplicateFileSymbol)symbol); + break; + + case SymbolDefinitionType.Environment: + this.AddEnvironmentSymbol((EnvironmentSymbol)symbol); + break; + + case SymbolDefinitionType.Error: + this.AddErrorSymbol((ErrorSymbol)symbol); + break; + + case SymbolDefinitionType.Feature: + this.AddFeatureSymbol((FeatureSymbol)symbol); + break; + + case SymbolDefinitionType.File: + this.AddFileSymbol((FileSymbol)symbol); + break; + + case SymbolDefinitionType.IniFile: + this.AddIniFileSymbol((IniFileSymbol)symbol); + break; + + case SymbolDefinitionType.IniLocator: + this.AddIniLocatorSymbol((IniLocatorSymbol)symbol); + break; + + case SymbolDefinitionType.Media: + this.AddMediaSymbol((MediaSymbol)symbol); + break; + + case SymbolDefinitionType.ModuleConfiguration: + this.AddModuleConfigurationSymbol((ModuleConfigurationSymbol)symbol); + this.EnsureModuleIgnoredTable(symbol, "ModuleConfiguration"); + break; + + case SymbolDefinitionType.ModuleSubstitution: + this.EnsureModuleIgnoredTable(symbol, "ModuleSubstitution"); + break; + + case SymbolDefinitionType.MsiEmbeddedUI: + this.AddMsiEmbeddedUISymbol((MsiEmbeddedUISymbol)symbol); + break; + + case SymbolDefinitionType.MsiServiceConfig: + this.AddMsiServiceConfigSymbol((MsiServiceConfigSymbol)symbol); + break; + + case SymbolDefinitionType.MsiServiceConfigFailureActions: + this.AddMsiServiceConfigFailureActionsSymbol((MsiServiceConfigFailureActionsSymbol)symbol); + break; + + case SymbolDefinitionType.MoveFile: + this.AddMoveFileSymbol((MoveFileSymbol)symbol); + break; + + case SymbolDefinitionType.ProgId: + this.AddSymbolDefaultly(symbol); + this.Data.EnsureTable(this.TableDefinitions["Extension"]); + break; + + case SymbolDefinitionType.Property: + this.AddPropertySymbol((PropertySymbol)symbol); + break; + + case SymbolDefinitionType.RemoveFile: + this.AddRemoveFileSymbol((RemoveFileSymbol)symbol); + break; + + case SymbolDefinitionType.Registry: + this.AddRegistrySymbol((RegistrySymbol)symbol); + break; + + case SymbolDefinitionType.RegLocator: + this.AddRegLocatorSymbol((RegLocatorSymbol)symbol); + break; + + case SymbolDefinitionType.RemoveRegistry: + this.AddRemoveRegistrySymbol((RemoveRegistrySymbol)symbol); + break; + + case SymbolDefinitionType.ServiceControl: + this.AddServiceControlSymbol((ServiceControlSymbol)symbol); + break; + + case SymbolDefinitionType.ServiceInstall: + this.AddServiceInstallSymbol((ServiceInstallSymbol)symbol); + break; + + case SymbolDefinitionType.Shortcut: + this.AddShortcutSymbol((ShortcutSymbol)symbol); + break; + + case SymbolDefinitionType.TextStyle: + this.AddTextStyleSymbol((TextStyleSymbol)symbol); + break; + + case SymbolDefinitionType.Upgrade: + this.AddUpgradeSymbol((UpgradeSymbol)symbol); + break; + + case SymbolDefinitionType.WixAction: + this.AddWixActionSymbol((WixActionSymbol)symbol); + break; + + case SymbolDefinitionType.WixCustomTableCell: + this.IndexCustomTableCellSymbol((WixCustomTableCellSymbol)symbol, cellsByTableAndRowId); + break; + + case SymbolDefinitionType.WixEnsureTable: + this.AddWixEnsureTableSymbol((WixEnsureTableSymbol)symbol); + break; + + case SymbolDefinitionType.WixPackage: + this.AddWixPackageSymbol((WixPackageSymbol)symbol); + break; + + // Symbols used internally and are not added to the output. + case SymbolDefinitionType.WixBuildInfo: + case SymbolDefinitionType.WixBindUpdatedFiles: + case SymbolDefinitionType.WixComponentGroup: + case SymbolDefinitionType.WixComplexReference: + case SymbolDefinitionType.WixDeltaPatchFile: + case SymbolDefinitionType.WixDeltaPatchSymbolPaths: + case SymbolDefinitionType.WixFragment: + case SymbolDefinitionType.WixFeatureGroup: + case SymbolDefinitionType.WixInstanceComponent: + case SymbolDefinitionType.WixInstanceTransforms: + case SymbolDefinitionType.WixFeatureModules: + case SymbolDefinitionType.WixGroup: + case SymbolDefinitionType.WixMediaTemplate: + case SymbolDefinitionType.WixMerge: + case SymbolDefinitionType.WixOrdering: + case SymbolDefinitionType.WixPatchBaseline: + case SymbolDefinitionType.WixPatchFamilyGroup: + case SymbolDefinitionType.WixPatch: + case SymbolDefinitionType.WixPatchRef: + case SymbolDefinitionType.WixPatchTarget: + case SymbolDefinitionType.WixProperty: + case SymbolDefinitionType.WixProductTag: + case SymbolDefinitionType.WixSimpleReference: + case SymbolDefinitionType.WixSuppressAction: + case SymbolDefinitionType.WixSuppressModularization: + case SymbolDefinitionType.WixUI: + case SymbolDefinitionType.WixVariable: + break; + + // Already processed by LoadTableDefinitions. + case SymbolDefinitionType.WixCustomTable: + case SymbolDefinitionType.WixCustomTableColumn: + break; + + case SymbolDefinitionType.MustBeFromAnExtension: + unknownSymbol = !this.AddSymbolFromExtension(symbol); + break; + + default: + unknownSymbol = !this.AddSymbolDefaultly(symbol); + break; + } + + if (unknownSymbol) + { + this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol)); + } + } + + this.AddIndexedCellSymbols(cellsByTableAndRowId); + this.EnsureRequiredTables(); + this.ReportGeneratedShortFileNameConflicts(); + this.ReportIllegalTables(); + this.ReportMismatchedModularizations(); + this.ReportWindowsInstallerDataInconsistencies(); + } + + private void AddAssemblySymbol(AssemblySymbol symbol) + { + var attributes = symbol.Type == AssemblyType.Win32Assembly ? 1 : (int?)null; + + var row = this.CreateRow(symbol, "MsiAssembly"); + row[0] = symbol.ComponentRef; + row[1] = symbol.FeatureRef; + row[2] = symbol.ManifestFileRef; + row[3] = symbol.ApplicationFileRef; + row[4] = attributes; + } + + private void AddBBControlSymbol(BBControlSymbol symbol) + { + var attributes = symbol.Attributes; + attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; + attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0; + attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0; + attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0; + attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0; + attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0; + attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; + attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; + + var row = this.CreateRow(symbol, "BBControl"); + row[0] = symbol.BillboardRef; + row[1] = symbol.BBControl; + row[2] = symbol.Type; + row[3] = symbol.X; + row[4] = symbol.Y; + row[5] = symbol.Width; + row[6] = symbol.Height; + row[7] = attributes; + row[8] = symbol.Text; + } + + private void AddClassSymbol(ClassSymbol symbol) + { + var row = this.CreateRow(symbol, "Class"); + row[0] = symbol.CLSID; + row[1] = symbol.Context; + row[2] = symbol.ComponentRef; + row[3] = symbol.DefaultProgIdRef; + row[4] = symbol.Description; + row[5] = symbol.AppIdRef; + row[6] = symbol.FileTypeMask; + row[7] = symbol.IconRef; + row[8] = symbol.IconIndex; + row[9] = symbol.DefInprocHandler; + row[10] = symbol.Argument; + row[11] = symbol.FeatureRef; + row[12] = symbol.RelativePath ? (int?)1 : null; + } + + private void AddControlSymbol(ControlSymbol symbol) + { + var text = symbol.Text; + var attributes = symbol.Attributes; + attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0; + attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0; + attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0; + attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0; + attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0; + attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0; + attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0; + attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0; + + // If we're tracking disk space, and this is a non-FormatSize Text control, + // and the text attribute starts with '[' and ends with ']', add a space. + // It is not necessary for the whole string to be a property, just those + // two characters matter. + if (symbol.TrackDiskSpace && + "Text" == symbol.Type && + WindowsInstallerConstants.MsidbControlAttributesFormatSize != (attributes & WindowsInstallerConstants.MsidbControlAttributesFormatSize) && + null != text && text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) + { + text = String.Concat(text, " "); + } + + var row = this.CreateRow(symbol, "Control"); + row[0] = symbol.DialogRef; + row[1] = symbol.Control; + row[2] = symbol.Type; + row[3] = symbol.X; + row[4] = symbol.Y; + row[5] = symbol.Width; + row[6] = symbol.Height; + row[7] = attributes; + row[8] = symbol.Property; + row[9] = text; + row[10] = symbol.NextControlRef; + row[11] = symbol.Help; + } + + private void AddControlEventSymbol(ControlEventSymbol symbol) + { + var row = this.CreateRow(symbol, "ControlEvent"); + row[0] = symbol.DialogRef; + row[1] = symbol.ControlRef; + row[2] = symbol.Event; + row[3] = symbol.Argument; + row[4] = String.IsNullOrEmpty(symbol.Condition) ? "1" : symbol.Condition; + row[5] = symbol.Ordering; + } + + private void AddComponentSymbol(ComponentSymbol symbol) + { + var attributes = ComponentLocation.Either == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesOptional : 0; + attributes |= ComponentLocation.SourceOnly == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesSourceOnly : 0; + attributes |= ComponentKeyPathType.Registry == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath : 0; + attributes |= ComponentKeyPathType.OdbcDataSource == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource : 0; + attributes |= symbol.DisableRegistryReflection ? WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection : 0; + attributes |= symbol.NeverOverwrite ? WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite : 0; + attributes |= symbol.Permanent ? WindowsInstallerConstants.MsidbComponentAttributesPermanent : 0; + attributes |= symbol.SharedDllRefCount ? WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount : 0; + attributes |= symbol.Shared ? WindowsInstallerConstants.MsidbComponentAttributesShared : 0; + attributes |= symbol.Transitive ? WindowsInstallerConstants.MsidbComponentAttributesTransitive : 0; + attributes |= symbol.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence : 0; + attributes |= symbol.Win64 ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0; + + var row = this.CreateRow(symbol, "Component"); + row[0] = symbol.Id.Id; + row[1] = symbol.ComponentId; + row[2] = symbol.DirectoryRef; + row[3] = attributes; + row[4] = symbol.Condition; + row[5] = symbol.KeyPath; + } + + private void AddCustomActionSymbol(CustomActionSymbol symbol) + { + var type = symbol.Win64 ? WindowsInstallerConstants.MsidbCustomActionType64BitScript : 0; + type |= symbol.IgnoreResult ? WindowsInstallerConstants.MsidbCustomActionTypeContinue : 0; + type |= symbol.Hidden ? WindowsInstallerConstants.MsidbCustomActionTypeHideTarget : 0; + type |= symbol.Async ? WindowsInstallerConstants.MsidbCustomActionTypeAsync : 0; + type |= CustomActionExecutionType.FirstSequence == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence : 0; + type |= CustomActionExecutionType.OncePerProcess == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess : 0; + type |= CustomActionExecutionType.ClientRepeat == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat : 0; + type |= CustomActionExecutionType.Deferred == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript : 0; + type |= CustomActionExecutionType.Rollback == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeRollback : 0; + type |= CustomActionExecutionType.Commit == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeCommit : 0; + type |= CustomActionSourceType.File == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeSourceFile : 0; + type |= CustomActionSourceType.Directory == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeDirectory : 0; + type |= CustomActionSourceType.Property == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeProperty : 0; + type |= CustomActionTargetType.Dll == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeDll : 0; + type |= CustomActionTargetType.Exe == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeExe : 0; + type |= CustomActionTargetType.TextData == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeTextData : 0; + type |= CustomActionTargetType.JScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeJScript : 0; + type |= CustomActionTargetType.VBScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeVBScript : 0; + + if (WindowsInstallerConstants.MsidbCustomActionTypeInScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeInScript)) + { + type |= symbol.Impersonate ? 0 : WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate; + type |= symbol.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0; + } + + var row = this.CreateRow(symbol, "CustomAction"); + row[0] = symbol.Id.Id; + row[1] = type; + row[2] = symbol.Source; + row[3] = symbol.Target; + row[4] = symbol.PatchUninstall ? (int?)WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall : null; + + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); + } + } + + private void AddDialogSymbol(DialogSymbol symbol) + { + var attributes = symbol.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0; + attributes |= symbol.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; + attributes |= symbol.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0; + attributes |= symbol.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette : 0; + attributes |= symbol.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0; + attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0; + attributes |= symbol.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0; + attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0; + attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0; + attributes |= symbol.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; + attributes |= symbol.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; + + var row = this.CreateRow(symbol, "Dialog"); + row[0] = symbol.Id.Id; + row[1] = symbol.HCentering; + row[2] = symbol.VCentering; + row[3] = symbol.Width; + row[4] = symbol.Height; + row[5] = attributes; + row[6] = symbol.Title; + row[7] = symbol.FirstControlRef; + row[8] = symbol.DefaultControlRef; + row[9] = symbol.CancelControlRef; + + this.Data.EnsureTable(this.TableDefinitions["ListBox"]); + } + + private void AddDirectorySymbol(DirectorySymbol symbol) + { + (var name, var parentDir) = this.AddDirectorySubdirectories(symbol); + + var shortName = symbol.ShortName; + var sourceShortname = symbol.SourceShortName; + + if (String.IsNullOrEmpty(shortName) && name != null && name != "." && name != "SourceDir" && !this.BackendHelper.IsValidShortFilename(name, false)) + { + shortName = this.CreateShortName(name, false, "Directory", symbol.ParentDirectoryRef); + } + + if (String.IsNullOrEmpty(sourceShortname) && !String.IsNullOrEmpty(symbol.SourceName) && !this.BackendHelper.IsValidShortFilename(symbol.SourceName, false)) + { + sourceShortname = this.CreateShortName(symbol.SourceName, false, "Directory", symbol.ParentDirectoryRef); + } + + var sourceName = CreateMsiFilename(sourceShortname, symbol.SourceName); + var targetName = CreateMsiFilename(shortName, name); + + if (String.IsNullOrEmpty(targetName)) + { + targetName = "."; + } + + var defaultDir = String.IsNullOrEmpty(sourceName) || sourceName == targetName ? targetName : targetName + ":" + sourceName; + + var row = this.CreateRow(symbol, "Directory"); + row[0] = symbol.Id.Id; + row[1] = parentDir; + row[2] = defaultDir; + + if (OutputType.Module == this.Data.Type) + { + var directoryId = symbol.Id.Id; + + if (WindowsInstallerStandard.IsStandardDirectory(directoryId)) + { + // If the directory table contains references to standard windows folders + // mergemod.dll will add customactions to set the MSM directory to + // the same directory as the standard windows folder and will add references to + // custom action to all the standard sequence tables. A problem will occur + // if the MSI does not have these tables as mergemod.dll does not add these + // tables to the MSI if absent. This code adds the tables in case mergemod.dll + // needs them. + this.Data.EnsureTable(this.TableDefinitions["CustomAction"]); + this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); + this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); + this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); + } + else + { + foreach (var standardDirectory in WindowsInstallerStandard.StandardDirectories()) + { + if (directoryId.StartsWith(standardDirectory.Id.Id, StringComparison.Ordinal)) + { + this.Messaging.Write(WarningMessages.StandardDirectoryConflictInMergeModule(symbol.SourceLineNumbers, directoryId, standardDirectory.Id.Id)); + } + } + } + } + } + + private void AddDuplicateFileSymbol(DuplicateFileSymbol symbol) + { + var name = symbol.DestinationName; + if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.DestinationShortName = this.CreateShortName(name, true, "CopyFile", symbol.ComponentRef, symbol.FileRef); + } + + var row = this.CreateRow(symbol, "DuplicateFile"); + row[0] = symbol.Id.Id; + row[1] = symbol.ComponentRef; + row[2] = symbol.FileRef; + row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName); + row[4] = symbol.DestinationFolder; + } + + private void AddEnvironmentSymbol(EnvironmentSymbol symbol) + { + var action = String.Empty; + var system = symbol.System ? "*" : String.Empty; + var uninstall = symbol.Permanent ? String.Empty : "-"; + var value = symbol.Value; + + switch (symbol.Action) + { + case EnvironmentActionType.Create: + action = "+"; + break; + case EnvironmentActionType.Set: + action = "="; + break; + case EnvironmentActionType.Remove: + action = "!"; + break; + } + + switch (symbol.Part) + { + case EnvironmentPartType.First: + value = String.Concat(value, symbol.Separator, "[~]"); + break; + case EnvironmentPartType.Last: + value = String.Concat("[~]", symbol.Separator, value); + break; + } + + var row = this.CreateRow(symbol, "Environment"); + row[0] = symbol.Id.Id; + row[1] = String.Concat(action, uninstall, system, symbol.Name); + row[2] = value; + row[3] = symbol.ComponentRef; + } + + private void AddErrorSymbol(ErrorSymbol symbol) + { + var row = this.CreateRow(symbol, "Error"); + row[0] = Convert.ToInt32(symbol.Id.Id); + row[1] = symbol.Message; + } + + private void AddFeatureSymbol(FeatureSymbol symbol) + { + var attributes = symbol.DisallowAbsent ? WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent : 0; + attributes |= symbol.DisallowAdvertise ? WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise : 0; + attributes |= FeatureInstallDefault.FollowParent == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFollowParent : 0; + attributes |= FeatureInstallDefault.Source == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorSource : 0; + attributes |= FeatureTypicalDefault.Advertise == symbol.TypicalDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise : 0; + + var row = this.CreateRow(symbol, "Feature"); + row[0] = symbol.Id.Id; + row[1] = symbol.ParentFeatureRef; + row[2] = symbol.Title; + row[3] = symbol.Description; + row[4] = symbol.Display; + row[5] = symbol.Level; + row[6] = symbol.DirectoryRef; + row[7] = attributes; + } + + private void AddFileSymbol(FileSymbol symbol) + { + var name = symbol.Name; + if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.ShortName = this.CreateShortName(name, true, "File", symbol.DirectoryRef); + + if (!this.GeneratedShortNames.TryGetValue(symbol.ShortName, out var potentialConflicts)) + { + potentialConflicts = new List(); + this.GeneratedShortNames.Add(symbol.ShortName, potentialConflicts); + } + + potentialConflicts.Add(symbol); + } + + var row = (FileRow)this.CreateRow(symbol, "File"); + row.File = symbol.Id.Id; + row.Component = symbol.ComponentRef; + row.FileName = CreateMsiFilename(symbol.ShortName, name); + row.FileSize = symbol.FileSize; + row.Version = symbol.Version; + row.Language = symbol.Language; + row.DiskId = symbol.DiskId ?? 1; // TODO: is 1 the correct thing to default here + row.Sequence = symbol.Sequence; + row.Source = symbol.Source.Path; + + var attributes = (symbol.Attributes & FileSymbolAttributes.Checksum) == FileSymbolAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed ? WindowsInstallerConstants.MsidbFileAttributesNoncompressed : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.Hidden) == FileSymbolAttributes.Hidden ? WindowsInstallerConstants.MsidbFileAttributesHidden : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.ReadOnly) == FileSymbolAttributes.ReadOnly ? WindowsInstallerConstants.MsidbFileAttributesReadOnly : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.System) == FileSymbolAttributes.System ? WindowsInstallerConstants.MsidbFileAttributesSystem : 0; + attributes |= (symbol.Attributes & FileSymbolAttributes.Vital) == FileSymbolAttributes.Vital ? WindowsInstallerConstants.MsidbFileAttributesVital : 0; + row.Attributes = attributes; + + if (symbol.FontTitle != null) + { + var fontRow = this.CreateRow(symbol, "Font"); + fontRow[0] = symbol.Id.Id; + fontRow[1] = symbol.FontTitle; + } + + if (symbol.SelfRegCost.HasValue) + { + var selfRegRow = this.CreateRow(symbol, "SelfReg"); + selfRegRow[0] = symbol.Id.Id; + selfRegRow[1] = symbol.SelfRegCost.Value; + } + } + + private void AddIniFileSymbol(IniFileSymbol symbol) + { + var tableName = (IniFileActionType.AddLine == symbol.Action || IniFileActionType.AddTag == symbol.Action || IniFileActionType.CreateLine == symbol.Action) ? "IniFile" : "RemoveIniFile"; + + var name = symbol.FileName; + if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.ShortFileName = this.CreateShortName(name, true, "IniFile", symbol.ComponentRef); + } + + var row = this.CreateRow(symbol, tableName); + row[0] = symbol.Id.Id; + row[1] = CreateMsiFilename(symbol.ShortFileName, name); + row[2] = symbol.DirProperty; + row[3] = symbol.Section; + row[4] = symbol.Key; + row[5] = symbol.Value; + row[6] = symbol.Action; + row[7] = symbol.ComponentRef; + } + + private void AddIniLocatorSymbol(IniLocatorSymbol symbol) + { + var name = symbol.FileName; + if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.ShortFileName = this.CreateShortName(name, true, "IniFileSearch"); + } + + var row = this.CreateRow(symbol, "IniLocator"); + row[0] = symbol.Id.Id; + row[1] = CreateMsiFilename(symbol.ShortFileName, name); + row[2] = symbol.Section; + row[3] = symbol.Key; + row[4] = symbol.Field; + row[5] = symbol.Type; + } + + private void AddMediaSymbol(MediaSymbol symbol) + { + if (this.Section.Type != SectionType.Module) + { + var row = (MediaRow)this.CreateRow(symbol, "Media"); + row.DiskId = symbol.DiskId; + row.LastSequence = symbol.LastSequence ?? 0; + row.DiskPrompt = symbol.DiskPrompt; + row.Cabinet = symbol.Cabinet; + row.VolumeLabel = symbol.VolumeLabel; + row.Source = symbol.Source; + } + } + + private void AddModuleConfigurationSymbol(ModuleConfigurationSymbol symbol) + { + var row = this.CreateRow(symbol, "ModuleConfiguration"); + row[0] = symbol.Id.Id; + row[1] = symbol.Format; + row[2] = symbol.Type; + row[3] = symbol.ContextData; + row[4] = symbol.DefaultValue; + row[5] = (symbol.KeyNoOrphan ? WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan : 0) | + (symbol.NonNullable ? WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable : 0); + row[6] = symbol.DisplayName; + row[7] = symbol.Description; + row[8] = symbol.HelpLocation; + row[9] = symbol.HelpKeyword; + } + + private void AddMsiEmbeddedUISymbol(MsiEmbeddedUISymbol symbol) + { + var attributes = symbol.EntryPoint ? WindowsInstallerConstants.MsidbEmbeddedUI : 0; + attributes |= symbol.SupportsBasicUI ? WindowsInstallerConstants.MsidbEmbeddedHandlesBasic : 0; + + var row = this.CreateRow(symbol, "MsiEmbeddedUI"); + row[0] = symbol.Id.Id; + row[1] = symbol.FileName; + row[2] = attributes; + row[3] = symbol.MessageFilter; + row[4] = symbol.Source; + } + + private void AddMsiServiceConfigSymbol(MsiServiceConfigSymbol symbol) + { + var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; + events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; + events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; + + var row = this.CreateRow(symbol, "MsiServiceConfigFailureActions"); + row[0] = symbol.Id.Id; + row[1] = symbol.Name; + row[2] = events; + row[3] = symbol.ConfigType; + row[4] = symbol.Argument; + row[5] = symbol.ComponentRef; + } + + private void AddMsiServiceConfigFailureActionsSymbol(MsiServiceConfigFailureActionsSymbol symbol) + { + var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0; + events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0; + events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0; + + var row = this.CreateRow(symbol, "MsiServiceConfig"); + row[0] = symbol.Id.Id; + row[1] = symbol.Name; + row[2] = events; + row[3] = symbol.ResetPeriod.HasValue ? symbol.ResetPeriod : null; + row[4] = symbol.RebootMessage ?? "[~]"; + row[5] = symbol.Command ?? "[~]"; + row[6] = symbol.Actions; + row[7] = symbol.DelayActions; + row[8] = symbol.ComponentRef; + } + + private void AddMoveFileSymbol(MoveFileSymbol symbol) + { + var name = symbol.DestinationName; + if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.DestinationShortName = this.CreateShortName(name, true, "MoveFile", symbol.ComponentRef); + } + + var row = this.CreateRow(symbol, "MoveFile"); + row[0] = symbol.Id.Id; + row[1] = symbol.ComponentRef; + row[2] = symbol.SourceName; + row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName); + row[4] = symbol.SourceFolder; + row[5] = symbol.DestFolder; + row[6] = symbol.Delete ? WindowsInstallerConstants.MsidbMoveFileOptionsMove : 0; + } + + private void AddPropertySymbol(PropertySymbol symbol) + { + if (String.IsNullOrEmpty(symbol.Value)) + { + return; + } + + var row = (PropertyRow)this.CreateRow(symbol, "Property"); + row.Property = symbol.Id.Id; + row.Value = symbol.Value; + } + + private void AddRemoveFileSymbol(RemoveFileSymbol symbol) + { + var name = symbol.FileName; + if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.ShortFileName = this.CreateShortName(name, true, "RemoveFile", symbol.ComponentRef); + } + + var installMode = symbol.OnInstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall : 0; + installMode |= symbol.OnUninstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove : 0; + + var row = this.CreateRow(symbol, "RemoveFile"); + row[0] = symbol.Id.Id; + row[1] = symbol.ComponentRef; + row[2] = CreateMsiFilename(symbol.ShortFileName, symbol.FileName); + row[3] = symbol.DirPropertyRef; + row[4] = installMode; + } + + private void AddRegistrySymbol(RegistrySymbol symbol) + { + var value = symbol.Value; + + switch (symbol.ValueType) + { + case RegistryValueType.Binary: + value = String.Concat("#x", value); + break; + case RegistryValueType.Expandable: + value = String.Concat("#%", value); + break; + case RegistryValueType.Integer: + value = String.Concat("#", value); + break; + case RegistryValueType.MultiString: + switch (symbol.ValueAction) + { + case RegistryValueActionType.Append: + value = String.Concat("[~]", value); + break; + case RegistryValueActionType.Prepend: + value = String.Concat(value, "[~]"); + break; + case RegistryValueActionType.Write: + default: + if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) + { + value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); + } + break; + } + break; + case RegistryValueType.String: + // escape the leading '#' character for string registry keys + if (null != value && value.StartsWith("#", StringComparison.Ordinal)) + { + value = String.Concat("#", value); + } + break; + } + + var row = this.CreateRow(symbol, "Registry"); + row[0] = symbol.Id.Id; + row[1] = symbol.Root; + row[2] = symbol.Key; + row[3] = symbol.Name; + row[4] = value; + row[5] = symbol.ComponentRef; + } + + private void AddRegLocatorSymbol(RegLocatorSymbol symbol) + { + var type = (int)symbol.Type; + type |= symbol.Win64 ? WindowsInstallerConstants.MsidbLocatorType64bit : 0; + + var row = this.CreateRow(symbol, "RegLocator"); + row[0] = symbol.Id.Id; + row[1] = symbol.Root; + row[2] = symbol.Key; + row[3] = symbol.Name; + row[4] = type; + } + + private void AddRemoveRegistrySymbol(RemoveRegistrySymbol symbol) + { + if (symbol.Action == RemoveRegistryActionType.RemoveOnInstall) + { + var row = this.CreateRow(symbol, "RemoveRegistry"); + row[0] = symbol.Id.Id; + row[1] = symbol.Root; + row[2] = symbol.Key; + row[3] = symbol.Name; + row[4] = symbol.ComponentRef; + } + else // Registry table is used to remove registry keys on uninstall. + { + var row = this.CreateRow(symbol, "Registry"); + row[0] = symbol.Id.Id; + row[1] = symbol.Root; + row[2] = symbol.Key; + row[3] = symbol.Name; + row[5] = symbol.ComponentRef; + } + } + + private void AddServiceControlSymbol(ServiceControlSymbol symbol) + { + var events = symbol.InstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventDelete : 0; + events |= symbol.UninstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete : 0; + events |= symbol.InstallStart ? WindowsInstallerConstants.MsidbServiceControlEventStart : 0; + events |= symbol.UninstallStart ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStart : 0; + events |= symbol.InstallStop ? WindowsInstallerConstants.MsidbServiceControlEventStop : 0; + events |= symbol.UninstallStop ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStop : 0; + + var row = this.CreateRow(symbol, "ServiceControl"); + row[0] = symbol.Id.Id; + row[1] = symbol.Name; + row[2] = events; + row[3] = symbol.Arguments; + if (symbol.Wait.HasValue) + { + row[4] = symbol.Wait.Value ? 1 : 0; + } + row[5] = symbol.ComponentRef; + } + + private void AddServiceInstallSymbol(ServiceInstallSymbol symbol) + { + var errorControl = (int)symbol.ErrorControl; + errorControl |= symbol.Vital ? WindowsInstallerConstants.MsidbServiceInstallErrorControlVital : 0; + + var serviceType = (int)symbol.ServiceType; + serviceType |= symbol.Interactive ? WindowsInstallerConstants.MsidbServiceInstallInteractive : 0; + + var row = this.CreateRow(symbol, "ServiceInstall"); + row[0] = symbol.Id.Id; + row[1] = symbol.Name; + row[2] = symbol.DisplayName; + row[3] = serviceType; + row[4] = (int)symbol.StartType; + row[5] = errorControl; + row[6] = symbol.LoadOrderGroup; + row[7] = symbol.Dependencies; + row[8] = symbol.StartName; + row[9] = symbol.Password; + row[10] = symbol.Arguments; + row[11] = symbol.ComponentRef; + row[12] = symbol.Description; + } + + private void AddShortcutSymbol(ShortcutSymbol symbol) + { + var name = symbol.Name; + if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false)) + { + symbol.ShortName = this.CreateShortName(name, true, "Shortcut", symbol.ComponentRef, symbol.DirectoryRef); + } + + var row = this.CreateRow(symbol, "Shortcut"); + row[0] = symbol.Id.Id; + row[1] = symbol.DirectoryRef; + row[2] = CreateMsiFilename(symbol.ShortName, name); + row[3] = symbol.ComponentRef; + row[4] = symbol.Target; + row[5] = symbol.Arguments; + row[6] = symbol.Description; + row[7] = symbol.Hotkey; + row[8] = symbol.IconRef; + row[9] = symbol.IconIndex; + row[10] = (int?)symbol.Show; + row[11] = symbol.WorkingDirectory; + row[12] = symbol.DisplayResourceDll; + row[13] = symbol.DisplayResourceId; + row[14] = symbol.DescriptionResourceDll; + row[15] = symbol.DescriptionResourceId; + } + + private void AddTextStyleSymbol(TextStyleSymbol symbol) + { + var styleBits = symbol.Bold ? WindowsInstallerConstants.MsidbTextStyleStyleBitsBold : 0; + styleBits |= symbol.Italic ? WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic : 0; + styleBits |= symbol.Strike ? WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike : 0; + styleBits |= symbol.Underline ? WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline : 0; + + long? color = null; + + if (symbol.Red.HasValue || symbol.Green.HasValue || symbol.Blue.HasValue) + { + color = symbol.Red ?? 0; + color += (long)(symbol.Green ?? 0) * 256; + color += (long)(symbol.Blue ?? 0) * 65536; + } + + var row = this.CreateRow(symbol, "TextStyle"); + row[0] = symbol.Id.Id; + row[1] = symbol.FaceName; + row[2] = symbol.Size; + row[3] = color; + row[4] = styleBits == 0 ? null : (int?)styleBits; + } + + private void AddUpgradeSymbol(UpgradeSymbol symbol) + { + var row = (UpgradeRow)this.CreateRow(symbol, "Upgrade"); + row.UpgradeCode = symbol.UpgradeCode; + row.VersionMin = symbol.VersionMin; + row.VersionMax = symbol.VersionMax; + row.Language = symbol.Language; + row.Remove = symbol.Remove; + row.ActionProperty = symbol.ActionProperty; + + var attributes = symbol.MigrateFeatures ? WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures : 0; + attributes |= symbol.OnlyDetect ? WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect : 0; + attributes |= symbol.IgnoreRemoveFailures ? WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure : 0; + attributes |= symbol.VersionMinInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive : 0; + attributes |= symbol.VersionMaxInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive : 0; + attributes |= symbol.ExcludeLanguages ? WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive : 0; + row.Attributes = attributes; + } + + private void AddWixActionSymbol(WixActionSymbol symbol) + { + // Get the table definition for the action (and ensure the proper table exists for a module). + string sequenceTableName = null; + switch (symbol.SequenceTable) + { + case SequenceTable.AdminExecuteSequence: + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]); + sequenceTableName = "ModuleAdminExecuteSequence"; + } + else + { + sequenceTableName = "AdminExecuteSequence"; + } + break; + case SequenceTable.AdminUISequence: + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]); + sequenceTableName = "ModuleAdminUISequence"; + } + else + { + sequenceTableName = "AdminUISequence"; + } + break; + case SequenceTable.AdvertiseExecuteSequence: + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]); + sequenceTableName = "ModuleAdvtExecuteSequence"; + } + else + { + sequenceTableName = "AdvtExecuteSequence"; + } + break; + case SequenceTable.InstallExecuteSequence: + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]); + sequenceTableName = "ModuleInstallExecuteSequence"; + } + else + { + sequenceTableName = "InstallExecuteSequence"; + } + break; + case SequenceTable.InstallUISequence: + if (OutputType.Module == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]); + sequenceTableName = "ModuleInstallUISequence"; + } + else + { + sequenceTableName = "InstallUISequence"; + } + break; + } + + // create the action sequence row in the output + var row = this.CreateRow(symbol, sequenceTableName); + + if (SectionType.Module == this.Section.Type) + { + row[0] = symbol.Action; + if (0 != symbol.Sequence) + { + row[1] = symbol.Sequence; + } + else + { + var after = (null == symbol.Before); + row[2] = after ? symbol.After : symbol.Before; + row[3] = after ? 1 : 0; + } + row[4] = symbol.Condition; + } + else + { + row[0] = symbol.Action; + row[1] = symbol.Condition; + row[2] = symbol.Sequence; + } + } + + private void IndexCustomTableCellSymbol(WixCustomTableCellSymbol wixCustomTableCellSymbol, Dictionary> cellsByTableAndRowId) + { + var tableAndRowId = wixCustomTableCellSymbol.TableRef + "/" + wixCustomTableCellSymbol.RowId; + if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells)) + { + cells = new List(); + cellsByTableAndRowId.Add(tableAndRowId, cells); + } + + cells.Add(wixCustomTableCellSymbol); + } + + private void AddIndexedCellSymbols(Dictionary> cellsByTableAndRowId) + { + foreach (var rowOfCells in cellsByTableAndRowId.Values) + { + var firstCellSymbol = rowOfCells[0]; + var customTableDefinition = this.TableDefinitions[firstCellSymbol.TableRef]; + + if (customTableDefinition.Unreal) + { + continue; + } + + var customRow = this.CreateRow(firstCellSymbol, customTableDefinition); + var customRowFieldsByColumnName = customRow.Fields.ToDictionary(f => f.Column.Name); + +#if TODO // SectionId seems like a good thing to preserve. + customRow.SectionId = symbol.SectionId; +#endif + foreach (var cell in rowOfCells) + { + var data = cell.Data; + + if (customRowFieldsByColumnName.TryGetValue(cell.ColumnRef, out var rowField)) + { + if (!String.IsNullOrEmpty(data)) + { + if (rowField.Column.Type == ColumnType.Number) + { + try + { + rowField.Data = Convert.ToInt32(data, CultureInfo.InvariantCulture); + } + catch (FormatException) + { + this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); + } + catch (OverflowException) + { + this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); + } + } + else if (rowField.Column.Category == ColumnCategory.Identifier) + { + if (this.BackendHelper.IsValidIdentifier(data) || this.BackendHelper.IsValidBinderVariable(data) || ColumnCategory.Formatted == rowField.Column.Category) + { + rowField.Data = data; + } + else + { + this.Messaging.Write(ErrorMessages.IllegalIdentifier(cell.SourceLineNumbers, "Data", data)); + } + } + else + { + rowField.Data = data; + } + } + } + else + { + this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(cell.SourceLineNumbers, cell.ColumnRef)); + } + } + + for (var i = 0; i < customTableDefinition.Columns.Length; ++i) + { + if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) + { + this.Messaging.Write(ErrorMessages.NoDataForColumn(firstCellSymbol.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); + } + } + } + } + + private void AddWixEnsureTableSymbol(WixEnsureTableSymbol symbol) + { + var tableDefinition = this.TableDefinitions[symbol.Table]; + this.Data.EnsureTable(tableDefinition); + } + + private void AddWixPackageSymbol(WixPackageSymbol symbol) + { + // TODO: Remove the following from the compiler and do it here instead. + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); + //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); + //if (null != upgradeCode) + //{ + // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); + //} + + //if (isPerMachine) + //{ + // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); + //} + } + + private bool AddSymbolFromExtension(IntermediateSymbol symbol) + { + foreach (var extension in this.BackendExtensions) + { + if (extension.TryProcessSymbol(this.Section, symbol, this.Data, this.TableDefinitions)) + { + return true; + } + } + + return false; + } + + private bool AddSymbolDefaultly(IntermediateSymbol symbol) => + this.BackendHelper.TryAddSymbolToMatchingTableDefinitions(this.Section, symbol, this.Data, this.TableDefinitions); + + private void EnsureModuleIgnoredTable(IntermediateSymbol symbol, string ignoredTable) + { + var tableDefinition = this.TableDefinitions["ModuleIgnoreTable"]; + var table = this.Data.EnsureTable(tableDefinition); + if (!table.Rows.Any(r => r.FieldAsString(0) == ignoredTable)) + { + var row = this.CreateRow(symbol, tableDefinition); + row[0] = ignoredTable; + } + } + + private (string, string) AddDirectorySubdirectories(DirectorySymbol symbol) + { + var directory = symbol.Name.Trim(PathSeparatorChars); + var parentDir = symbol.ParentDirectoryRef ?? (symbol.Id.Id == "TARGETDIR" ? null : "TARGETDIR"); + + var start = 0; + var end = directory.IndexOfAny(PathSeparatorChars); + var path = String.Empty; + + while (start <= end) + { + var subdirectoryName = directory.Substring(start, end - start); + + if (!String.IsNullOrEmpty(subdirectoryName)) + { + path = Path.Combine(path, subdirectoryName); + + var id = this.BackendHelper.GenerateIdentifier("d", symbol.ParentDirectoryRef, path); + var shortnameSubdirectory = this.BackendHelper.IsValidShortFilename(subdirectoryName, false) ? null : this.CreateShortName(subdirectoryName, false, "Directory", symbol.ParentDirectoryRef); + + var subdirectoryRow = this.CreateRow(symbol, "Directory"); + subdirectoryRow[0] = id; + subdirectoryRow[1] = parentDir; + subdirectoryRow[2] = CreateMsiFilename(shortnameSubdirectory, subdirectoryName); + + parentDir = id; + } + + start = end + 1; + end = symbol.Name.IndexOfAny(PathSeparatorChars, start); + } + + var name = (start == 0) ? directory : directory.Substring(start); + + return (name, parentDir); + } + + private void EnsureRequiredTables() + { + // check for missing table and add them or display an error as appropriate + switch (this.Data.Type) + { + case OutputType.Module: + this.Data.EnsureTable(this.TableDefinitions["Component"]); + this.Data.EnsureTable(this.TableDefinitions["Directory"]); + this.Data.EnsureTable(this.TableDefinitions["FeatureComponents"]); + this.Data.EnsureTable(this.TableDefinitions["File"]); + this.Data.EnsureTable(this.TableDefinitions["ModuleComponents"]); + this.Data.EnsureTable(this.TableDefinitions["ModuleSignature"]); + break; + + case OutputType.PatchCreation: + var imageFamiliesCount = this.Data.Tables["ImageFamilies"]?.Rows.Count ?? 0; + var targetImagesCount = this.Data.Tables["TargetImages"]?.Rows.Count ?? 0; + var upgradedImagesCount = this.Data.Tables["UpgradedImages"]?.Rows.Count ?? 0; + + if (imageFamiliesCount < 1) + { + this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("ImageFamilies")); + } + + if (targetImagesCount < 1) + { + this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("TargetImages")); + } + + if (upgradedImagesCount < 1) + { + this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("UpgradedImages")); + } + + this.Data.EnsureTable(this.TableDefinitions["Properties"]); + break; + + case OutputType.Product: + this.Data.EnsureTable(this.TableDefinitions["File"]); + this.Data.EnsureTable(this.TableDefinitions["Media"]); + break; + } + } + + private void ReportGeneratedShortFileNameConflicts() + { + foreach (var conflicts in this.GeneratedShortNames.Values.Where(l => l.Count > 1)) + { + this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict(conflicts[0].SourceLineNumbers, conflicts[0].ShortName)); + for (var i = 1; i < conflicts.Count; ++i) + { + this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict2(conflicts[i].SourceLineNumbers)); + } + } + } + + private void ReportIllegalTables() + { + foreach (var table in this.Data.Tables) + { + switch (this.Data.Type) + { + case OutputType.Module: + if ("BBControl" == table.Name || + "Billboard" == table.Name || + "CCPSearch" == table.Name || + "Feature" == table.Name || + "LaunchCondition" == table.Name || + "Media" == table.Name || + "Patch" == table.Name || + "Upgrade" == table.Name || + "WixMerge" == table.Name) + { + foreach (Row row in table.Rows) + { + this.Messaging.Write(ErrorMessages.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name)); + } + } + else if ("Error" == table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(WarningMessages.DangerousTableInMergeModule(row.SourceLineNumbers, table.Name)); + } + } + break; + + case OutputType.PatchCreation: + if (!table.Definition.Unreal && + "_SummaryInformation" != table.Name && + "ExternalFiles" != table.Name && + "FamilyFileRanges" != table.Name && + "ImageFamilies" != table.Name && + "PatchMetadata" != table.Name && + "PatchSequence" != table.Name && + "Properties" != table.Name && + "TargetFiles_OptionalData" != table.Name && + "TargetImages" != table.Name && + "UpgradedFiles_OptionalData" != table.Name && + "UpgradedFilesToIgnore" != table.Name && + "UpgradedImages" != table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(ErrorMessages.UnexpectedTableInPatchCreationPackage(row.SourceLineNumbers, table.Name)); + } + } + break; + + case OutputType.Patch: + if (!table.Definition.Unreal && + "_SummaryInformation" != table.Name && + "Media" != table.Name && + "MsiFileHash" != table.Name && + "MsiPatchMetadata" != table.Name && + "MsiPatchSequence" != table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(ErrorMessages.UnexpectedTableInPatch(row.SourceLineNumbers, table.Name)); + } + } + break; + + case OutputType.Product: + if ("ModuleAdminExecuteSequence" == table.Name || + "ModuleAdminUISequence" == table.Name || + "ModuleAdvtExecuteSequence" == table.Name || + "ModuleAdvtUISequence" == table.Name || + "ModuleComponents" == table.Name || + "ModuleConfiguration" == table.Name || + "ModuleDependency" == table.Name || + "ModuleExclusion" == table.Name || + "ModuleIgnoreTable" == table.Name || + "ModuleInstallExecuteSequence" == table.Name || + "ModuleInstallUISequence" == table.Name || + "ModuleSignature" == table.Name || + "ModuleSubstitution" == table.Name) + { + foreach (var row in table.Rows) + { + this.Messaging.Write(WarningMessages.UnexpectedTableInProduct(row.SourceLineNumbers, table.Name)); + } + } + break; + } + } + } + + private void ReportMismatchedModularizations() + { + // verify that modularization types match for foreign key relationships + foreach (var tableDefinition in this.TableDefinitions) + { + foreach (var columnDefinition in tableDefinition.Columns) + { + if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.KeyColumn.HasValue) + { + if (this.TableDefinitions.TryGet(columnDefinition.KeyTable, out var keyTableDefinition)) + { + var keyColumnIndex = columnDefinition.KeyColumn ?? -1; + + if (keyColumnIndex <= 0 || keyColumnIndex > keyTableDefinition.Columns.Length) + { + this.Messaging.Write(ErrorMessages.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex)); + } + else if (keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType) + { + this.Messaging.Write(WarningMessages.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType.ToString())); + } + } + // else - ignore missing table definitions as that error is caught in other places + } + } + } + } + + private void ReportWindowsInstallerDataInconsistencies() + { + // Get the output's minimum installer version + var outputInstallerVersion = Int32.MaxValue; + + if (this.Data.Tables.TryGetTable("_SummaryInformation", out var summaryInformationTable)) + { + outputInstallerVersion = summaryInformationTable.Rows.FirstOrDefault(r => 14 == r.FieldAsInteger(0))?.FieldAsInteger(1) ?? Int32.MaxValue; + } + + // Ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40). + if (outputInstallerVersion <= 100 && OutputType.Product == this.Data.Type) + { + this.Data.EnsureTable(this.TableDefinitions["Error"]); + } + + // Check for the presence of tables/rows/columns that require MSI 1.1 or later. + if (outputInstallerVersion < 110) + { + if (this.Data.Tables.TryGetTable("IsolatedComponent", out var isolatedComponentTable)) + { + foreach (var row in isolatedComponentTable.Rows) + { + this.Messaging.Write(WarningMessages.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion)); + } + } + } + + // Check for the presence of tables/rows/columns that require MSI 4.0 or later + if (outputInstallerVersion < 400) + { + if (this.Data.Tables.TryGetTable("Shortcut", out var shortcutTable)) + { + foreach (var row in shortcutTable.Rows) + { + if (null != row[12] || null != row[13] || null != row[14] || null != row[15]) + { + this.Messaging.Write(WarningMessages.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion)); + } + } + } + } + } + + private static OutputType SectionTypeToOutputType(SectionType type) + { + switch (type) + { + case SectionType.Bundle: + return OutputType.Bundle; + case SectionType.Module: + return OutputType.Module; + case SectionType.Product: + return OutputType.Product; + case SectionType.PatchCreation: + return OutputType.PatchCreation; + case SectionType.Patch: + return OutputType.Patch; + + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + } + + private Row CreateRow(IntermediateSymbol symbol, string tableDefinitionName) => + this.CreateRow(symbol, this.TableDefinitions[tableDefinitionName]); + + private Row CreateRow(IntermediateSymbol symbol, TableDefinition tableDefinition) => + this.BackendHelper.CreateRow(this.Section, symbol, this.Data, tableDefinition); + + + private string CreateShortName(string longName, bool keepExtension, params string[] args) + { + longName = longName.ToLowerInvariant(); + + // collect all the data + var strings = new List(1 + args.Length); + strings.Add(longName); + strings.AddRange(args); + + // prepare for hashing + var stringData = String.Join("|", strings); + var data = Encoding.UTF8.GetBytes(stringData); + + // hash the data + byte[] hash; + using (var sha1 = new SHA1CryptoServiceProvider()) + { + hash = sha1.ComputeHash(data); + } + + // generate the short file/directory name without an extension + var shortName = new StringBuilder(Convert.ToBase64String(hash)); + shortName.Length = 8; + shortName.Replace('+', '-').Replace('/', '_'); + + if (keepExtension) + { + var extension = Path.GetExtension(longName); + + if (4 < extension.Length) + { + extension = extension.Substring(0, 4); + } + + shortName.Append(extension); + + // check the generated short name to ensure its still legal (the extension may not be legal) + if (!this.BackendHelper.IsValidShortFilename(shortName.ToString(), false)) + { + // remove the extension (by truncating the generated file name back to the generated characters) + shortName.Length -= extension.Length; + } + } + + return shortName.ToString().ToLowerInvariant(); + } + + private static string CreateMsiFilename(string shortName, string longName) + { + if (String.IsNullOrEmpty(shortName) || String.Equals(shortName, longName, StringComparison.OrdinalIgnoreCase)) + { + return longName; + } + else + { + return shortName + "|" + longName; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs new file mode 100644 index 00000000..7c1e085c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs @@ -0,0 +1,221 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using WixToolset.Core.Native; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.Native.Msm; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Retrieve files information and extract them from merge modules. + /// + internal class ExtractMergeModuleFilesCommand + { + public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable wixMergeSymbols, IEnumerable fileFacades, int installerVersion, string intermediateFolder, bool suppressLayout) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.WixMergeSymbols = wixMergeSymbols; + this.FileFacades = fileFacades; + this.OutputInstallerVersion = installerVersion; + this.IntermediateFolder = intermediateFolder; + this.SuppressLayout = suppressLayout; + } + + private IMessaging Messaging { get; } + + private IWindowsInstallerBackendHelper BackendHelper { get; } + + private IEnumerable WixMergeSymbols { get; } + + private IEnumerable FileFacades { get; } + + private int OutputInstallerVersion { get; } + + private string IntermediateFolder { get; } + + private bool SuppressLayout { get; } + + public IEnumerable MergeModulesFileFacades { get; private set; } + + public void Execute() + { + var mergeModulesFileFacades = new List(); + + var merge = MsmInterop.GetMsmMerge(); + + // Index all of the file rows to be able to detect collisions with files in the Merge Modules. + // It may seem a bit expensive to build up this index solely for the purpose of checking collisions + // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out + // there are other cases where we need all the file rows indexed, however they are not common cases. + // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let + // this case be slightly more expensive because the cost of maintaining an indexed file row collection + // is a lot more costly for the common cases. + var indexedFileFacades = this.FileFacades.ToDictionary(f => f.Id, StringComparer.Ordinal); + + foreach (var wixMergeRow in this.WixMergeSymbols) + { + var containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); + + // If the module has files and creating layout + if (containsFiles && !this.SuppressLayout) + { + this.ExtractFilesFromMergeModule(merge, wixMergeRow); + } + } + + this.MergeModulesFileFacades = mergeModulesFileFacades; + } + + private bool CreateFacadesForMergeModuleFiles(WixMergeSymbol wixMergeRow, List mergeModulesFileFacades, Dictionary indexedFileFacades) + { + var containsFiles = false; + + try + { + // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. + using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) + { + if (db.TableExists("File") && db.TableExists("Component")) + { + var uniqueModuleFileIdentifiers = new Dictionary(StringComparer.OrdinalIgnoreCase); + + using (var view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) + { + // add each file row from the merge module into the file row collection (check for errors along the way) + foreach (var record in view.Records) + { + // NOTE: this is very tricky - the merge module file rows are not added to the + // file table because they should not be created via idt import. Instead, these + // rows are created by merging in the actual modules. + var fileSymbol = new FileSymbol(wixMergeRow.SourceLineNumbers, new Identifier(AccessModifier.Section, record[1])); + fileSymbol.Attributes = wixMergeRow.FileAttributes; + fileSymbol.DirectoryRef = record[2]; + fileSymbol.DiskId = wixMergeRow.DiskId; + fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) }; + + var mergeModuleFileFacade = this.BackendHelper.CreateFileFacadeFromMergeModule(fileSymbol); + + // If case-sensitive collision with another merge module or a user-authored file identifier. + if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade)) + { + this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.Id)); + } + else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module + { + this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.Id, collidingFacade.Id)); + } + else // no collision + { + mergeModulesFileFacades.Add(mergeModuleFileFacade); + + // Keep updating the indexes as new rows are added. + indexedFileFacades.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); + uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade); + } + + containsFiles = true; + } + } + } + + // Get the summary information to detect the Schema + using (var summaryInformation = new SummaryInformation(db)) + { + var moduleInstallerVersionString = summaryInformation.GetProperty(14); + + try + { + var moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); + if (moduleInstallerVersion > this.OutputInstallerVersion) + { + this.Messaging.Write(WarningMessages.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleInstallerVersion, this.OutputInstallerVersion)); + } + } + catch (FormatException) + { + throw new WixException(ErrorMessages.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); + } + } + } + } + catch (FileNotFoundException) + { + throw new WixException(ErrorMessages.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); + } + catch (Win32Exception) + { + throw new WixException(ErrorMessages.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile)); + } + + return containsFiles; + } + + private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeSymbol wixMergeRow) + { + var moduleOpen = false; + short mergeLanguage; + + var mergeId = wixMergeRow.Id.Id; + + try + { + mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); + } + catch (FormatException) + { + this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, mergeId, wixMergeRow.Language.ToString())); + return; + } + + try + { + merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); + moduleOpen = true; + + // extract the module cabinet, then explode all of the files to a temp directory + var moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab"); + merge.ExtractCAB(moduleCabPath); + + var mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId); + Directory.CreateDirectory(mergeIdPath); + + try + { + var cabinet = new Cabinet(moduleCabPath); + cabinet.Extract(mergeIdPath); + } + catch (FileNotFoundException) + { + throw new WixException(ErrorMessages.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); + } + catch + { + throw new WixException(ErrorMessages.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); + } + } + catch (COMException ce) + { + throw new WixException(ErrorMessages.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); + } + finally + { + if (moduleOpen) + { + merge.CloseModule(); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs new file mode 100644 index 00000000..fe65ccef --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs @@ -0,0 +1,77 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Extensibility; + + internal class FileSystemManager + { + public FileSystemManager(IEnumerable fileSystemExtensions) + { + this.Extensions = fileSystemExtensions; + } + + private IEnumerable Extensions { get; } + + public bool CompareFiles(string firstPath, string secondPath) + { + foreach (var extension in this.Extensions) + { + var compared = extension.CompareFiles(firstPath, secondPath); + if (compared.HasValue) + { + return compared.Value; + } + } + + return BuiltinCompareFiles(firstPath, secondPath); + } + + private static bool BuiltinCompareFiles(string firstPath, string secondPath) + { + if (String.Equals(firstPath, secondPath, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + using (var firstStream = File.OpenRead(firstPath)) + using (var secondStream = File.OpenRead(secondPath)) + { + if (firstStream.Length != secondStream.Length) + { + return false; + } + + // Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance. + // The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined. + var firstBuffer = new byte[16 * 1024]; + var secondBuffer = new byte[16 * 1024]; + + var firstReadLength = 0; + do + { + firstReadLength = firstStream.Read(firstBuffer, 0, firstBuffer.Length); + var secondReadLength = secondStream.Read(secondBuffer, 0, secondBuffer.Length); + + if (firstReadLength != secondReadLength) + { + return false; + } + + for (var i = 0; i < firstReadLength; ++i) + { + if (firstBuffer[i] != secondBuffer[i]) + { + return false; + } + } + } while (0 < firstReadLength); + } + + return true; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs new file mode 100644 index 00000000..3cdc0c28 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs @@ -0,0 +1,262 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Set the guids for components with generatable guids and validate all are appropriately unique. + /// + internal class FinalizeComponentGuids + { + internal FinalizeComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform) + { + this.Messaging = messaging; + this.BackendHelper = helper; + this.PathResolver = pathResolver; + this.Section = section; + this.Platform = platform; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private IntermediateSection Section { get; } + + private Platform Platform { get; } + + private Dictionary ComponentIdGenSeeds { get; set; } + + private ILookup FilesByComponentId { get; set; } + + private Dictionary RegistrySymbolsById { get; set; } + + private Dictionary TargetPathsByDirectoryId { get; set; } + + public void Execute() + { + var componentGuidConditions = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var guidCollisions = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var componentSymbol in this.Section.Symbols.OfType()) + { + if (componentSymbol.ComponentId == "*") + { + this.GenerateComponentGuid(componentSymbol); + } + + // Now check for GUID collisions, but we don't care about unmanaged components and + // if there's a * GUID remaining, there's already an error that explained why it + // was not replaced with a real GUID. + if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && componentSymbol.ComponentId != "*") + { + if (!componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var components)) + { + components = new List(); + componentGuidConditions.Add(componentSymbol.ComponentId, components); + } + + components.Add(componentSymbol); + if (components.Count > 1) + { + guidCollisions.Add(componentSymbol.ComponentId); + } + } + } + + if (guidCollisions.Count > 0) + { + this.ReportGuidCollisions(guidCollisions, componentGuidConditions); + } + } + + private void GenerateComponentGuid(ComponentSymbol componentSymbol) + { + if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType) + { + this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers)); + return; + } + + if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType) + { + if (this.RegistrySymbolsById is null) + { + this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + if (this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol)) + { + var bitness = componentSymbol.Win64 ? "64" : String.Empty; + var regkey = String.Concat(bitness, registrySymbol.Root, "\\", registrySymbol.Key, "\\", registrySymbol.Name); + componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); + } + } + else // must be a File KeyPath. + { + // If the directory table hasn't been loaded into an indexed hash + // of directory ids to target names do that now. + if (this.TargetPathsByDirectoryId is null) + { + this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); + } + + // If the component id generation seeds have not been indexed + // from the Directory symbols do that now. + if (this.ComponentIdGenSeeds is null) + { + // If there are any Directory symbols, build up the Component Guid + // generation seeds indexed by Directory/@Id. + this.ComponentIdGenSeeds = this.Section.Symbols.OfType() + .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) + .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed); + } + + // If the file symbols have not been indexed by File's ComponentRef yet + // then do that now. + if (this.FilesByComponentId is null) + { + this.FilesByComponentId = this.Section.Symbols.OfType().ToLookup(f => f.ComponentRef); + } + + // validate component meets all the conditions to have a generated guid + var currentComponentFiles = this.FilesByComponentId[componentSymbol.Id.Id]; + var numFilesInComponent = currentComponentFiles.Count(); + string path = null; + + foreach (var fileSymbol in currentComponentFiles) + { + if (fileSymbol.Id.Id == componentSymbol.KeyPath) + { + // calculate the key file's canonical target path + var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, this.ComponentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform); + var fileName = this.BackendHelper.GetMsiFileName(fileSymbol.Name, false, true).ToLowerInvariant(); + path = Path.Combine(directoryPath, fileName); + + // find paths that are not canonicalized + if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || + path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || + path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || + path.StartsWith("TARGETDIR", StringComparison.Ordinal) || + path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || + path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) + { + this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileSymbol.ComponentRef, path)); + } + + // if component has more than one file, the key path must be versioned + if (1 < numFilesInComponent && String.IsNullOrEmpty(fileSymbol.Version)) + { + this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.SourceLineNumbers)); + } + } + else + { + // not a key path, so it must be an unversioned file if component has more than one file + if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileSymbol.Version)) + { + this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers)); + } + } + } + + // if the rules were followed, reward with a generated guid + if (!this.Messaging.EncounteredError) + { + componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path); + } + } + } + + private void ReportGuidCollisions(HashSet guidCollisions, Dictionary> componentGuidConditions) + { + Dictionary fileSymbolsById = null; + + foreach (var guid in guidCollisions) + { + var collidingComponents = componentGuidConditions[guid]; + var allComponentsHaveConditions = collidingComponents.All(c => !String.IsNullOrEmpty(c.Condition)); + + foreach (var componentSymbol in collidingComponents) + { + string path; + string type; + + if (componentSymbol.KeyPathType == ComponentKeyPathType.File) + { + if (fileSymbolsById is null) + { + fileSymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + path = fileSymbolsById.TryGetValue(componentSymbol.KeyPath, out var fileSymbol) ? fileSymbol.Source.Path : componentSymbol.KeyPath; + type = "source path"; + } + else if (componentSymbol.KeyPathType == ComponentKeyPathType.Registry) + { + if (this.RegistrySymbolsById is null) + { + this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + path = this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol) ? String.Concat(registrySymbol.Key, "\\", registrySymbol.Name) : componentSymbol.KeyPath; + type = "registry path"; + } + else + { + if (this.TargetPathsByDirectoryId is null) + { + this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); + } + + path = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, componentIdGenSeeds: null, componentSymbol.DirectoryRef, this.Platform); + type = "directory"; + } + + if (allComponentsHaveConditions) + { + this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); + } + } + } + } + + private Dictionary ResolveDirectoryTargetPaths() + { + var directories = this.Section.Symbols.OfType().ToList(); + + var targetPathsByDirectoryId = new Dictionary(directories.Count); + + // Get the target paths for all directories. + foreach (var directory in directories) + { + // If the directory Id already exists, we will skip it here since + // checking for duplicate primary keys is done later when importing tables + // into database + if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) + { + continue; + } + + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); + targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); + } + + return targetPathsByDirectoryId; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs new file mode 100644 index 00000000..b8cca752 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs @@ -0,0 +1,408 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.IO; + using System.Linq; + using System.Text; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class GenerateDatabaseCommand + { + private const string IdtsSubFolder = "_idts"; + + public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.FileSystemManager = fileSystemManager; + this.Data = data; + this.OutputPath = outputPath; + this.TableDefinitions = tableDefinitions; + this.IntermediateFolder = intermediateFolder; + this.KeepAddedColumns = keepAddedColumns; + this.SuppressAddingValidationRows = suppressAddingValidationRows; + this.UseSubDirectory = useSubdirectory; + } + + private IBackendHelper BackendHelper { get; } + + private FileSystemManager FileSystemManager { get; } + + /// + /// Whether to keep columns added in a transform. + /// + private bool KeepAddedColumns { get; } + + private IMessaging Messaging { get; } + + private WindowsInstallerData Data { get; } + + private string OutputPath { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private string IntermediateFolder { get; } + + public List GeneratedTemporaryFiles { get; } = new List(); + + /// + /// Whether to use a subdirectory based on the database file name for intermediate files. + /// + private bool SuppressAddingValidationRows { get; } + + private bool UseSubDirectory { get; } + + public void Execute() + { + // Add the _Validation rows. + if (!this.SuppressAddingValidationRows) + { + this.AddValidationRows(); + } + + var baseDirectory = this.IntermediateFolder; + + if (this.UseSubDirectory) + { + var filename = Path.GetFileNameWithoutExtension(this.OutputPath); + baseDirectory = Path.Combine(baseDirectory, filename); + } + + var idtFolder = Path.Combine(baseDirectory, IdtsSubFolder); + + var type = OpenDatabase.CreateDirect; + + if (OutputType.Patch == this.Data.Type) + { + type |= OpenDatabase.OpenPatchFile; + } + + try + { + Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); + + Directory.CreateDirectory(idtFolder); + + using (var db = new Database(this.OutputPath, type)) + { + // 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 != this.Data.Codepage) + { + this.SetDatabaseCodepage(db, this.Data.Codepage, idtFolder); + } + + this.ImportTables(db, idtFolder); + + // Insert substorages (usually transforms inside a patch or instance transforms in a package). + this.ImportSubStorages(db); + + // We're good, commit the changes to the new database. + db.Commit(); + } + } + catch (IOException e) + { + // TODO: this error message doesn't seem specific enough + throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e); + } + } + + private void AddValidationRows() + { + var validationTable = this.Data.EnsureTable(this.TableDefinitions["_Validation"]); + + // Add the validation rows for real tables and columns. + foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) + { + foreach (var columnDef in table.Definition.Columns.Where(c => !c.Unreal)) + { + var row = validationTable.CreateRow(null); + + row[0] = table.Name; + + row[1] = columnDef.Name; + + if (columnDef.Nullable) + { + row[2] = "Y"; + } + else + { + row[2] = "N"; + } + + if (columnDef.MinValue.HasValue) + { + row[3] = columnDef.MinValue.Value; + } + + if (columnDef.MaxValue.HasValue) + { + row[4] = columnDef.MaxValue.Value; + } + + row[5] = columnDef.KeyTable; + + if (columnDef.KeyColumn.HasValue) + { + row[6] = columnDef.KeyColumn.Value; + } + + if (ColumnCategory.Unknown != columnDef.Category) + { + row[7] = columnDef.Category.ToString(); + } + + row[8] = columnDef.Possibilities; + + row[9] = columnDef.Description; + } + } + } + + private void ImportTables(Database db, string idtDirectory) + { + foreach (var table in this.Data.Tables) + { + var importTable = table; + var hasBinaryColumn = false; + + // Skip all unreal tables other than _Streams. + if (table.Definition.Unreal && "_Streams" != table.Name) + { + continue; + } + + // Do not put the _Validation table in patches, it is not needed. + if (OutputType.Patch == this.Data.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 (var columnDefinition in table.Definition.Columns) + { + if (ColumnType.Object == columnDefinition.Type) + { + importTable = new Table(table.Definition); + hasBinaryColumn = true; + break; + } + } + + // Create the table via IDT import. + if ("_Streams" != importTable.Name) + { + try + { + var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Data.Codepage, idtDirectory, this.KeepAddedColumns); + command.Execute(); + + var trackIdt = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary); + this.GeneratedTemporaryFiles.Add(trackIdt); + + db.Import(command.IdtPath); + } + 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) + { + var query = new StringBuilder("SELECT "); + + // Build the query for the view. + var firstColumn = true; + foreach (var columnDefinition in table.Definition.Columns) + { + if (columnDefinition.Unreal) + { + continue; + } + + if (!firstColumn) + { + query.Append(","); + } + + query.AppendFormat(" `{0}`", columnDefinition.Name); + firstColumn = false; + } + query.AppendFormat(" FROM `{0}`", table.Name); + + using (var tableView = db.OpenExecuteView(query.ToString())) + { + // Import each row containing a stream + foreach (var row in table.Rows) + { + using (var record = new Record(table.Definition.Columns.Length)) + { + // Stream names are created by concatenating the name of the table with the values + // of the primary key (delimited by periods). + var streamName = new StringBuilder(); + + // the _Streams table doesn't prepend the table name (or a period) + if ("_Streams" != table.Name) + { + streamName.Append(table.Name); + } + + var needStream = false; + + for (var i = 0; i < table.Definition.Columns.Length; i++) + { + var columnDefinition = table.Definition.Columns[i]; + + if (columnDefinition.Unreal) + { + continue; + } + + switch (columnDefinition.Type) + { + case ColumnType.Localized: + case ColumnType.Preserved: + case ColumnType.String: + var str = row.FieldAsString(i); + + if (columnDefinition.PrimaryKey) + { + if (0 < streamName.Length) + { + streamName.Append("."); + } + + streamName.Append(str); + } + + record.SetString(i + 1, str); + break; + case ColumnType.Number: + record.SetInteger(i + 1, row.FieldAsInteger(i)); + break; + + case ColumnType.Object: + var path = row.FieldAsString(i); + if (null != path) + { + needStream = true; + try + { + record.SetStream(i + 1, path); + } + catch (Win32Exception e) + { + if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME + { + throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, path)); + } + else + { + throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); + } + } + } + break; + } + } + + // check for a stream name that is more than 62 characters long (the maximum allowed length) + if (needStream && Database.MsiMaxStreamNameLength < streamName.Length) + { + this.Messaging.Write(ErrorMessages.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(); + } + } + } + } + + private void ImportSubStorages(Database db) + { + if (0 < this.Data.SubStorages.Count) + { + using (var storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) + { + foreach (var subStorage in this.Data.SubStorages) + { + var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst")); + + // Bind the transform. + var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions); + command.Execute(); + + if (this.Messaging.EncounteredError) + { + continue; + } + + // Add the storage to the database. + using (var record = new Record(2)) + { + record.SetString(1, subStorage.Name); + record.SetStream(2, transformFile); + storagesView.Modify(ModifyView.Assign, record); + } + } + } + } + } + + private void SetDatabaseCodepage(Database db, int codepage, string idtFolder) + { + // Write out the _ForceCodepage IDT file. + var idtPath = Path.Combine(idtFolder, "_ForceCodepage.idt"); + using (var idtFile = new StreamWriter(idtPath, false, Encoding.ASCII)) + { + idtFile.WriteLine(); // dummy column name record + idtFile.WriteLine(); // dummy column definition record + idtFile.Write(codepage); + idtFile.WriteLine("\t_ForceCodepage"); + } + + var trackIdt = this.BackendHelper.TrackFile(idtPath, TrackedFileType.Temporary); + this.GeneratedTemporaryFiles.Add(trackIdt); + + // Try to import the table into the MSI. + try + { + db.Import(idtPath); + } + catch (WixInvalidIdtException) + { + // The IDT should always be generated correctly, so an invalid code page was given. + throw new WixException(ErrorMessages.IllegalCodepage(codepage)); + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs new file mode 100644 index 00000000..faa03762 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs @@ -0,0 +1,582 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + /// + /// Creates a transform by diffing two outputs. + /// + internal class GenerateTransformCommand + { + private const char sectionDelimiter = '/'; + private readonly IMessaging messaging; + private SummaryInformationStreams transformSummaryInfo; + + /// + /// Instantiates a new Differ class. + /// + public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool preserveUnchangedRows, bool showPedanticMessages) + { + this.messaging = messaging; + this.TargetOutput = targetOutput; + this.UpdatedOutput = updatedOutput; + this.PreserveUnchangedRows = preserveUnchangedRows; + this.ShowPedanticMessages = showPedanticMessages; + } + + private WindowsInstallerData TargetOutput { get; } + + private WindowsInstallerData UpdatedOutput { get; } + + private TransformFlags ValidationFlags { get; } + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + private bool ShowPedanticMessages { get; } + + /// + /// Gets or sets the option to suppress keeping special rows. + /// + /// The option to suppress keeping special rows. + private bool SuppressKeepingSpecialRows { get; } + + /// + /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. + /// + /// The option to keep all rows including unchanged rows. + private bool PreserveUnchangedRows { get; } + + public WindowsInstallerData Transform { get; private set; } + + /// + /// Creates a transform by diffing two outputs. + /// + public WindowsInstallerData Execute() + { + var targetOutput = this.TargetOutput; + var updatedOutput = this.UpdatedOutput; + var validationFlags = this.ValidationFlags; + + var transform = new WindowsInstallerData(null) + { + Type = OutputType.Transform, + Codepage = updatedOutput.Codepage + }; + + this.transformSummaryInfo = new SummaryInformationStreams(); + + // compare the codepages + if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) + { + this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); + if (null != updatedOutput.SourceLineNumbers) + { + this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); + } + } + + // compare the output types + if (targetOutput.Type != updatedOutput.Type) + { + throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); + } + + // compare the contents of the tables + foreach (var targetTable in targetOutput.Tables) + { + var updatedTable = updatedOutput.Tables[targetTable.Name]; + var operation = TableOperation.None; + + var rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); + + if (TableOperation.Drop == operation) + { + var droppedTable = transform.EnsureTable(targetTable.Definition); + droppedTable.Operation = TableOperation.Drop; + } + else if (TableOperation.None == operation) + { + var modifiedTable = transform.EnsureTable(updatedTable.Definition); + foreach (var row in rows) + { + modifiedTable.Rows.Add(row); + } + } + } + + // added tables + foreach (var updatedTable in updatedOutput.Tables) + { + if (null == targetOutput.Tables[updatedTable.Name]) + { + var addedTable = transform.EnsureTable(updatedTable.Definition); + addedTable.Operation = TableOperation.Add; + + foreach (var updatedRow in updatedTable.Rows) + { + updatedRow.Operation = RowOperation.Add; + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + addedTable.Rows.Add(updatedRow); + } + } + } + + // set summary information properties + if (!this.SuppressKeepingSpecialRows) + { + var summaryInfoTable = transform.Tables["_SummaryInformation"]; + this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); + } + + this.Transform = transform; + return this.Transform; + } + + /// + /// Add a row to the using the primary key. + /// + /// The indexed rows. + /// The row to index. + private void AddIndexedRow(Dictionary index, Row row) + { + var primaryKey = row.GetPrimaryKey(); + + if (null != primaryKey) + { + if (index.TryGetValue(primaryKey, out var collisionRow)) + { +#if TODO_PATCH // This case doesn't seem like it can happen any longer. + // Overriding WixActionRows have a primary key defined and take precedence in the index. + if (row is WixActionRow actionRow) + { + // If the current row is not overridable, see if the indexed row is. + if (!actionRow.Overridable) + { + if (collisionRow is WixActionRow indexedRow && indexedRow.Overridable) + { + // The indexed key is overridable and should be replaced. + index[primaryKey] = actionRow; + } + } + + // If we got this far, the row does not need to be indexed. + return; + } +#endif + + if (this.ShowPedanticMessages) + { + this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); + } + } + else + { + index.Add(primaryKey, row); + } + } + else // use the string representation of the row as its primary key (it may not be unique) + { + // this is provided for compatibility with unreal tables with no primary key + // all real tables must specify at least one column as the primary key + primaryKey = row.ToString(); + index[primaryKey] = row; + } + } + + private bool CompareRows(Table targetTable, Row targetRow, Row updatedRow, out Row comparedRow) + { + comparedRow = null; + + var keepRow = false; + + if (null == targetRow ^ null == updatedRow) + { + if (null == targetRow) + { + updatedRow.Operation = RowOperation.Add; + comparedRow = updatedRow; + } + else if (null == updatedRow) + { + targetRow.Operation = RowOperation.Delete; + targetRow.SectionId += sectionDelimiter; + + comparedRow = targetRow; + keepRow = true; + } + } + else // possibly modified + { + updatedRow.Operation = RowOperation.None; + if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) + { + // ignore rows that shouldn't be in a transform + if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) + { + updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + comparedRow = updatedRow; + keepRow = true; + } + } + else + { + if (this.PreserveUnchangedRows) + { + keepRow = true; + } + + for (var i = 0; i < updatedRow.Fields.Length; i++) + { + var columnDefinition = updatedRow.Fields[i].Column; + + if (!columnDefinition.PrimaryKey) + { + var modified = false; + + if (i >= targetRow.Fields.Length) + { + columnDefinition.Added = true; + modified = true; + } + else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) + { + if (null == targetRow[i] ^ null == updatedRow[i]) + { + modified = true; + } + else if (null != targetRow[i] && null != updatedRow[i]) + { + modified = (targetRow.FieldAsInteger(i) != updatedRow.FieldAsInteger(i)); + } + } + else if (ColumnType.Preserved == columnDefinition.Type) + { + updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); + + // keep rows containing preserved fields so the historical data is available to the binder + keepRow = !this.SuppressKeepingSpecialRows; + } + else if (ColumnType.Object == columnDefinition.Type) + { + var targetObjectField = (ObjectField)targetRow.Fields[i]; + var updatedObjectField = (ObjectField)updatedRow.Fields[i]; + + updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex; + updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri; + + // always keep a copy of the previous data even if they are identical + // This makes diff.wixmst clean and easier to control patch logic + updatedObjectField.PreviousData = (string)targetObjectField.Data; + + // always remember the unresolved data for target build + updatedObjectField.UnresolvedPreviousData = targetObjectField.UnresolvedData; + + // keep rows containing object fields so the files can be compared in the binder + keepRow = !this.SuppressKeepingSpecialRows; + } + else + { + modified = (targetRow.FieldAsString(i) != updatedRow.FieldAsString(i)); + } + + if (modified) + { + if (null != updatedRow.Fields[i].PreviousData) + { + updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i); + } + + updatedRow.Fields[i].Modified = true; + updatedRow.Operation = RowOperation.Modify; + keepRow = true; + } + } + } + + if (keepRow) + { + comparedRow = updatedRow; + comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + } + } + } + + return keepRow; + } + + private List CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) + { + var rows = new List(); + operation = TableOperation.None; + + // dropped tables + if (null == updatedTable ^ null == targetTable) + { + if (null == targetTable) + { + operation = TableOperation.Add; + rows.AddRange(updatedTable.Rows); + } + else if (null == updatedTable) + { + operation = TableOperation.Drop; + } + } + else // possibly modified tables + { + var updatedPrimaryKeys = new Dictionary(); + var targetPrimaryKeys = new Dictionary(); + + // compare the table definitions + if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) + { + // continue to the next table; may be more mismatches + this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); + } + else + { + this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); + + // diff the target and updated rows + foreach (var targetPrimaryKeyEntry in targetPrimaryKeys) + { + var targetPrimaryKey = targetPrimaryKeyEntry.Key; + var targetRow = targetPrimaryKeyEntry.Value; + updatedPrimaryKeys.TryGetValue(targetPrimaryKey, out var updatedRow); + + var keepRow = this.CompareRows(targetTable, targetRow, updatedRow, out var compared); + + if (keepRow) + { + rows.Add(compared); + } + } + + // find the inserted rows + foreach (var updatedPrimaryKeyEntry in updatedPrimaryKeys) + { + var updatedPrimaryKey = updatedPrimaryKeyEntry.Key; + + if (!targetPrimaryKeys.ContainsKey(updatedPrimaryKey)) + { + var updatedRow = updatedPrimaryKeyEntry.Value; + + updatedRow.Operation = RowOperation.Add; + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + rows.Add(updatedRow); + } + } + } + } + + return rows; + } + + private void IndexPrimaryKeys(Table targetTable, Dictionary targetPrimaryKeys, Table updatedTable, Dictionary updatedPrimaryKeys) + { + // index the target rows + foreach (var row in targetTable.Rows) + { + this.AddIndexedRow(targetPrimaryKeys, row); + + if ("Property" == targetTable.Name) + { + var id = row.FieldAsString(0); + + if ("ProductCode" == id) + { + this.transformSummaryInfo.TargetProductCode = row.FieldAsString(1); + + if ("*" == this.transformSummaryInfo.TargetProductCode) + { + this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); + } + } + else if ("ProductVersion" == id) + { + this.transformSummaryInfo.TargetProductVersion = row.FieldAsString(1); + } + else if ("UpgradeCode" == id) + { + this.transformSummaryInfo.TargetUpgradeCode = row.FieldAsString(1); + } + } + else if ("_SummaryInformation" == targetTable.Name) + { + var id = row.FieldAsInteger(0); + + if (1 == id) // PID_CODEPAGE + { + this.transformSummaryInfo.TargetSummaryInfoCodepage = row.FieldAsString(1); + } + else if (7 == id) // PID_TEMPLATE + { + this.transformSummaryInfo.TargetPlatformAndLanguage = row.FieldAsString(1); + } + else if (14 == id) // PID_PAGECOUNT + { + this.transformSummaryInfo.TargetMinimumVersion = row.FieldAsString(1); + } + } + } + + // index the updated rows + foreach (var row in updatedTable.Rows) + { + this.AddIndexedRow(updatedPrimaryKeys, row); + + if ("Property" == updatedTable.Name) + { + var id = row.FieldAsString(0); + + if ("ProductCode" == id) + { + this.transformSummaryInfo.UpdatedProductCode = row.FieldAsString(1); + + if ("*" == this.transformSummaryInfo.UpdatedProductCode) + { + this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); + } + } + else if ("ProductVersion" == id) + { + this.transformSummaryInfo.UpdatedProductVersion = row.FieldAsString(1); + } + } + else if ("_SummaryInformation" == updatedTable.Name) + { + var id = row.FieldAsInteger(0); + + if (1 == id) // PID_CODEPAGE + { + this.transformSummaryInfo.UpdatedSummaryInfoCodepage = row.FieldAsString(1); + } + else if (7 == id) // PID_TEMPLATE + { + this.transformSummaryInfo.UpdatedPlatformAndLanguage = row.FieldAsString(1); + } + else if (14 == id) // PID_PAGECOUNT + { + this.transformSummaryInfo.UpdatedMinimumVersion = row.FieldAsString(1); + } + } + } + } + + private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) + { + // calculate the minimum version of MSI required to process the transform + var minimumVersion = 100; + + if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out var targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out var updatedMin)) + { + minimumVersion = Math.Max(targetMin, updatedMin); + } + + var summaryRows = new Dictionary(summaryInfoTable.Rows.Count); + + foreach (var row in summaryInfoTable.Rows) + { + var id = row.FieldAsInteger(0); + + summaryRows[id] = row; + + if ((int)SummaryInformation.Transform.CodePage == id) + { + row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; + row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; + } + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == id) + { + row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == id) + { + row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.ProductCodes == id) + { + row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); + } + else if ((int)SummaryInformation.Transform.InstallerRequirement == id) + { + row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); + } + else if ((int)SummaryInformation.Transform.Security == id) + { + row[1] = "4"; + } + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; + summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; + summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; + summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.InstallerRequirement)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; + summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); + } + + if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security)) + { + var summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.Security; + summaryRow[1] = "4"; + } + } + + private class SummaryInformationStreams + { + public string TargetSummaryInfoCodepage { get; set; } + + public string TargetPlatformAndLanguage { get; set; } + + public string TargetProductCode { get; set; } + + public string TargetProductVersion { get; set; } + + public string TargetUpgradeCode { get; set; } + + public string TargetMinimumVersion { get; set; } + + public string UpdatedSummaryInfoCodepage { get; set; } + + public string UpdatedPlatformAndLanguage { get; set; } + + public string UpdatedProductCode { get; set; } + + public string UpdatedProductVersion { get; set; } + + public string UpdatedMinimumVersion { get; set; } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs new file mode 100644 index 00000000..949d5e18 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs @@ -0,0 +1,157 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class GetFileFacadesCommand + { + public GetFileFacadesCommand(IntermediateSection section, IWindowsInstallerBackendHelper backendHelper) + { + this.Section = section; + this.BackendHelper = backendHelper; + } + + private IntermediateSection Section { get; } + + private IWindowsInstallerBackendHelper BackendHelper { get; } + + public List FileFacades { get; private set; } + + public void Execute() + { + var facades = new List(); + + var assemblyFile = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); +#if TODO_PATCHING_DELTA + //var deltaPatchFiles = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); +#endif + + foreach (var file in this.Section.Symbols.OfType()) + { + assemblyFile.TryGetValue(file.Id.Id, out var assembly); + +#if TODO_PATCHING_DELTA + //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile); + // TODO: should we be passing along delta information to the file facade? Probably, right? +#endif + var fileFacade = this.BackendHelper.CreateFileFacade(file, assembly); + + facades.Add(fileFacade); + } + +#if TODO_PATCHING_DELTA + this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); +#endif + + this.FileFacades = facades; + } + +#if TODO_PATCHING_DELTA + /// + /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. + /// + public void ResolveDeltaPatchSymbolPaths(Dictionary deltaPatchFiles, IEnumerable facades) + { + ILookup filesByComponent = null; + ILookup filesByDirectory = null; + ILookup filesByDiskId = null; + + foreach (var row in this.Section.Symbols.OfType().OrderBy(r => r.SymbolType)) + { + switch (row.SymbolType) + { + case SymbolPathType.File: + this.MergeSymbolPaths(row, deltaPatchFiles[row.SymbolId]); + break; + + case SymbolPathType.Component: + if (null == filesByComponent) + { + filesByComponent = facades.ToLookup(f => f.File.ComponentRef); + } + + foreach (var facade in filesByComponent[row.SymbolId]) + { + this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); + } + break; + + case SymbolPathType.Directory: + if (null == filesByDirectory) + { + filesByDirectory = facades.ToLookup(f => f.File.DirectoryRef); + } + + foreach (var facade in filesByDirectory[row.SymbolId]) + { + this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); + } + break; + + case SymbolPathType.Media: + if (null == filesByDiskId) + { + filesByDiskId = facades.ToLookup(f => f.File.DiskId.ToString(CultureInfo.InvariantCulture)); + } + + foreach (var facade in filesByDiskId[row.SymbolId]) + { + this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]); + } + break; + + case SymbolPathType.Product: + foreach (var fileRow in deltaPatchFiles.Values) + { + this.MergeSymbolPaths(row, fileRow); + } + break; + + default: + // error + break; + } + } + } + + /// + /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row. + /// + /// Row from the WixPatchSymbolsPaths table. + /// FileRow into which to set symbol information. + /// This includes PreviousData as well. + private void MergeSymbolPaths(WixDeltaPatchSymbolPathsSymbol row, WixDeltaPatchFileSymbol file) + { + if (file.SymbolPaths is null) + { + file.SymbolPaths = row.SymbolPaths; + } + else + { + file.SymbolPaths = String.Concat(file.SymbolPaths, ";", row.SymbolPaths); + } + + Field field = row.Fields[2]; + if (null != field.PreviousData) + { + if (null == file.PreviousSymbols) + { + file.PreviousSymbols = field.PreviousData; + } + else + { + file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData); + } + } + } +#endif + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs new file mode 100644 index 00000000..2ac563ac --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.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.Core.WindowsInstaller.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class GetFileFacadesFromTransforms + { + public GetFileFacadesFromTransforms(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, FileSystemManager fileSystemManager, IEnumerable subStorages) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.FileSystemManager = fileSystemManager; + this.SubStorages = subStorages; + } + + private IMessaging Messaging { get; } + + private IWindowsInstallerBackendHelper BackendHelper { get; } + + private FileSystemManager FileSystemManager { get; } + + private IEnumerable SubStorages { get; } + + public List FileFacades { get; private set; } + + public void Execute() + { + var allFileRows = new List(); + + var patchMediaFileRows = new Dictionary>(); + + //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); + + // Index paired transforms by name without their "#" prefix. + var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); + + // Enumerate through main transforms. + foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) + { + var mainTransform = substorage.Data; + var mainFileTable = mainTransform.Tables["File"]; + + if (null == mainFileTable) + { + continue; + } + + // Index File table of pairedTransform + var pairedTransform = pairedTransforms["#" + substorage.Name]; + var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); + + foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete)) + { + var mainFileId = mainFileRow.File; + + // We need compare the underlying files and include all file changes. + var objectField = (ObjectField)mainFileRow.Fields[9]; + var pairedFileRow = pairedFileRows.Get(mainFileId); + + // If the file is new, we always need to add it to the patch. + if (mainFileRow.Operation == RowOperation.Add) + { + if (null != pairedFileRow) // RowOperation.Add + { + // Always patch-added, but never non-compressed. + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = RowOperation.Add; + } + } + else + { + // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. + if (null == objectField.PreviousData) + { + if (mainFileRow.Operation == RowOperation.None) + { + continue; + } + } + else + { + // TODO: should this entire condition be placed in the binder file manager? + if (/*(0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&*/ + !this.FileSystemManager.CompareFiles(objectField.PreviousData, objectField.Data.ToString())) + { + // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. + mainFileRow.Operation = RowOperation.Modify; + if (null != pairedFileRow) + { + // Always patch-added, but never non-compressed. + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = RowOperation.Modify; + } + } + else + { + // The File is same. We need mark all the attributes as unchanged. + mainFileRow.Operation = RowOperation.None; + foreach (var field in mainFileRow.Fields) + { + field.Modified = false; + } + + if (null != pairedFileRow) + { + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Fields[6].Modified = false; + pairedFileRow.Operation = RowOperation.None; + } + continue; + } + } + } + + // index patch files by diskId+fileId + var diskId = mainFileRow.DiskId; + + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) + { + mediaFileRows = new RowDictionary(); + patchMediaFileRows.Add(diskId, mediaFileRows); + } + + var patchFileRow = mediaFileRows.Get(mainFileId); + + if (null == patchFileRow) + { + //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); + patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers); + mainFileRow.CopyTo(patchFileRow); + + mediaFileRows.Add(patchFileRow); + +#if TODO_PATCHING_DELTA + // TODO: should we be passing along delta information to the file facade? Probably, right? +#endif + var fileFacade = this.BackendHelper.CreateFileFacade(patchFileRow); + + allFileRows.Add(fileFacade); + } + else + { + // TODO: confirm the rest of data is identical? + + // make sure Source is same. Otherwise we are silently ignoring a file. + if (0 != String.Compare(patchFileRow.Source, mainFileRow.Source, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, mainFileId, patchFileRow.Source, mainFileRow.Source)); + } + +#if TODO_PATCHING_DELTA + // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. + patchFileRow.AppendPreviousDataFrom(mainFileRow); +#endif + } + } + } + + this.FileFacades = allFileRows; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs new file mode 100644 index 00000000..2eb95bc5 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs @@ -0,0 +1,215 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class LoadTableDefinitionsCommand + { + public LoadTableDefinitionsCommand(IMessaging messaging, IntermediateSection section, IEnumerable backendExtensions) + { + this.Messaging = messaging; + this.Section = section; + this.BackendExtensions = backendExtensions; + } + + public IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private IEnumerable BackendExtensions { get; } + + public TableDefinitionCollection TableDefinitions { get; private set; } + + public TableDefinitionCollection Execute() + { + var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + var customColumnsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + + if (customColumnsById.Any()) + { + foreach (var symbol in this.Section.Symbols.OfType()) + { + var customTableDefinition = this.CreateCustomTable(symbol, customColumnsById); + tableDefinitions.Add(customTableDefinition); + } + } + + foreach (var backendExtension in this.BackendExtensions) + { + foreach (var tableDefinition in backendExtension.TableDefinitions) + { + if (tableDefinitions.Contains(tableDefinition.Name)) + { + this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(backendExtension.GetType().Assembly.Location, tableDefinition.Name)); + } + + tableDefinitions.Add(tableDefinition); + } + } + + this.TableDefinitions = tableDefinitions; + return this.TableDefinitions; + } + + private TableDefinition CreateCustomTable(WixCustomTableSymbol symbol, Dictionary customColumnsById) + { + var columnNames = symbol.ColumnNamesSeparated; + var columns = new List(columnNames.Length); + + foreach (var name in columnNames) + { + var column = customColumnsById[symbol.Id.Id + "/" + name]; + + var type = ColumnType.Unknown; + + if (column.Type == IntermediateFieldType.String) + { + type = column.Localizable ? ColumnType.Localized : ColumnType.String; + } + else if (column.Type == IntermediateFieldType.Number) + { + type = ColumnType.Number; + } + else if (column.Type == IntermediateFieldType.Path) + { + type = ColumnType.Object; + } + + var category = ColumnCategory.Unknown; + switch (column.Category) + { + case WixCustomTableColumnCategoryType.Text: + category = ColumnCategory.Text; + break; + case WixCustomTableColumnCategoryType.UpperCase: + category = ColumnCategory.UpperCase; + break; + case WixCustomTableColumnCategoryType.LowerCase: + category = ColumnCategory.LowerCase; + break; + case WixCustomTableColumnCategoryType.Integer: + category = ColumnCategory.Integer; + break; + case WixCustomTableColumnCategoryType.DoubleInteger: + category = ColumnCategory.DoubleInteger; + break; + case WixCustomTableColumnCategoryType.TimeDate: + category = ColumnCategory.TimeDate; + break; + case WixCustomTableColumnCategoryType.Identifier: + category = ColumnCategory.Identifier; + break; + case WixCustomTableColumnCategoryType.Property: + category = ColumnCategory.Property; + break; + case WixCustomTableColumnCategoryType.Filename: + category = ColumnCategory.Filename; + break; + case WixCustomTableColumnCategoryType.WildCardFilename: + category = ColumnCategory.WildCardFilename; + break; + case WixCustomTableColumnCategoryType.Path: + category = ColumnCategory.Path; + break; + case WixCustomTableColumnCategoryType.Paths: + category = ColumnCategory.Paths; + break; + case WixCustomTableColumnCategoryType.AnyPath: + category = ColumnCategory.AnyPath; + break; + case WixCustomTableColumnCategoryType.DefaultDir: + category = ColumnCategory.DefaultDir; + break; + case WixCustomTableColumnCategoryType.RegPath: + category = ColumnCategory.RegPath; + break; + case WixCustomTableColumnCategoryType.Formatted: + category = ColumnCategory.Formatted; + break; + case WixCustomTableColumnCategoryType.FormattedSddl: + category = ColumnCategory.FormattedSDDLText; + break; + case WixCustomTableColumnCategoryType.Template: + category = ColumnCategory.Template; + break; + case WixCustomTableColumnCategoryType.Condition: + category = ColumnCategory.Condition; + break; + case WixCustomTableColumnCategoryType.Guid: + category = ColumnCategory.Guid; + break; + case WixCustomTableColumnCategoryType.Version: + category = ColumnCategory.Version; + break; + case WixCustomTableColumnCategoryType.Language: + category = ColumnCategory.Language; + break; + case WixCustomTableColumnCategoryType.Binary: + category = ColumnCategory.Binary; + break; + case WixCustomTableColumnCategoryType.CustomSource: + category = ColumnCategory.CustomSource; + break; + case WixCustomTableColumnCategoryType.Cabinet: + category = ColumnCategory.Cabinet; + break; + case WixCustomTableColumnCategoryType.Shortcut: + category = ColumnCategory.Shortcut; + break; + case null: + default: + break; + } + + var modularization = ColumnModularizeType.None; + + switch (column.Modularize) + { + case null: + case WixCustomTableColumnModularizeType.None: + modularization = ColumnModularizeType.None; + break; + case WixCustomTableColumnModularizeType.Column: + modularization = ColumnModularizeType.Column; + break; + case WixCustomTableColumnModularizeType.CompanionFile: + modularization = ColumnModularizeType.CompanionFile; + break; + case WixCustomTableColumnModularizeType.Condition: + modularization = ColumnModularizeType.Condition; + break; + case WixCustomTableColumnModularizeType.ControlEventArgument: + modularization = ColumnModularizeType.ControlEventArgument; + break; + case WixCustomTableColumnModularizeType.ControlText: + modularization = ColumnModularizeType.ControlText; + break; + case WixCustomTableColumnModularizeType.Icon: + modularization = ColumnModularizeType.Icon; + break; + case WixCustomTableColumnModularizeType.Property: + modularization = ColumnModularizeType.Property; + break; + case WixCustomTableColumnModularizeType.SemicolonDelimited: + modularization = ColumnModularizeType.SemicolonDelimited; + break; + } + + var columnDefinition = new ColumnDefinition(name, type, column.Width, column.PrimaryKey, column.Nullable, category, column.MinValue, column.MaxValue, column.KeyTable, column.KeyColumn, column.Set, column.Description, modularization, ColumnType.Localized == type, useCData: true, column.Unreal); + columns.Add(columnDefinition); + } + + var customTable = new TableDefinition(symbol.Id.Id, null, columns, symbol.Unreal); + return customTable; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs new file mode 100644 index 00000000..6446692e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -0,0 +1,331 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.Native.Msm; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Merge modules into the database at output path. + /// + internal class MergeModulesCommand + { + public MergeModulesCommand(IMessaging messaging, IEnumerable fileFacades, IntermediateSection section, IEnumerable suppressedTableNames, string outputPath, string intermediateFolder) + { + this.Messaging = messaging; + this.FileFacades = fileFacades; + this.Section = section; + this.SuppressedTableNames = suppressedTableNames ?? Array.Empty(); + this.OutputPath = outputPath; + this.IntermediateFolder = intermediateFolder; + } + + private IMessaging Messaging { get; } + + private IEnumerable FileFacades { get; } + + private IntermediateSection Section { get; } + + private IEnumerable SuppressedTableNames { get; } + + private string OutputPath { get; } + + private string IntermediateFolder { get; } + + public void Execute() + { + var wixMergeSymbols = this.Section.Symbols.OfType().ToList(); + if (!wixMergeSymbols.Any()) + { + return; + } + + IMsmMerge2 merge = null; + var commit = true; + var logOpen = false; + var databaseOpen = false; + var logPath = Path.Combine(this.IntermediateFolder, "merge.log"); + + try + { + merge = MsmInterop.GetMsmMerge(); + + merge.OpenLog(logPath); + logOpen = true; + + merge.OpenDatabase(this.OutputPath); + databaseOpen = true; + + var featureModulesByMergeId = this.Section.Symbols.OfType().GroupBy(t => t.WixMergeRef).ToDictionary(g => g.Key); + + // process all the merge rows + foreach (var wixMergeRow in wixMergeSymbols) + { + var moduleOpen = false; + + try + { + short mergeLanguage; + + try + { + mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); + } + catch (FormatException) + { + this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.Language.ToString())); + continue; + } + + this.Messaging.Write(VerboseMessages.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.Messaging.Write(VerboseMessages.MergingMergeModule(wixMergeRow.SourceFile)); + merge.MergeEx(wixMergeRow.FeatureRef, wixMergeRow.DirectoryRef, callback); + + // Connect any non-primary features. + if (featureModulesByMergeId.TryGetValue(wixMergeRow.Id.Id, out var featureModules)) + { + foreach (var featureModule in featureModules) + { + this.Messaging.Write(VerboseMessages.ConnectingMergeModule(wixMergeRow.SourceFile, featureModule.FeatureRef)); + merge.Connect(featureModule.FeatureRef); + } + } + } + catch (COMException) + { + commit = false; + } + finally + { + var mergeErrors = merge.Errors; + + // display all the errors encountered during the merge operations for this module + for (var i = 1; i <= mergeErrors.Count; i++) + { + var mergeError = mergeErrors[i]; + var databaseKeys = new StringBuilder(); + var moduleKeys = new StringBuilder(); + + // build a string of the database keys + for (var 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 (var 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.Messaging.Write(ErrorMessages.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleKeys.ToString())); + break; + case MsmErrorType.msmErrorFeatureRequired: + this.Messaging.Write(ErrorMessages.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id.Id)); + break; + case MsmErrorType.msmErrorLanguageFailed: + this.Messaging.Write(ErrorMessages.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); + break; + case MsmErrorType.msmErrorLanguageUnsupported: + this.Messaging.Write(ErrorMessages.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); + break; + case MsmErrorType.msmErrorResequenceMerge: + this.Messaging.Write(WarningMessages.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.Messaging.Write(WarningMessages.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); + } + break; + case MsmErrorType.msmErrorPlatformMismatch: + this.Messaging.Write(ErrorMessages.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); + break; + default: + this.Messaging.Write(ErrorMessages.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, "Encountered an unexpected merge error of type '{0}' for which there is currently no error message to display. More information about the merge and the failure can be found in the merge log: '{1}'", Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); + break; + } + } + + if (0 >= mergeErrors.Count && !commit) + { + this.Messaging.Write(ErrorMessages.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, "Encountered an unexpected error while merging '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'", 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.Messaging.EncounteredError) + { + return; + } + + using (var db = new Database(this.OutputPath, OpenDatabase.Direct)) + { + // Suppress individual actions. + foreach (var suppressAction in this.Section.Symbols.OfType()) + { + var tableName = suppressAction.SequenceTable.WindowsInstallerTableName(); + if (db.TableExists(tableName)) + { + var query = $"SELECT * FROM {tableName} WHERE `Action` = '{suppressAction.Action}'"; + + using (var view = db.OpenExecuteView(query)) + using (var record = view.Fetch()) + { + if (null != record) + { + this.Messaging.Write(WarningMessages.SuppressMergedAction(suppressAction.Action, tableName)); + view.Modify(ModifyView.Delete, record); + } + } + } + } + + // Query for merge module actions in suppressed sequences and drop them. + foreach (var tableName in this.SuppressedTableNames) + { + if (!db.TableExists(tableName)) + { + continue; + } + + using (var view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) + { + foreach (var resultRecord in view.Records) + { + this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName)); + } + } + + // drop suppressed sequences + using (var view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) + { + } + + // delete the validation rows + using (var view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) + using (var record = new Record(1)) + { + record.SetString(1, tableName); + view.Execute(record); + } + } + + // now update the Attributes column for the files from the Merge Modules + this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles()); + using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) + { + foreach (var file in this.FileFacades) + { + if (!file.FromModule) + { + continue; + } + + using (var record = new Record(1)) + { + record.SetString(1, file.Id); + view.Execute(record); + } + + using (var 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.Sequence); + + // Update the file attributes to match the compression specified + // on the Merge element or on the Package element. + var attributes = 0; + + // Get the current value if its not null. + if (!recordUpdate.IsNull(2)) + { + attributes = recordUpdate.GetInteger(2); + } + + if (file.Compressed) + { + attributes |= WindowsInstallerConstants.MsidbFileAttributesCompressed; + attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + } + else if (file.Uncompressed) + { + attributes |= WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; + } + else // clear all compression bits. + { + attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed; + attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + } + + recordUpdate.SetInteger(2, attributes); + + view.Modify(ModifyView.Update, recordUpdate); + } + } + } + + db.Commit(); + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs new file mode 100644 index 00000000..04f1b771 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs @@ -0,0 +1,236 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class ModularizeCommand + { + public ModularizeCommand(IBackendHelper backendHelper, WindowsInstallerData output, string modularizationSuffix, IEnumerable suppressSymbols) + { + this.BackendHelper = backendHelper; + this.Output = output; + this.ModularizationSuffix = modularizationSuffix; + + // Gather all the unique suppress modularization identifiers. + this.SuppressModularizationIdentifiers = new HashSet(suppressSymbols.Select(s => s.SuppressIdentifier)); + } + + private IBackendHelper BackendHelper { get; } + + private WindowsInstallerData Output { get; } + + private string ModularizationSuffix { get; } + + private HashSet SuppressModularizationIdentifiers { get; } + + public void Execute() + { + foreach (var table in this.Output.Tables) + { + this.ModularizeTable(table); + } + } + + private void ModularizeTable(Table table) + { + var modularizedColumns = new List(); + + // find the modularized columns + for (var i = 0; i < table.Definition.Columns.Length; ++i) + { + if (ColumnModularizeType.None != table.Definition.Columns[i].ModularizeType) + { + modularizedColumns.Add(i); + } + } + + if (0 < modularizedColumns.Count) + { + foreach (var row in table.Rows) + { + foreach (var modularizedColumn in modularizedColumns) + { + var field = row.Fields[modularizedColumn]; + + if (field.Data != null) + { + field.Data = this.ModularizedRowFieldValue(row, field); + } + } + } + } + } + + private string ModularizedRowFieldValue(Row row, Field field) + { + var fieldData = field.AsString(); + + if (!(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData))) + { + var modularizeType = field.Column.ModularizeType; + + // special logic for the ControlEvent table's Argument column + // this column requires different modularization methods depending upon the value of the Event column + if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType) + { + switch (row[2].ToString()) + { + case "CheckExistingTargetPath": // redirectable property name + case "CheckTargetPath": + case "DoAction": // custom action name + case "NewDialog": // dialog name + case "SelectionBrowse": + case "SetTargetPath": + case "SpawnDialog": + case "SpawnWaitDialog": + if (this.BackendHelper.IsValidIdentifier(fieldData)) + { + modularizeType = ColumnModularizeType.Column; + } + else + { + modularizeType = ColumnModularizeType.Property; + } + break; + default: // formatted + modularizeType = ColumnModularizeType.Property; + break; + } + } + else if (ColumnModularizeType.ControlText == field.Column.ModularizeType) + { + // icons are stored in the Binary table, so they get column-type modularization + if (("Bitmap" == row[2].ToString() || "Icon" == row[2].ToString()) && this.BackendHelper.IsValidIdentifier(fieldData)) + { + modularizeType = ColumnModularizeType.Column; + } + else + { + modularizeType = ColumnModularizeType.Property; + } + } + + switch (modularizeType) + { + case ColumnModularizeType.Column: + // ensure the value is an identifier (otherwise it shouldn't be modularized this way) + if (!this.BackendHelper.IsValidIdentifier(fieldData)) + { + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData)); + } + + // if we're not supposed to suppress modularization of this identifier + if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) + { + fieldData = String.Concat(fieldData, this.ModularizationSuffix); + } + break; + + case ColumnModularizeType.Property: + case ColumnModularizeType.Condition: + Regex regex; + if (ColumnModularizeType.Property == modularizeType) + { + regex = new Regex(@"\[(?[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture); + } + else + { + Debug.Assert(ColumnModularizeType.Condition == modularizeType); + + // This heinous looking regular expression is actually quite an elegant way + // to shred the entire condition into the identifiers that need to be + // modularized. Let's break it down piece by piece: + // + // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the + // regular expression is case insensitive so we don't have to worry about + // all the permutations of these strings. + // 2. Look for quoted strings. Quoted strings are just text and are ignored + // outright. + // 3. Look for environment variables. These look like identifiers we might + // otherwise be interested in but start with a percent sign. Like quoted + // strings these enviroment variable references are ignored outright. + // 4. Match all identifiers that are things that need to be modularized. Note + // the special characters (!, $, ?, &) that denote Component and Feature states. + regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); + + // less performant version of the above with captures showing where everything lives + // regex = new Regex(@"(?NOT|EQV|XOR|OR|AND|IMP)|(?"".*?"")|(?%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); + } + + var matches = regex.Matches(fieldData); + + var sb = new StringBuilder(fieldData); + + // Notice how this code walks backward through the list + // because it modifies the string as we through it. + for (var i = matches.Count - 1; 0 <= i; i--) + { + var group = matches[i].Groups["identifier"]; + if (group.Success) + { + var identifier = group.Value; + if (!WindowsInstallerStandard.IsStandardProperty(identifier) && !this.SuppressModularizationIdentifiers.Contains(identifier)) + { + sb.Insert(group.Index + group.Length, this.ModularizationSuffix); + } + } + } + + fieldData = sb.ToString(); + break; + + case ColumnModularizeType.CompanionFile: + // if we're not supposed to ignore this identifier and the value does not start with + // a digit, we must have a companion file so modularize it + if (!this.SuppressModularizationIdentifiers.Contains(fieldData) && + 0 < fieldData.Length && !Char.IsDigit(fieldData, 0)) + { + fieldData = String.Concat(fieldData, this.ModularizationSuffix); + } + break; + + case ColumnModularizeType.Icon: + if (!this.SuppressModularizationIdentifiers.Contains(fieldData)) + { + var start = fieldData.LastIndexOf(".", StringComparison.Ordinal); + if (-1 == start) + { + fieldData = String.Concat(fieldData, this.ModularizationSuffix); + } + else + { + fieldData = String.Concat(fieldData.Substring(0, start), this.ModularizationSuffix, fieldData.Substring(start)); + } + } + break; + + case ColumnModularizeType.SemicolonDelimited: + var keys = fieldData.Split(';'); + for (var i = 0; i < keys.Length; ++i) + { + if (!String.IsNullOrEmpty(keys[i])) + { + keys[i] = String.Concat(keys[i], this.ModularizationSuffix); + } + } + + fieldData = String.Join(";", keys); + break; + } + } + + return fieldData; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs new file mode 100644 index 00000000..5dd4d3ea --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs @@ -0,0 +1,119 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class OptimizeFileFacadesOrderCommand + { + public OptimizeFileFacadesOrderCommand(IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform, List fileFacades) + { + this.BackendHelper = helper; + this.PathResolver = pathResolver; + this.Section = section; + this.Platform = platform; + this.FileFacades = fileFacades; + } + + public List FileFacades { get; private set; } + + private IBackendHelper BackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private IntermediateSection Section { get; } + + private Platform Platform { get; } + + public List Execute() + { + var canonicalComponentTargetPaths = this.ComponentTargetPaths(); + + this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths, this.Section.Type == SectionType.Module)); + + return this.FileFacades; + } + + private Dictionary ComponentTargetPaths() + { + var directories = this.ResolveDirectories(); + + var canonicalPathsByDirectoryId = new Dictionary(); + foreach (var component in this.Section.Symbols.OfType()) + { + var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(directories, null, component.DirectoryRef, this.Platform); + canonicalPathsByDirectoryId.Add(component.Id.Id, directoryPath); + } + + return canonicalPathsByDirectoryId; + } + + private Dictionary ResolveDirectories() + { + var targetPathsByDirectoryId = new Dictionary(); + + // Get the target paths for all directories. + foreach (var directory in this.Section.Symbols.OfType()) + { + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); + targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); + } + + return targetPathsByDirectoryId; + } + + private class FileFacadeOptimizer : IComparer + { + public FileFacadeOptimizer(Dictionary componentTargetPaths, bool optimizingMergeModule) + { + this.ComponentTargetPaths = componentTargetPaths; + this.OptimizingMergeModule = optimizingMergeModule; + } + + private Dictionary ComponentTargetPaths { get; } + + private bool OptimizingMergeModule { get; } + + public int Compare(IFileFacade x, IFileFacade y) + { + // First group files by DiskId but ignore if processing a Merge Module + // because Merge Modules don't have separate disks. + var compare = this.OptimizingMergeModule ? 0 : x.DiskId.CompareTo(y.DiskId); + + if (compare != 0) + { + return compare; + } + + // Next try to group files by target install directory. + if (this.ComponentTargetPaths.TryGetValue(x.ComponentRef, out var canonicalX) && + this.ComponentTargetPaths.TryGetValue(y.ComponentRef, out var canonicalY)) + { + compare = String.Compare(canonicalX, canonicalY, StringComparison.Ordinal); + + if (compare != 0) + { + return compare; + } + } + + // TODO: Consider sorting these facades even smarter by file size or file extension + // or other creative ideas to get optimal install speed out of MSI. + compare = String.Compare(x.FileName, y.FileName, StringComparison.Ordinal); + + if (compare != 0) + { + return compare; + } + + return String.Compare(x.Id, y.Id, StringComparison.Ordinal); + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs new file mode 100644 index 00000000..4d849753 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs @@ -0,0 +1,19 @@ +// 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.Bind +{ + using WixToolset.Data.WindowsInstaller; + + internal class PatchTransform + { + public PatchTransform(string baseline, WindowsInstallerData transform) + { + this.Baseline = baseline; + this.Transform = transform; + } + + public string Baseline { get; } + + public WindowsInstallerData Transform { get; } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs new file mode 100644 index 00000000..1bd2a427 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs @@ -0,0 +1,114 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class ProcessDependencyReferencesCommand + { + // The root registry key for the dependency extension. We write to Software\Classes explicitly + // based on the current security context instead of HKCR. See + // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. + private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; + private const string RegistryDependents = "Dependents"; + + public ProcessDependencyReferencesCommand(IBackendHelper backendHelper, IntermediateSection section, IEnumerable dependencyRefSymbols) + { + this.BackendHelper = backendHelper; + this.Section = section; + this.DependencyRefSymbols = dependencyRefSymbols; + } + + private IBackendHelper BackendHelper { get; } + + private IntermediateSection Section { get; } + + private IEnumerable DependencyRefSymbols { get; } + + public void Execute() + { + var wixDependencyRows = this.Section.Symbols.OfType().ToDictionary(d => d.Id.Id); + var wixDependencyProviderRows = this.Section.Symbols.OfType().ToDictionary(d => d.Id.Id); + + // For each relationship, get the provides and requires rows to generate registry values. + foreach (var wixDependencyRefRow in this.DependencyRefSymbols) + { + var providesId = wixDependencyRefRow.WixDependencyProviderRef; + var requiresId = wixDependencyRefRow.WixDependencyRef; + + // If we do not find both symbols, skip the registry key generation. + if (!wixDependencyRows.TryGetValue(requiresId, out var wixDependencyRow)) + { + continue; + } + + if (!wixDependencyProviderRows.TryGetValue(providesId, out var wixDependencyProviderRow)) + { + continue; + } + + // Format the root registry key using the required provider key and the current provider key. + var requiresKey = wixDependencyRow.Id.Id; + var providesKey = wixDependencyRow.ProviderKey; + var keyRequires = String.Format(@"{0}{1}\{2}\{3}", DependencyRegistryRoot, requiresKey, RegistryDependents, providesKey); + + // Get the component ID from the provider. + var componentId = wixDependencyProviderRow.ParentRef; + + var id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "(Default)"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "*", + }); + + if (!String.IsNullOrEmpty(wixDependencyRow.MinVersion)) + { + id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MinVersion"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "MinVersion", + Value = wixDependencyRow.MinVersion + }); + } + + var maxVersion = (string)wixDependencyRow[3]; + if (!String.IsNullOrEmpty(wixDependencyRow.MaxVersion)) + { + id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MaxVersion"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "MaxVersion", + Value = wixDependencyRow.MaxVersion + }); + } + + if (wixDependencyRow.Attributes != WixDependencySymbolAttributes.None) + { + id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "Attributes"); + this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id)) + { + ComponentRef = componentId, + Root = RegistryRootType.MachineUser, + Key = keyRequires, + Name = "Attributes", + Value = String.Concat("#", (int)wixDependencyRow.Attributes) + }); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs new file mode 100644 index 00000000..9a068603 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs @@ -0,0 +1,131 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class ProcessPackageSoftwareTagsCommand + { + public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags, string intermediateFolder) + { + this.Section = section; + this.SoftwareTags = softwareTags; + this.IntermediateFolder = intermediateFolder; + } + + private string IntermediateFolder { get; } + + private IntermediateSection Section { get; } + + private IEnumerable SoftwareTags { get; } + + public void Execute() + { + string productName = null; + string productVersion = null; + string manufacturer = null; + string upgradeCode = null; + + var summaryInfo = this.Section.Symbols.OfType().FirstOrDefault(s => s.PropertyId == SummaryInformationType.PackageCode); + var packageCode = NormalizeGuid(summaryInfo?.Value); + + foreach (var property in this.Section.Symbols.OfType()) + { + switch (property.Id.Id) + { + case "ProductName": + productName = property.Value; + break; + case "ProductVersion": + productVersion = property.Value; + break; + case "Manufacturer": + manufacturer = property.Value; + break; + case "UpgradeCode": + upgradeCode = NormalizeGuid(property.Value); + break; + } + } + + var fileSymbolsById = this.Section.Symbols.OfType().Where(f => f.Id != null).ToDictionary(f => f.Id.Id); + + var workingFolder = Path.Combine(this.IntermediateFolder, "_swidtag"); + + Directory.CreateDirectory(workingFolder); + + foreach (var tagRow in this.SoftwareTags) + { + if (fileSymbolsById.TryGetValue(tagRow.FileRef, out var fileSymbol)) + { + var uniqueId = String.Concat("msi:package/", packageCode); + var persistentId = String.IsNullOrEmpty(upgradeCode) ? null : String.Concat("msi:upgrade/", upgradeCode); + + // Write the tag file. + fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(workingFolder, fileSymbol.Name) }; + + using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create)) + { + CreateTagFile(fs, uniqueId, productName, productVersion, tagRow.Regid, manufacturer, persistentId); + } + + // Ensure the matching "SoftwareIdentificationTag" row exists and + // is populated correctly. + this.Section.AddSymbol(new SoftwareIdentificationTagSymbol(tagRow.SourceLineNumbers, tagRow.Id) + { + FileRef = fileSymbol.Id.Id, + Regid = tagRow.Regid, + TagId = uniqueId, + PersistentId = persistentId + }); + } + } + } + + private static string NormalizeGuid(string guidString) + { + if (Guid.TryParse(guidString, out var guid)) + { + return guid.ToString("D").ToUpperInvariant(); + } + + return guidString; + } + + private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId) + { + var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; + + using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) + { + writer.WriteStartDocument(); + writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); + writer.WriteAttributeString("tagId", uniqueId); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("version", version); + writer.WriteAttributeString("versionScheme", versionScheme); + + writer.WriteStartElement("Entity"); + writer.WriteAttributeString("name", manufacturer); + writer.WriteAttributeString("regid", regid); + writer.WriteAttributeString("role", "softwareCreator tagCreator"); + writer.WriteEndElement(); // + + if (!String.IsNullOrEmpty(persistendId)) + { + writer.WriteStartElement("Meta"); + writer.WriteAttributeString("persistentId", persistendId); + writer.WriteEndElement(); // + } + + writer.WriteEndElement(); // + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs new file mode 100644 index 00000000..217609be --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs @@ -0,0 +1,101 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class ProcessPropertiesCommand + { + public ProcessPropertiesCommand(IntermediateSection section, WixPackageSymbol packageSymbol, int fallbackLcid, bool populateDelayedVariables, IBackendHelper backendHelper) + { + this.Section = section; + this.PackageSymbol = packageSymbol; + this.FallbackLcid = fallbackLcid; + this.PopulateDelayedVariables = populateDelayedVariables; + this.BackendHelper = backendHelper; + } + + private IntermediateSection Section { get; } + + private WixPackageSymbol PackageSymbol { get; } + + private int FallbackLcid { get; } + + private bool PopulateDelayedVariables { get; } + + private IBackendHelper BackendHelper { get; } + + public Dictionary DelayedVariablesCache { get; private set; } + + public string ProductLanguage { get; private set; } + + public void Execute() + { + PropertySymbol languageSymbol = null; + var variableCache = this.PopulateDelayedVariables ? new Dictionary(StringComparer.OrdinalIgnoreCase) : null; + + if (SectionType.Product == this.Section.Type || variableCache != null) + { + foreach (var propertySymbol in this.Section.Symbols.OfType()) + { + // Set the ProductCode if it is to be generated. + if ("ProductCode" == propertySymbol.Id.Id && "*".Equals(propertySymbol.Value, StringComparison.Ordinal)) + { + propertySymbol.Value = this.BackendHelper.CreateGuid(); + +#if TODO_PATCHING // Is this still necessary? + // Update the target ProductCode in any instance transforms. + foreach (SubStorage subStorage in this.Output.SubStorages) + { + Output subStorageOutput = subStorage.Data; + if (OutputType.Transform != subStorageOutput.Type) + { + continue; + } + + Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; + foreach (Row row in instanceSummaryInformationTable.Rows) + { + if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) + { + row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value); + break; + } + } + } +#endif + } + else if ("ProductLanguage" == propertySymbol.Id.Id) + { + languageSymbol = propertySymbol; + } + + // Add the property name and value to the variableCache. + if (variableCache != null) + { + variableCache[$"property.{propertySymbol.Id.Id}"] = propertySymbol.Value; + } + } + + if (this.Section.Type == SectionType.Product && String.IsNullOrEmpty(languageSymbol?.Value)) + { + if (languageSymbol == null) + { + languageSymbol = this.Section.AddSymbol(new PropertySymbol(this.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, "ProductLanguage"))); + } + + this.PackageSymbol.Language = this.FallbackLcid.ToString(); + languageSymbol.Value = this.FallbackLcid.ToString(); + } + } + + this.DelayedVariablesCache = variableCache; + this.ProductLanguage = languageSymbol?.Value; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs new file mode 100644 index 00000000..039ba495 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs @@ -0,0 +1,125 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Defines the file transfers necessary to layout the uncompressed files. + /// + internal class ProcessUncompressedFilesCommand + { + public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver) + { + this.Section = section; + this.BackendHelper = backendHelper; + this.PathResolver = pathResolver; + } + + private IntermediateSection Section { get; } + + public IBackendHelper BackendHelper { get; } + + public IPathResolver PathResolver { get; } + + public string DatabasePath { private get; set; } + + public IEnumerable FileFacades { private get; set; } + + public string LayoutDirectory { private get; set; } + + public bool Compressed { private get; set; } + + public bool LongNamesInImage { private get; set; } + + public Func ResolveMedia { private get; set; } + + public IEnumerable FileTransfers { get; private set; } + + public IEnumerable TrackedFiles { get; private set; } + + public void Execute() + { + var fileTransfers = new List(); + + var trackedFiles = new List(); + + var directories = new Dictionary(); + + var mediaRows = this.Section.Symbols.OfType().ToDictionary(t => t.DiskId); + + using (var db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) + { + using (var directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) + { + foreach (var directoryRecord in directoryView.Records) + { + var sourceName = this.BackendHelper.GetMsiFileName(directoryRecord.GetString(3), true, this.LongNamesInImage); + + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName); + + directories.Add(directoryRecord.GetString(1), resolvedDirectory); + } + } + + using (var fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) + { + using (var fileQueryRecord = new Record(1)) + { + // for each file in the array of uncompressed files + foreach (var facade in this.FileFacades) + { + var mediaSymbol = mediaRows[facade.DiskId]; + string relativeFileLayoutPath = null; + var mediaLayoutFolder = mediaSymbol.Layout; + + var mediaLayoutDirectory = this.ResolveMedia(mediaSymbol, mediaLayoutFolder, this.LayoutDirectory); + + // setup up the query record and find the appropriate file in the + // previously executed file view + fileQueryRecord[1] = facade.Id; + fileView.Execute(fileQueryRecord); + + using (var fileRecord = fileView.Fetch()) + { + if (null == fileRecord) + { + throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.SourceLineNumber, facade.Id)); + } + + relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); + } + + // finally put together the base media layout path and the relative file layout path + var fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); + + var transfer = this.BackendHelper.CreateFileTransfer(facade.SourcePath, fileLayoutPath, false, facade.SourceLineNumber); + fileTransfers.Add(transfer); + + // Track the location where the cabinet will be placed. If the transfer is + // redundant then then the file should not be cleaned. This is important + // because if the source and destination of the transfer is the same, we + // don't want to clean the file because we'd be deleting the original + // (and that would be bad). + var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.SourceLineNumber); + tracked.Clean = !transfer.Redundant; + + trackedFiles.Add(tracked); + } + } + } + } + + this.FileTransfers = fileTransfers; + this.TrackedFiles = trackedFiles; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs new file mode 100644 index 00000000..94fa0a6a --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs @@ -0,0 +1,714 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + /// + /// Set sequence numbers for all the actions and create symbols in the output object. + /// + internal class SequenceActionsCommand + { + public SequenceActionsCommand(IMessaging messaging, IntermediateSection section) + { + this.Messaging = messaging; + this.Section = section; + + this.RelativeActionsForActions = new Dictionary(); + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private Dictionary RelativeActionsForActions { get; } + + public void Execute() + { + var requiredActionSymbols = new Dictionary(); + + // Index all the action symbols and look for collisions. + foreach (var actionSymbol in this.Section.Symbols.OfType()) + { + if (actionSymbol.Overridable) // overridable action + { + if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol)) + { + if (collidingActionSymbol.Overridable) + { + this.Messaging.Write(ErrorMessages.OverridableActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + if (null != collidingActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(ErrorMessages.OverridableActionCollision2(collidingActionSymbol.SourceLineNumbers)); + } + } + } + else + { + requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); + } + } + else // unsequenced or sequenced action. + { + // Unsequenced action (allowed for certain standard actions). + if (null == actionSymbol.Before && null == actionSymbol.After && !actionSymbol.Sequence.HasValue) + { + if (WindowsInstallerStandard.TryGetStandardAction(actionSymbol.Id.Id, out var standardAction)) + { + // Populate the sequence from the standard action + actionSymbol.Sequence = standardAction.Sequence; + } + else // not a supported unscheduled action. + { + throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); + } + } + + if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol) && !collidingActionSymbol.Overridable) + { + this.Messaging.Write(ErrorMessages.ActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + if (null != collidingActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(ErrorMessages.ActionCollision2(collidingActionSymbol.SourceLineNumbers)); + } + } + else + { + requiredActionSymbols[actionSymbol.Id.Id] = actionSymbol; + } + } + } + + // Get the standard actions required based on symbols in the section. + var requiredStandardActions = this.GetRequiredStandardActions(); + + // Add the overridable action symbols that are not overridden to the required action symbols. + foreach (var actionSymbol in requiredStandardActions.Values) + { + if (!requiredActionSymbols.ContainsKey(actionSymbol.Id.Id)) + { + requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol); + } + } + + // Suppress the required actions that are overridable. + foreach (var suppressActionSymbol in this.Section.Symbols.OfType()) + { + var key = suppressActionSymbol.Id.Id; + + // If there is an overridable symbol to suppress; suppress it. There is no warning if there + // is no action to suppress because the action may be suppressed from a merge module in + // the binder. + if (requiredActionSymbols.TryGetValue(key, out var requiredActionSymbol)) + { + if (requiredActionSymbol.Overridable) + { + this.Messaging.Write(WarningMessages.SuppressAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.Action, suppressActionSymbol.SequenceTable.ToString())); + if (null != requiredActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(WarningMessages.SuppressAction2(requiredActionSymbol.SourceLineNumbers)); + } + + requiredActionSymbols.Remove(key); + } + else // suppressing a non-overridable action symbol + { + this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.SequenceTable.ToString(), suppressActionSymbol.Action)); + if (null != requiredActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction2(requiredActionSymbol.SourceLineNumbers)); + } + } + } + } + + // A dictionary used for detecting cyclic references among action symbols. + var firstReference = new Dictionary(); + + // Build up dependency trees of the relatively scheduled actions. + // Use ToList() to create a copy of the required action symbols so that new symbols can + // be added while enumerating. + foreach (var actionSymbol in requiredActionSymbols.Values.ToList()) + { + if (!actionSymbol.Sequence.HasValue) + { + // check for standard actions that don't have a sequence number in a merge module + if (SectionType.Module == this.Section.Type && WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) + { + this.Messaging.Write(ErrorMessages.StandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + } + + this.SequenceActionSymbol(actionSymbol, requiredActionSymbols, firstReference); + } + else if (SectionType.Module == this.Section.Type && 0 < actionSymbol.Sequence && !WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) // check for custom actions and dialogs that have a sequence number + { + this.Messaging.Write(ErrorMessages.CustomActionSequencedInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + } + } + + // Look for standard actions with sequence restrictions that aren't necessarily scheduled based + // on the presence of a particular table. + if (requiredActionSymbols.ContainsKey("InstallExecuteSequence/DuplicateFiles") && !requiredActionSymbols.ContainsKey("InstallExecuteSequence/InstallFiles")) + { + WindowsInstallerStandard.TryGetStandardAction("InstallExecuteSequence/InstallFiles", out var standardAction); + requiredActionSymbols.Add(standardAction.Id.Id, standardAction); + } + + // Schedule actions. + List scheduledActionSymbols; + if (SectionType.Module == this.Section.Type) + { + scheduledActionSymbols = requiredActionSymbols.Values.ToList(); + } + else + { + scheduledActionSymbols = this.ScheduleActions(requiredActionSymbols); + } + + // Remove all existing WixActionSymbols from the section then add the + // scheduled actions back to the section. + var removeActionSymbols = this.Section.Symbols.Where(s => s.Definition.Type == SymbolDefinitionType.WixAction).ToList(); + + foreach (var removeSymbol in removeActionSymbols) + { + this.Section.RemoveSymbol(removeSymbol); + } + + foreach (var action in scheduledActionSymbols) + { + this.Section.AddSymbol(action); + } + } + + private Dictionary GetRequiredStandardActions() + { + var overridableActionSymbols = new Dictionary(); + + var requiredActionIds = this.GetRequiredActionIds(); + + foreach (var actionId in requiredActionIds) + { + WindowsInstallerStandard.TryGetStandardAction(actionId, out var standardAction); + overridableActionSymbols.Add(standardAction.Id.Id, standardAction); + } + + return overridableActionSymbols; + } + + private List ScheduleActions(Dictionary requiredActionSymbols) + { + var scheduledActionSymbols = new List(); + + // Process each sequence table individually. + foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) + { + // Create a collection of just the action symbols in this sequence + var sequenceActionSymbols = requiredActionSymbols.Values.Where(a => a.SequenceTable == sequenceTable).ToList(); + + // Schedule the absolutely scheduled actions (by sorting them by their sequence numbers). + var absoluteActionSymbols = new List(); + foreach (var actionSymbol in sequenceActionSymbols) + { + if (actionSymbol.Sequence.HasValue) + { + // Look for sequence number collisions + foreach (var sequenceScheduledActionSymbol in absoluteActionSymbols) + { + if (sequenceScheduledActionSymbol.Sequence == actionSymbol.Sequence) + { + this.Messaging.Write(WarningMessages.ActionSequenceCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, sequenceScheduledActionSymbol.Action, actionSymbol.Sequence ?? 0)); + if (null != sequenceScheduledActionSymbol.SourceLineNumbers) + { + this.Messaging.Write(WarningMessages.ActionSequenceCollision2(sequenceScheduledActionSymbol.SourceLineNumbers)); + } + } + } + + absoluteActionSymbols.Add(actionSymbol); + } + } + + absoluteActionSymbols.Sort((x, y) => (x.Sequence ?? 0).CompareTo(y.Sequence ?? 0)); + + // Schedule the relatively scheduled actions (by resolving the dependency trees). + var previousUsedSequence = 0; + var relativeActionSymbols = new List(); + for (int j = 0; j < absoluteActionSymbols.Count; j++) + { + var absoluteActionSymbol = absoluteActionSymbols[j]; + + // Get all the relatively scheduled action symbols occuring before and after this absolutely scheduled action symbol. + var relativeActions = this.GetAllRelativeActionsForSequenceType(sequenceTable, absoluteActionSymbol); + + // Check for relatively scheduled actions occuring before/after a special action + // (those actions with a negative sequence number). + if (absoluteActionSymbol.Sequence < 0 && (relativeActions.PreviousActions.Any() || relativeActions.NextActions.Any())) + { + // Create errors for all the before actions. + foreach (var actionSymbol in relativeActions.PreviousActions) + { + this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); + } + + // Create errors for all the after actions. + foreach (var actionSymbol in relativeActions.NextActions) + { + this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action)); + } + + // If there is source line information for the absolutely scheduled action display it + if (absoluteActionSymbol.SourceLineNumbers != null) + { + this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction2(absoluteActionSymbol.SourceLineNumbers)); + } + + continue; + } + + // Schedule the action symbols before this one. + var unusedSequence = absoluteActionSymbol.Sequence - 1; + for (var i = relativeActions.PreviousActions.Count - 1; i >= 0; i--) + { + var relativeActionSymbol = relativeActions.PreviousActions[i]; + + // look for collisions + if (unusedSequence == previousUsedSequence) + { + this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); + if (absoluteActionSymbol.SourceLineNumbers != null) + { + this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); + } + + unusedSequence++; + } + + relativeActionSymbol.Sequence = unusedSequence; + relativeActionSymbols.Add(relativeActionSymbol); + + unusedSequence--; + } + + // Determine the next used action sequence number. + var nextUsedSequence = Int16.MaxValue + 1; + if (absoluteActionSymbols.Count > j + 1) + { + nextUsedSequence = absoluteActionSymbols[j + 1].Sequence ?? 0; + } + + // Schedule the action symbols after this one. + unusedSequence = absoluteActionSymbol.Sequence + 1; + for (var i = 0; i < relativeActions.NextActions.Count; i++) + { + var relativeActionSymbol = relativeActions.NextActions[i]; + + if (unusedSequence == nextUsedSequence) + { + this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action)); + if (absoluteActionSymbol.SourceLineNumbers != null) + { + this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers)); + } + + unusedSequence--; + } + + relativeActionSymbol.Sequence = unusedSequence; + relativeActionSymbols.Add(relativeActionSymbol); + + unusedSequence++; + } + + // keep track of this sequence number as the previous used sequence number for the next iteration + previousUsedSequence = absoluteActionSymbol.Sequence ?? 0; + } + + // add the absolutely and relatively scheduled actions to the list of scheduled actions + scheduledActionSymbols.AddRange(absoluteActionSymbols); + scheduledActionSymbols.AddRange(relativeActionSymbols); + } + + return scheduledActionSymbols; + } + + private IEnumerable GetRequiredActionIds() + { + var set = new HashSet(); + + // gather the required actions for the output type + if (SectionType.Product == this.Section.Type) + { + // AdminExecuteSequence table + set.Add("AdminExecuteSequence/CostFinalize"); + set.Add("AdminExecuteSequence/CostInitialize"); + set.Add("AdminExecuteSequence/FileCost"); + set.Add("AdminExecuteSequence/InstallAdminPackage"); + set.Add("AdminExecuteSequence/InstallFiles"); + set.Add("AdminExecuteSequence/InstallFinalize"); + set.Add("AdminExecuteSequence/InstallInitialize"); + set.Add("AdminExecuteSequence/InstallValidate"); + + // AdminUISequence table + set.Add("AdminUISequence/CostFinalize"); + set.Add("AdminUISequence/CostInitialize"); + set.Add("AdminUISequence/ExecuteAction"); + set.Add("AdminUISequence/FileCost"); + + // AdvtExecuteSequence table + set.Add("AdvertiseExecuteSequence/CostFinalize"); + set.Add("AdvertiseExecuteSequence/CostInitialize"); + set.Add("AdvertiseExecuteSequence/InstallInitialize"); + set.Add("AdvertiseExecuteSequence/InstallFinalize"); + set.Add("AdvertiseExecuteSequence/InstallValidate"); + set.Add("AdvertiseExecuteSequence/PublishFeatures"); + set.Add("AdvertiseExecuteSequence/PublishProduct"); + + // InstallExecuteSequence table + set.Add("InstallExecuteSequence/CostFinalize"); + set.Add("InstallExecuteSequence/CostInitialize"); + set.Add("InstallExecuteSequence/FileCost"); + set.Add("InstallExecuteSequence/InstallFinalize"); + set.Add("InstallExecuteSequence/InstallInitialize"); + set.Add("InstallExecuteSequence/InstallValidate"); + set.Add("InstallExecuteSequence/ProcessComponents"); + set.Add("InstallExecuteSequence/PublishFeatures"); + set.Add("InstallExecuteSequence/PublishProduct"); + set.Add("InstallExecuteSequence/RegisterProduct"); + set.Add("InstallExecuteSequence/RegisterUser"); + set.Add("InstallExecuteSequence/UnpublishFeatures"); + set.Add("InstallExecuteSequence/ValidateProductID"); + + // InstallUISequence table + set.Add("InstallUISequence/CostFinalize"); + set.Add("InstallUISequence/CostInitialize"); + set.Add("InstallUISequence/ExecuteAction"); + set.Add("InstallUISequence/FileCost"); + set.Add("InstallUISequence/ValidateProductID"); + } + + // Gather the required actions for each symbol type. + foreach (var symbolType in this.Section.Symbols.Select(t => t.Definition.Type).Distinct()) + { + switch (symbolType) + { + case SymbolDefinitionType.AppSearch: + set.Add("InstallExecuteSequence/AppSearch"); + set.Add("InstallUISequence/AppSearch"); + break; + case SymbolDefinitionType.CCPSearch: + set.Add("InstallExecuteSequence/AppSearch"); + set.Add("InstallExecuteSequence/CCPSearch"); + set.Add("InstallExecuteSequence/RMCCPSearch"); + set.Add("InstallUISequence/AppSearch"); + set.Add("InstallUISequence/CCPSearch"); + set.Add("InstallUISequence/RMCCPSearch"); + break; + case SymbolDefinitionType.Class: + set.Add("AdvertiseExecuteSequence/RegisterClassInfo"); + set.Add("InstallExecuteSequence/RegisterClassInfo"); + set.Add("InstallExecuteSequence/UnregisterClassInfo"); + break; + case SymbolDefinitionType.Complus: + set.Add("InstallExecuteSequence/RegisterComPlus"); + set.Add("InstallExecuteSequence/UnregisterComPlus"); + break; + case SymbolDefinitionType.Component: + case SymbolDefinitionType.CreateFolder: + set.Add("InstallExecuteSequence/CreateFolders"); + set.Add("InstallExecuteSequence/RemoveFolders"); + break; + case SymbolDefinitionType.DuplicateFile: + set.Add("InstallExecuteSequence/DuplicateFiles"); + set.Add("InstallExecuteSequence/RemoveDuplicateFiles"); + break; + case SymbolDefinitionType.Environment: + set.Add("InstallExecuteSequence/WriteEnvironmentStrings"); + set.Add("InstallExecuteSequence/RemoveEnvironmentStrings"); + break; + case SymbolDefinitionType.Extension: + set.Add("AdvertiseExecuteSequence/RegisterExtensionInfo"); + set.Add("InstallExecuteSequence/RegisterExtensionInfo"); + set.Add("InstallExecuteSequence/UnregisterExtensionInfo"); + break; + case SymbolDefinitionType.File: + set.Add("InstallExecuteSequence/InstallFiles"); + set.Add("InstallExecuteSequence/RemoveFiles"); + + var foundFont = false; + var foundSelfReg = false; + var foundBindPath = false; + foreach (var file in this.Section.Symbols.OfType()) + { + if (!foundFont && !String.IsNullOrEmpty(file.FontTitle)) + { + set.Add("InstallExecuteSequence/RegisterFonts"); + set.Add("InstallExecuteSequence/UnregisterFonts"); + foundFont = true; + } + + if (!foundSelfReg && file.SelfRegCost.HasValue) + { + set.Add("InstallExecuteSequence/SelfRegModules"); + set.Add("InstallExecuteSequence/SelfUnregModules"); + foundSelfReg = true; + } + + if (!foundBindPath && !String.IsNullOrEmpty(file.BindPath)) + { + set.Add("InstallExecuteSequence/BindImage"); + foundBindPath = true; + } + } + break; + case SymbolDefinitionType.IniFile: + set.Add("InstallExecuteSequence/WriteIniValues"); + set.Add("InstallExecuteSequence/RemoveIniValues"); + break; + case SymbolDefinitionType.IsolatedComponent: + set.Add("InstallExecuteSequence/IsolateComponents"); + break; + case SymbolDefinitionType.LaunchCondition: + set.Add("InstallExecuteSequence/LaunchConditions"); + set.Add("InstallUISequence/LaunchConditions"); + break; + case SymbolDefinitionType.MIME: + set.Add("AdvertiseExecuteSequence/RegisterMIMEInfo"); + set.Add("InstallExecuteSequence/RegisterMIMEInfo"); + set.Add("InstallExecuteSequence/UnregisterMIMEInfo"); + break; + case SymbolDefinitionType.MoveFile: + set.Add("InstallExecuteSequence/MoveFiles"); + break; + case SymbolDefinitionType.Assembly: + set.Add("AdvertiseExecuteSequence/MsiPublishAssemblies"); + set.Add("InstallExecuteSequence/MsiPublishAssemblies"); + set.Add("InstallExecuteSequence/MsiUnpublishAssemblies"); + break; + case SymbolDefinitionType.MsiServiceConfig: + case SymbolDefinitionType.MsiServiceConfigFailureActions: + set.Add("InstallExecuteSequence/MsiConfigureServices"); + break; + case SymbolDefinitionType.ODBCDataSource: + case SymbolDefinitionType.ODBCTranslator: + case SymbolDefinitionType.ODBCDriver: + set.Add("InstallExecuteSequence/SetODBCFolders"); + set.Add("InstallExecuteSequence/InstallODBC"); + set.Add("InstallExecuteSequence/RemoveODBC"); + break; + case SymbolDefinitionType.ProgId: + set.Add("AdvertiseExecuteSequence/RegisterProgIdInfo"); + set.Add("InstallExecuteSequence/RegisterProgIdInfo"); + set.Add("InstallExecuteSequence/UnregisterProgIdInfo"); + break; + case SymbolDefinitionType.PublishComponent: + set.Add("AdvertiseExecuteSequence/PublishComponents"); + set.Add("InstallExecuteSequence/PublishComponents"); + set.Add("InstallExecuteSequence/UnpublishComponents"); + break; + case SymbolDefinitionType.Registry: + case SymbolDefinitionType.RemoveRegistry: + set.Add("InstallExecuteSequence/WriteRegistryValues"); + set.Add("InstallExecuteSequence/RemoveRegistryValues"); + break; + case SymbolDefinitionType.RemoveFile: + set.Add("InstallExecuteSequence/RemoveFiles"); + break; + case SymbolDefinitionType.ServiceControl: + set.Add("InstallExecuteSequence/StartServices"); + set.Add("InstallExecuteSequence/StopServices"); + set.Add("InstallExecuteSequence/DeleteServices"); + break; + case SymbolDefinitionType.ServiceInstall: + set.Add("InstallExecuteSequence/InstallServices"); + break; + case SymbolDefinitionType.Shortcut: + set.Add("AdvertiseExecuteSequence/CreateShortcuts"); + set.Add("InstallExecuteSequence/CreateShortcuts"); + set.Add("InstallExecuteSequence/RemoveShortcuts"); + break; + case SymbolDefinitionType.TypeLib: + set.Add("InstallExecuteSequence/RegisterTypeLibraries"); + set.Add("InstallExecuteSequence/UnregisterTypeLibraries"); + break; + case SymbolDefinitionType.Upgrade: + set.Add("InstallExecuteSequence/FindRelatedProducts"); + set.Add("InstallUISequence/FindRelatedProducts"); + + // Only add the MigrateFeatureStates action if MigrateFeature attribute is set on + // at least one UpgradeVersion element. + if (this.Section.Symbols.OfType().Any(t => t.MigrateFeatures)) + { + set.Add("InstallExecuteSequence/MigrateFeatureStates"); + set.Add("InstallUISequence/MigrateFeatureStates"); + } + break; + } + } + + return set; + } + + /// + /// Sequence an action before or after a standard action. + /// + /// The action symbol to be sequenced. + /// Collection of actions which must be included. + /// A dictionary used for detecting cyclic references among action symbols. + private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) + { + var after = false; + + if (actionSymbol.After != null) + { + after = true; + } + else if (actionSymbol.Before == null) + { + throw new WixException($"Found action '{actionSymbol.Id.Id}' at {actionSymbol.SourceLineNumbers}' with no Sequence, Before, or After column set. The compiler should have prevented this."); + } + + var parentActionName = (after ? actionSymbol.After : actionSymbol.Before); + var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + parentActionName; + + if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) + { + // If the missing parent action is a standard action (with a suggested sequence number), add it. + if (WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol)) + { + // Create a clone to avoid modifying the static copy of the object. + // TODO: consider this: parentActionSymbol = parentActionSymbol.Clone(); + + requiredActionSymbols.Add(parentActionSymbol.Id.Id, parentActionSymbol); + } + else + { + throw new WixException($"Found action {actionSymbol.Id.Id} with a non-existent {(after ? "After" : "Before")} action '{parentActionName}'. The linker should have prevented this."); + } + } + + this.CheckForCircularActionReference(actionSymbol, requiredActionSymbols, firstReference); + + // Add this action to the appropriate list of dependent action symbols. + var relativeActions = this.GetRelativeActions(parentActionSymbol); + var relatedSymbols = (after ? relativeActions.NextActions : relativeActions.PreviousActions); + relatedSymbols.Add(actionSymbol); + } + + /// + /// Check the specified action symbol to see if it leads to a cycle. + /// + /// Use the provided dictionary to note the initial action symbol that first led to each action + /// symbol. Any action symbol encountered that has already been encountered starting from a different + /// initial action symbol inherits the loop characteristics of that initial action symbol, and thus is + /// also not part of a cycle. However, any action symbol encountered that has already been encountered + /// starting from the same initial action symbol is an indication that the current action symbol is + /// part of a cycle. + /// + /// The action symbol to be checked. + /// Collection of actions which must be included. + /// The first encountered action symbol that led to each action symbol. + private void CheckForCircularActionReference(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols, Dictionary firstReference) + { + WixActionSymbol currentActionSymbol = null; + var parentActionSymbol = actionSymbol; + + do + { + var previousActionSymbol = currentActionSymbol ?? parentActionSymbol; + currentActionSymbol = parentActionSymbol; + + if (!firstReference.TryGetValue(currentActionSymbol, out var existingInitialActionSymbol)) + { + firstReference[currentActionSymbol] = actionSymbol; + } + else if (existingInitialActionSymbol == actionSymbol) + { + this.Messaging.Write(ErrorMessages.ActionCircularDependency(currentActionSymbol.SourceLineNumbers, currentActionSymbol.SequenceTable.ToString(), currentActionSymbol.Action, previousActionSymbol.Action)); + } + + parentActionSymbol = this.GetParentActionSymbol(currentActionSymbol, requiredActionSymbols); + } while (null != parentActionSymbol && !this.Messaging.EncounteredError); + } + + /// + /// Get the action symbol that is the parent of the given action symbol. + /// + /// The given action symbol. + /// Collection of actions which must be included. + /// Null if there is no parent. Used for loop termination. + private WixActionSymbol GetParentActionSymbol(WixActionSymbol actionSymbol, Dictionary requiredActionSymbols) + { + if (null == actionSymbol.Before && null == actionSymbol.After) + { + return null; + } + + var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + (actionSymbol.After ?? actionSymbol.Before); + + if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol)) + { + WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol); + } + + return parentActionSymbol; + } + + + private RelativeActions GetRelativeActions(WixActionSymbol action) + { + if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions)) + { + relativeActions = new RelativeActions(); + this.RelativeActionsForActions.Add(action.Id.Id, relativeActions); + } + + return relativeActions; + } + + private RelativeActions GetAllRelativeActionsForSequenceType(SequenceTable sequenceType, WixActionSymbol action) + { + var relativeActions = new RelativeActions(); + + if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) + { + this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, relativeActions.PreviousActions); + + this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, relativeActions.NextActions); + } + + return relativeActions; + } + + private void RecurseRelativeActionsForSequenceType(SequenceTable sequenceType, List actions, List visitedActions) + { + foreach (var action in actions.Where(a => a.SequenceTable == sequenceType)) + { + if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives)) + { + this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, visitedActions); + } + + visitedActions.Add(action); + + if (actionRelatives != null) + { + this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, visitedActions); + } + } + } + + private class RelativeActions + { + public List PreviousActions { get; } = new List(); + + public List NextActions { get; } = new List(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs new file mode 100644 index 00000000..0f77abfc --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -0,0 +1,365 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Update file information. + /// + internal class UpdateFileFacadesCommand + { + public UpdateFileFacadesCommand(IMessaging messaging, IntermediateSection section, IEnumerable fileFacades, IEnumerable updateFileFacades, IDictionary variableCache, bool overwriteHash) + { + this.Messaging = messaging; + this.Section = section; + this.FileFacades = fileFacades; + this.UpdateFileFacades = updateFileFacades; + this.VariableCache = variableCache; + this.OverwriteHash = overwriteHash; + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + private IEnumerable FileFacades { get; } + + private IEnumerable UpdateFileFacades { get; } + + private bool OverwriteHash { get; } + + private IDictionary VariableCache { get; } + + public void Execute() + { + var assemblyNameSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + + foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) + { + this.UpdateFileFacade(file, assemblyNameSymbols); + } + } + + private void UpdateFileFacade(IFileFacade facade, Dictionary assemblyNameSymbols) + { + FileInfo fileInfo = null; + try + { + fileInfo = new FileInfo(facade.SourcePath); + } + catch (ArgumentException) + { + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); + return; + } + catch (PathTooLongException) + { + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); + return; + } + catch (NotSupportedException) + { + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); + return; + } + + if (!fileInfo.Exists) + { + this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath)); + return; + } + + using (var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + if (Int32.MaxValue < fileStream.Length) + { + throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath)); + } + + facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); + } + + string version = null; + string language = null; + try + { + Installer.GetFileVersion(fileInfo.FullName, out version, out language); + } + catch (Win32Exception e) + { + if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND + { + throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); + } + else + { + throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message)); + } + } + + // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. + if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table + { + if (!this.OverwriteHash) + { + // not overwriting hash, so don't do the rest of these options. + } + else if (null != facade.Version) + { + // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks + // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up. + // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing + // all the file rows) for a relatively uncommon situation. Let's not do that. + // + // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version + // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user. + if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) + { + this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); + } + } + else + { + if (null != facade.Language) + { + this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); + } + + int[] hash; + try + { + Installer.GetFileHash(fileInfo.FullName, 0, out hash); + } + catch (Win32Exception e) + { + if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND + { + throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); + } + else + { + throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); + } + } + + if (null == facade.Hash) + { + facade.Hash = this.Section.AddSymbol(new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)); + } + + facade.Hash.Options = 0; + facade.Hash.HashPart1 = hash[0]; + facade.Hash.HashPart2 = hash[1]; + facade.Hash.HashPart3 = hash[2]; + facade.Hash.HashPart4 = hash[3]; + } + } + else // update the file row with the version and language information. + { + // If no version was provided by the user, use the version from the file itself. + // This is the most common case. + if (String.IsNullOrEmpty(facade.Version)) + { + facade.Version = version; + } + else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below. + { + // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching + // the version value). We didn't find it so, we will override the default version they provided with the actual + // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match + // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case + // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more + // CPU intensive search to save on the memory intensive index that wouldn't be used much. + // + // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism. + // That's typically even more rare than companion files so again, no index, just search. + facade.Version = version; + } + + if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language)) + { + this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); + } + else // override the default provided by the user (usually nothing) with the actual language from the file itself. + { + facade.Language = language; + } + + // Populate the binder variables for this file information if requested. + if (null != this.VariableCache) + { + if (!String.IsNullOrEmpty(facade.Version)) + { + var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.Id); + this.VariableCache[key] = facade.Version; + } + + if (!String.IsNullOrEmpty(facade.Language)) + { + var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.Id); + this.VariableCache[key] = facade.Language; + } + } + } + + // If this is a CLR assembly, load the assembly and get the assembly name information + if (AssemblyType.DotNetAssembly == facade.AssemblyType) + { + try + { + var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version); + + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "culture", assemblyName.Culture); + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); + + if (!String.IsNullOrEmpty(assemblyName.Architecture)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); + } + // TODO: WiX v3 seemed to do this but not clear it should actually be done. + //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) + //{ + // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); + //} + + if (assemblyName.StrongNamedSigned) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); + } + else if (facade.AssemblyApplicationFileRef == null) + { + throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef)); + } + + if (!String.IsNullOrEmpty(assemblyName.FileVersion)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "fileVersion", assemblyName.FileVersion); + } + + // add the assembly name to the information cache + if (null != this.VariableCache) + { + this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName(); + } + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + } + else if (AssemblyType.Win32Assembly == facade.AssemblyType) + { + // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through + // all files like this. Even though this is a rare case it looks like we might be able to index the + // file earlier. + var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal)); + if (null == fileManifest) + { + this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef)); + } + + try + { + var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath); + + if (!String.IsNullOrEmpty(assemblyName.Name)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name); + } + + if (!String.IsNullOrEmpty(assemblyName.Version)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version); + } + + if (!String.IsNullOrEmpty(assemblyName.Type)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "type", assemblyName.Type); + } + + if (!String.IsNullOrEmpty(assemblyName.Architecture)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture); + } + + if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) + { + this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken); + } + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + } + } + + /// + /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise + /// create a new row. + /// + /// MsiAssemblyName table. + /// FileFacade containing the assembly read for the MsiAssemblyName row. + /// MsiAssemblyName name. + /// MsiAssemblyName value. + private void SetMsiAssemblyName(Dictionary assemblyNameSymbols, IFileFacade facade, string name, string value) + { + // check for null value (this can occur when grabbing the file version from an assembly without one) + if (String.IsNullOrEmpty(value)) + { + this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name)); + } + else + { + // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail. + if ("name" == name && AssemblyType.DotNetAssembly == facade.AssemblyType && + String.IsNullOrEmpty(facade.AssemblyApplicationFileRef) && + !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value)); + } + + // override directly authored value + var lookup = String.Concat(facade.ComponentRef, "/", name); + if (!assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol)) + { + assemblyNameSymbol = this.Section.AddSymbol(new MsiAssemblyNameSymbol(facade.SourceLineNumber, new Identifier(AccessModifier.Section, facade.ComponentRef, name)) + { + ComponentRef = facade.ComponentRef, + Name = name, + Value = value, + }); + + if (null == facade.AssemblyNames) + { + facade.AssemblyNames = new List(); + } + + facade.AssemblyNames.Add(assemblyNameSymbol); + + assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol); + } + + assemblyNameSymbol.Value = value; + + if (this.VariableCache != null) + { + var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant(); + this.VariableCache[key] = value; + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs new file mode 100644 index 00000000..66a648cc --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs @@ -0,0 +1,77 @@ +// 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.Bind +{ + using System; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class UpdateFromTextFilesCommand + { + public UpdateFromTextFilesCommand(IMessaging messaging, IntermediateSection section) + { + this.Messaging = messaging; + this.Section = section; + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public void Execute() + { + foreach (var bbControl in this.Section.Symbols.OfType().Where(t => t.SourceFile != null)) + { + bbControl.Text = this.ReadTextFile(bbControl.SourceLineNumbers, bbControl.SourceFile.Path); + } + + foreach (var control in this.Section.Symbols.OfType().Where(t => t.SourceFile != null)) + { + control.Text = this.ReadTextFile(control.SourceLineNumbers, control.SourceFile.Path); + } + + foreach (var customAction in this.Section.Symbols.OfType().Where(c => c.ScriptFile != null)) + { + customAction.Target = this.ReadTextFile(customAction.SourceLineNumbers, customAction.ScriptFile.Path); + } + } + + /// + /// Reads a text file and returns the contents. + /// + /// Source line numbers for row from source. + /// Source path to file to read. + /// Text string read from file. + private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source) + { + try + { + using (var reader = new StreamReader(source)) + { + return reader.ReadToEnd(); + } + } + catch (DirectoryNotFoundException e) + { + this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); + } + catch (FileNotFoundException e) + { + this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); + } + catch (IOException e) + { + this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message)); + } + catch (NotSupportedException) + { + this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, source)); + } + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs new file mode 100644 index 00000000..affec09f --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs @@ -0,0 +1,109 @@ +// 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.Bind +{ + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + + internal class UpdateMediaSequencesCommand + { + public UpdateMediaSequencesCommand(IntermediateSection section, IEnumerable fileFacades) + { + this.Section = section; + this.FileFacades = fileFacades; + } + + private IntermediateSection Section { get; } + + private IEnumerable FileFacades { get; } + + public void Execute() + { + var mediaRows = this.Section.Symbols.OfType().ToDictionary(t => t.DiskId); + + // Calculate sequence numbers and media disk id layout for all file media information objects. + if (SectionType.Module == this.Section.Type) + { + var lastSequence = 0; + + foreach (var facade in this.FileFacades) + { + facade.Sequence = ++lastSequence; + } + } + else + { + var lastSequence = 0; + MediaSymbol mediaSymbol = null; + var patchGroups = new Dictionary>(); + + // Sequence the non-patch-added files. + foreach (var facade in this.FileFacades) + { + if (null == mediaSymbol) + { + mediaSymbol = mediaRows[facade.DiskId]; + if (SectionType.Patch == this.Section.Type) + { + // patch Media cannot start at zero + lastSequence = mediaSymbol.LastSequence ?? 1; + } + } + else if (mediaSymbol.DiskId != facade.DiskId) + { + mediaSymbol.LastSequence = lastSequence; + mediaSymbol = mediaRows[facade.DiskId]; + } + + if (facade.PatchGroup.HasValue) + { + if (patchGroups.TryGetValue(facade.PatchGroup.Value, out var patchGroup)) + { + patchGroup = new List(); + patchGroups.Add(facade.PatchGroup.Value, patchGroup); + } + + patchGroup.Add(facade); + } + else if (!facade.FromModule) + { + facade.Sequence = ++lastSequence; + } + } + + if (null != mediaSymbol) + { + mediaSymbol.LastSequence = lastSequence; + mediaSymbol = null; + } + + // Sequence the patch-added files. + foreach (var patchGroup in patchGroups.Values) + { + foreach (var facade in patchGroup) + { + if (null == mediaSymbol) + { + mediaSymbol = mediaRows[facade.DiskId]; + } + else if (mediaSymbol.DiskId != facade.DiskId) + { + mediaSymbol.LastSequence = lastSequence; + mediaSymbol = mediaRows[facade.DiskId]; + } + + facade.Sequence = ++lastSequence; + } + } + + if (null != mediaSymbol) + { + mediaSymbol.LastSequence = lastSequence; + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs new file mode 100644 index 00000000..981fa0a4 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs @@ -0,0 +1,451 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class UpdateTransformsWithFileFacades + { + public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable subStorages, TableDefinitionCollection tableDefinitions, IEnumerable fileFacades) + { + this.Messaging = messaging; + this.Output = output; + this.SubStorages = subStorages; + this.TableDefinitions = tableDefinitions; + this.FileFacades = fileFacades; + } + + private IMessaging Messaging { get; } + + private WindowsInstallerData Output { get; } + + private IEnumerable SubStorages { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private IEnumerable FileFacades { get; } + + public void Execute() + { + var fileFacadesByDiskId = new Dictionary>(); + + // Index patch file facades by diskId+fileId. + foreach (var facade in this.FileFacades) + { + if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades)) + { + mediaFacades = new Dictionary(); + fileFacadesByDiskId.Add(facade.DiskId, mediaFacades); + } + + mediaFacades.Add(facade.Id, facade); + } + + var patchMediaRows = new RowDictionary(this.Output.Tables["Media"]); + + // Index paired transforms by name without the "#" prefix. + var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); + + // Copy File bind data into substorages + foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) + { + var mainTransform = substorage.Data; + + var mainMsiFileHashIndex = new RowDictionary(mainTransform.Tables["MsiFileHash"]); + + var pairedTransform = pairedTransforms["#" + substorage.Name]; + + // Copy Media.LastSequence. + var pairedMediaTable = pairedTransform.Tables["Media"]; + foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) + { + var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); + pairedMediaRow.LastSequence = patchMediaRow.LastSequence; + } + + // Validate file row changes for keypath-related issues + this.ValidateFileRowChanges(mainTransform); + + // Index File table of pairedTransform + var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); + + var mainFileTable = mainTransform.Tables["File"]; + if (null != mainFileTable) + { + // Remove the MsiFileHash table because it will be updated later with the final file hash for each file + mainTransform.Tables.Remove("MsiFileHash"); + + foreach (FileRow mainFileRow in mainFileTable.Rows) + { + if (RowOperation.Delete == mainFileRow.Operation) + { + continue; + } + else if (RowOperation.None == mainFileRow.Operation) + { + continue; + } + + // Index patch files by diskId+fileId + if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades)) + { + mediaFacades = new Dictionary(); + fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades); + } + + // copy data from the patch back to the transform + if (mediaFacades.TryGetValue(mainFileRow.File, out var facade)) + { + var patchFileRow = facade.GetFileRow(); + var pairedFileRow = pairedFileRows.Get(mainFileRow.File); + + for (var i = 0; i < patchFileRow.Fields.Length; i++) + { + var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty; + var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty; + + if (1 == i) + { + // File.Component_ changes should not come from the shared file rows + // that contain the file information as each individual transform might + // have different changes (or no changes at all). + } + else if (6 == i) // File.Attributes should not changed for binary deltas + { +#if TODO_PATCHING_DELTA + if (null != patchFileRow.Patch) + { + // File.Attribute should not change for binary deltas + pairedFileRow.Attributes = mainFileRow.Attributes; + mainFileRow.Fields[i].Modified = false; + } +#endif + } + else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform + { + // file sequence is updated in Patch table instead of File table for delta patches +#if TODO_PATCHING_DELTA + if (null != patchFileRow.Patch) + { + pairedFileRow.Fields[i].Modified = false; + } + else +#endif + { + pairedFileRow[i] = patchFileRow[i]; + pairedFileRow.Fields[i].Modified = true; + } + mainFileRow.Fields[i].Modified = false; + } + else if (patchValue != mainValue) + { + mainFileRow[i] = patchFileRow[i]; + mainFileRow.Fields[i].Modified = true; + if (mainFileRow.Operation == RowOperation.None) + { + mainFileRow.Operation = RowOperation.Modify; + } + } + } + + // Copy MsiFileHash row for this File. + if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) + { + //patchHashRow = patchFileRow.Hash; + throw new NotImplementedException(); + } + + if (null != patchHashRow) + { + var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); + var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); + for (var i = 0; i < patchHashRow.Fields.Length; i++) + { + mainHashRow[i] = patchHashRow[i]; + if (i > 1) + { + // assume all hash fields have been modified + mainHashRow.Fields[i].Modified = true; + } + } + + // assume the MsiFileHash operation follows the File one + mainHashRow.Operation = mainFileRow.Operation; + } + + // copy MsiAssemblyName rows for this File +#if TODO_PATCHING + List patchAssemblyNameRows = patchFileRow.AssemblyNames; + if (null != patchAssemblyNameRows) + { + var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); + foreach (var patchAssemblyNameRow in patchAssemblyNameRows) + { + // Copy if there isn't an identical modified/added row already in the transform. + var foundMatchingModifiedRow = false; + foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows) + { + if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) + { + foundMatchingModifiedRow = true; + break; + } + } + + if (!foundMatchingModifiedRow) + { + var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); + for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++) + { + mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; + } + + // assume value field has been modified + mainAssemblyNameRow.Fields[2].Modified = true; + mainAssemblyNameRow.Operation = mainFileRow.Operation; + } + } + } +#endif + + // Add patch header for this file +#if TODO_PATCHING_DELTA + if (null != patchFileRow.Patch) + { + // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. + this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); + this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); + + // Add to Patch table + var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); + if (0 == patchTable.Rows.Count) + { + patchTable.Operation = TableOperation.Add; + } + + var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); + patchRow[0] = patchFileRow.File; + patchRow[1] = patchFileRow.Sequence; + + var patchFile = new FileInfo(patchFileRow.Source); + patchRow[2] = (int)patchFile.Length; + patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; + + var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; + if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) + { + streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); + + var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); + if (0 == patchHeadersTable.Rows.Count) + { + patchHeadersTable.Operation = TableOperation.Add; + } + + var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); + patchHeadersRow[0] = streamName; + patchHeadersRow[1] = patchFileRow.Patch; + patchRow[5] = streamName; + patchHeadersRow.Operation = RowOperation.Add; + } + else + { + patchRow[4] = patchFileRow.Patch; + } + patchRow.Operation = RowOperation.Add; + } +#endif + } + else + { + // TODO: throw because all transform rows should have made it into the patch + } + } + } + + this.Output.Tables.Remove("Media"); + this.Output.Tables.Remove("File"); + this.Output.Tables.Remove("MsiFileHash"); + this.Output.Tables.Remove("MsiAssemblyName"); + } + } + + /// + /// Adds the PatchFiles action to the sequence table if it does not already exist. + /// + /// The sequence table to check or modify. + /// The primary authoring transform. + /// The secondary patch transform. + /// The file row that contains information about the patched file. + private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow) + { + var tableName = table.ToString(); + + // Find/add PatchFiles action (also determine sequence for it). + // Search mainTransform first, then pairedTransform (pairedTransform overrides). + var hasPatchFilesAction = false; + var installFilesSequence = 0; + var duplicateFilesSequence = 0; + + TestSequenceTableForPatchFilesAction( + mainTransform.Tables[tableName], + ref hasPatchFilesAction, + ref installFilesSequence, + ref duplicateFilesSequence); + TestSequenceTableForPatchFilesAction( + pairedTransform.Tables[tableName], + ref hasPatchFilesAction, + ref installFilesSequence, + ref duplicateFilesSequence); + if (!hasPatchFilesAction) + { + WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionSymbol); + + var sequence = patchFilesActionSymbol.Sequence; + + // Test for default sequence value's appropriateness + if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence)) + { + if (0 != duplicateFilesSequence) + { + if (duplicateFilesSequence < installFilesSequence) + { + throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action)); + } + else + { + sequence = (duplicateFilesSequence + installFilesSequence) / 2; + if (installFilesSequence == sequence || duplicateFilesSequence == sequence) + { + throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action)); + } + } + } + else + { + sequence = installFilesSequence + 1; + } + } + + var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); + if (0 == sequenceTable.Rows.Count) + { + sequenceTable.Operation = TableOperation.Add; + } + + var patchAction = sequenceTable.CreateRow(null); + patchAction[0] = patchFilesActionSymbol.Action; + patchAction[1] = patchFilesActionSymbol.Condition; + patchAction[2] = sequence; + patchAction.Operation = RowOperation.Add; + } + } + + /// + /// Tests sequence table for PatchFiles and associated actions + /// + /// The table to test. + /// Set to true if PatchFiles action is found. Left unchanged otherwise. + /// Set to sequence value of InstallFiles action if found. Left unchanged otherwise. + /// Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise. + private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence) + { + if (null != sequenceTable) + { + foreach (var row in sequenceTable.Rows) + { + var actionName = row.FieldAsString(0); + switch (actionName) + { + case "PatchFiles": + hasPatchFilesAction = true; + break; + + case "InstallFiles": + installFilesSequence = row.FieldAsInteger(2); + break; + + case "DuplicateFiles": + duplicateFilesSequence = row.FieldAsInteger(2); + break; + } + } + } + } + + /// + /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. + /// + /// The output to validate. + private void ValidateFileRowChanges(WindowsInstallerData transform) + { + var componentTable = transform.Tables["Component"]; + var fileTable = transform.Tables["File"]; + + // There's no sense validating keypaths if the transform has no component or file table + if (componentTable == null || fileTable == null) + { + return; + } + + var componentKeyPath = new Dictionary(componentTable.Rows.Count); + + // Index the Component table for non-directory & non-registry key paths. + foreach (var row in componentTable.Rows) + { + var keyPath = row.FieldAsString(5); + if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) + { + componentKeyPath.Add(row.FieldAsString(0), keyPath); + } + } + + var componentWithChangedKeyPath = new Dictionary(); + var componentWithNonKeyPathChanged = new Dictionary(); + // Verify changes in the file table, now that file diffing has occurred + foreach (FileRow row in fileTable.Rows) + { + if (RowOperation.Modify != row.Operation) + { + continue; + } + + var fileId = row.FieldAsString(0); + var componentId = row.FieldAsString(1); + + // If this file is the keypath of a component + if (componentKeyPath.ContainsValue(fileId)) + { + if (!componentWithChangedKeyPath.ContainsKey(componentId)) + { + componentWithChangedKeyPath.Add(componentId, fileId); + } + } + else + { + if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) + { + componentWithNonKeyPathChanged.Add(componentId, fileId); + } + } + } + + foreach (var componentFile in componentWithNonKeyPathChanged) + { + // Make sure all changes to non keypath files also had a change in the keypath. + if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath)) + { + this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath)); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs new file mode 100644 index 00000000..cf1e21c2 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs @@ -0,0 +1,187 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ValidateDatabaseCommand : IWindowsInstallerValidatorCallback + { + // Set of ICEs that have equivalent-or-better checks in WiX. + private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" }; + + public ValidateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable cubeFiles, IEnumerable ices, IEnumerable suppressedIces) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Data = data; + this.OutputPath = outputPath; + this.CubeFiles = cubeFiles; + this.Ices = ices; + this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces); + + this.IntermediateFolder = intermediateFolder; + this.OutputSourceLineNumber = new SourceLineNumber(outputPath); + } + + public IEnumerable TrackedFiles { get; private set; } + + /// + /// Encountered error implementation for . + /// + public bool EncounteredError => this.Messaging.EncounteredError; + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private WindowsInstallerData Data { get; } + + private string OutputPath { get; } + + private IEnumerable CubeFiles { get; } + + private IEnumerable Ices { get; } + + private IEnumerable SuppressedIces { get; } + + private string IntermediateFolder { get; } + + /// + /// Fallback when an exact source line number cannot be calculated for a validation error. + /// + private SourceLineNumber OutputSourceLineNumber { get; set; } + + private Dictionary SourceLineNumbersByTablePrimaryKey { get; set; } + + public void Execute() + { + var trackedFiles = new List(); + var stopwatch = Stopwatch.StartNew(); + + this.Messaging.Write(VerboseMessages.ValidatingDatabase()); + + // Ensure the temporary files can be created the working folder. + var workingFolder = Path.Combine(this.IntermediateFolder, "_validate"); + Directory.CreateDirectory(workingFolder); + + // Copy the database to a temporary location so it can be manipulated. + // Ensure it is not read-only. + var workingDatabasePath = Path.Combine(workingFolder, Path.GetFileName(this.OutputPath)); + FileSystem.CopyFile(this.OutputPath, workingDatabasePath, allowHardlink: false); + + var trackWorkingDatabase = this.BackendHelper.TrackFile(workingDatabasePath, TrackedFileType.Temporary); + trackedFiles.Add(trackWorkingDatabase); + + var attributes = File.GetAttributes(workingDatabasePath); + File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly); + + var validator = new WindowsInstallerValidator(this, workingDatabasePath, this.CubeFiles, this.Ices, this.SuppressedIces); + validator.Execute(); + + stopwatch.Stop(); + this.Messaging.Write(VerboseMessages.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); + + + this.TrackedFiles = trackedFiles; + } + + private void LogValidationMessage(ValidationMessage message) + { + var messageSourceLineNumbers = this.OutputSourceLineNumber; + if (!String.IsNullOrEmpty(message.Table) && !String.IsNullOrEmpty(message.Column) && message.PrimaryKeys != null) + { + messageSourceLineNumbers = this.GetSourceLineNumbers(message.Table, message.PrimaryKeys); + } + + switch (message.Type) + { + case ValidationMessageType.InternalFailure: + case ValidationMessageType.Error: + this.Messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, message.IceName, message.Description)); + break; + case ValidationMessageType.Warning: + this.Messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, message.IceName, message.Description)); + break; + case ValidationMessageType.Info: + this.Messaging.Write(VerboseMessages.ValidationInfo(message.IceName, message.Description)); + break; + default: + throw new WixException(ErrorMessages.InvalidValidatorMessageType(message.Type.ToString())); + } + } + + /// + /// Validation blocked by other installation operation for . + /// + public void ValidationBlocked() + { + this.Messaging.Write(VerboseMessages.ValidationSerialized()); + } + + /// + /// Validation message implementation for . + /// + public bool ValidationMessage(ValidationMessage message) + { + this.LogValidationMessage(message); + return true; + } + + /// + /// Gets the source line information (if available) for a row by its table name and primary key. + /// + /// The table name of the row. + /// The primary keys of the row. + /// The source line number information if found; null otherwise. + private SourceLineNumber GetSourceLineNumbers(string tableName, IEnumerable primaryKeys) + { + // Source line information only exists if an output file was supplied + if (this.Data == null) + { + // Use the file name as the source line information. + return this.OutputSourceLineNumber; + } + + // Index the source line information if it hasn't been indexed already. + if (this.SourceLineNumbersByTablePrimaryKey == null) + { + this.SourceLineNumbersByTablePrimaryKey = new Dictionary(); + + // Index each real table + foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) + { + // Index each row that contain source line information + foreach (var row in table.Rows.Where(r => r.SourceLineNumbers != null)) + { + // Index the row using its table name and primary key + var primaryKey = row.GetPrimaryKey(';'); + + if (!String.IsNullOrEmpty(primaryKey)) + { + try + { + var key = String.Concat(table.Name, ":", primaryKey); + this.SourceLineNumbersByTablePrimaryKey.Add(key, row.SourceLineNumbers); + } + catch (ArgumentException) + { + this.Messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); + } + } + } + } + } + + return this.SourceLineNumbersByTablePrimaryKey.TryGetValue(String.Concat(tableName, ":", String.Join(";", primaryKeys)), out var sourceLineNumbers) ? sourceLineNumbers : null; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs new file mode 100644 index 00000000..aeda4443 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs @@ -0,0 +1,96 @@ +// 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.Decompile +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.IO; + using System.Linq; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class DecompileMsiOrMsmCommand + { + public DecompileMsiOrMsmCommand(IDecompileContext context, IEnumerable backendExtensions) + { + this.Context = context; + this.Extensions = backendExtensions; + this.Messaging = context.ServiceProvider.GetService(); + } + + private IDecompileContext Context { get; } + + private IEnumerable Extensions { get; } + + private IMessaging Messaging { get; } + + public IDecompileResult Execute() + { + var result = this.Context.ServiceProvider.GetService(); + + try + { + using (var database = new Database(this.Context.DecompilePath, OpenDatabase.ReadOnly)) + { + // Delete the directory and its files to prevent cab extraction failure due to an existing file. + if (Directory.Exists(this.Context.ExtractFolder)) + { + Directory.Delete(this.Context.ExtractFolder, true); + } + + var backendHelper = this.Context.ServiceProvider.GetService(); + + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, backendHelper, database, this.Context.DecompilePath, this.Context.DecompileType, this.Context.ExtractFolder, this.Context.IntermediateFolder, this.Context.IsAdminImage, suppressDemodularization: false, skipSummaryInfo: false); + var output = unbindCommand.Execute(); + var extractedFilePaths = new List(unbindCommand.ExportedFiles); + + var decompiler = new Decompiler(this.Messaging, backendHelper, this.Extensions, this.Context.BaseSourcePath, this.Context.SuppressCustomTables, this.Context.SuppressDroppingEmptyTables, this.Context.SuppressUI, this.Context.TreatProductAsModule); + result.Document = decompiler.Decompile(output); + + result.Platform = GetPlatformFromOutput(output); + + // extract the files from the cabinets + if (!String.IsNullOrEmpty(this.Context.ExtractFolder) && !this.Context.SuppressExtractCabinets) + { + var fileDirectory = String.IsNullOrEmpty(this.Context.CabinetExtractFolder) ? Path.Combine(this.Context.ExtractFolder, "File") : this.Context.CabinetExtractFolder; + + var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.DecompilePath, fileDirectory, this.Context.IntermediateFolder, this.Context.TreatProductAsModule); + extractCommand.Execute(); + + extractedFilePaths.AddRange(extractCommand.ExtractedFiles); + result.ExtractedFilePaths = extractedFilePaths; + } + else + { + result.ExtractedFilePaths = new string[0]; + } + } + } + catch (Win32Exception e) + { + if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED + { + throw new WixException(ErrorMessages.OpenDatabaseFailed(this.Context.DecompilePath)); + } + + throw; + } + + return result; + } + + private static Platform? GetPlatformFromOutput(WindowsInstallerData output) + { + var template = output.Tables["_SummaryInformation"]?.Rows.SingleOrDefault(row => row.FieldAsInteger(0) == 7)?.FieldAsString(1); + + return Decompiler.GetPlatformFromTemplateSummaryInformation(template?.Split(';')); + + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs new file mode 100644 index 00000000..0b45a8b3 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs @@ -0,0 +1,7596 @@ +// 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.Decompile +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + /// + /// Decompiles an msi database into WiX source. + /// + internal class Decompiler + { + private static readonly Regex NullSplitter = new Regex(@"\[~]"); + + // NameToBit arrays + private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; + private static readonly string[] HyperlinkControlAttributes = { "Transparent" }; + private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; + private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; + private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; + private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; + private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; + private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; + private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; + private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; + private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; + private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; + private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; + private XElement uiElement; + + /// + /// Creates a new decompiler object with a default set of table definitions. + /// + public Decompiler(IMessaging messaging, IBackendHelper backendHelper, IEnumerable extensions, string baseSourcePath, bool suppressCustomTables, bool suppressDroppingEmptyTables, bool suppressUI, bool treatProductAsModule) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Extensions = extensions; + this.BaseSourcePath = baseSourcePath ?? "SourceDir"; + this.SuppressCustomTables = suppressCustomTables; + this.SuppressDroppingEmptyTables = suppressDroppingEmptyTables; + this.SuppressUI = suppressUI; + this.TreatProductAsModule = treatProductAsModule; + + this.ExtensionsByTableName = new Dictionary(); + this.StandardActions = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id); + + this.TableDefinitions = new TableDefinitionCollection(); + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private IEnumerable Extensions { get; } + + private Dictionary ExtensionsByTableName { get; } + + private string BaseSourcePath { get; } + + private bool SuppressCustomTables { get; } + + private bool SuppressDroppingEmptyTables { get; } + + private bool SuppressRelativeActionSequencing { get; } + + private bool SuppressUI { get; } + + private bool TreatProductAsModule { get; } + + private OutputType OutputType { get; set; } + + private Dictionary StandardActions { get; } + + private bool Compressed { get; set; } + + private XElement RootElement { get; set; } + + private TableDefinitionCollection TableDefinitions { get; } + + private bool ShortNames { get; set; } + + private string ModularizationGuid { get; set; } + + private XElement UIElement + { + get + { + if (null == this.uiElement) + { + this.uiElement = new XElement(Names.UIElement); + this.RootElement.Add(this.uiElement); + } + + return this.uiElement; + } + } + + private Dictionary Singletons { get; } = new Dictionary(); + + private Dictionary IndexedElements { get; } = new Dictionary(); + + private Dictionary PatchTargetFiles { get; } = new Dictionary(); + + /// + /// Decompile the database file. + /// + /// The output to decompile. + /// The serialized WiX source code. + public XDocument Decompile(WindowsInstallerData output) + { + if (null == output) + { + throw new ArgumentNullException(nameof(output)); + } + + this.OutputType = output.Type; + + // collect the table definitions from the output + this.TableDefinitions.Clear(); + foreach (var table in output.Tables) + { + this.TableDefinitions.Add(table.Definition); + } + + // add any missing standard and wix-specific table definitions + foreach (var tableDefinition in WindowsInstallerTableDefinitions.All) + { + if (!this.TableDefinitions.Contains(tableDefinition.Name)) + { + this.TableDefinitions.Add(tableDefinition); + } + } + + // add any missing extension table definitions +#if TODO_DECOMPILER_EXTENSIONS + foreach (var extension in this.Extensions) + { + this.AddExtension(extension); + } +#endif + + switch (this.OutputType) + { + case OutputType.Module: + this.RootElement = new XElement(Names.ModuleElement); + break; + case OutputType.PatchCreation: + this.RootElement = new XElement(Names.PatchCreationElement); + break; + case OutputType.Product: + this.RootElement = new XElement(Names.PackageElement); + break; + default: + throw new InvalidOperationException("Unknown output type."); + } + + var xWix = new XElement(Names.WixElement, this.RootElement); + + // try to decompile the database file + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + this.InitializeDecompile(output.Tables, output.Codepage); + + // stop processing if an error previously occurred + if (this.Messaging.EncounteredError) + { + return null; + } + + // decompile the tables + this.DecompileTables(output); + + // finalize the decompiler and its extensions + this.FinalizeDecompile(output.Tables); + + // return the XML document only if decompilation completed successfully + var document = new XDocument(xWix); + return this.Messaging.EncounteredError ? null : document; + } + +#if TODO_DECOMPILER_EXTENSIONS + private void AddExtension(IWindowsInstallerBackendDecompilerExtension extension) + { + if (null != extension.TableDefinitions) + { + foreach (TableDefinition tableDefinition in extension.TableDefinitions) + { + if (!this.ExtensionsByTableName.ContainsKey(tableDefinition.Name)) + { + this.ExtensionsByTableName.Add(tableDefinition.Name, extension); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name)); + } + } + } + } +#endif + + internal static Platform? GetPlatformFromTemplateSummaryInformation(string[] template) + { + if (null != template && 1 < template.Length && null != template[0] && 0 < template[0].Length) + { + switch (template[0]) + { + case "Intel": + return Platform.X86; + case "x64": + return Platform.X64; + case "Arm64": + return Platform.ARM64; + } + } + + return null; + } + + /// + /// Gets the element corresponding to the row it came from. + /// + /// The row corresponding to the element. + /// The indexed element. + private XElement GetIndexedElement(WixToolset.Data.WindowsInstaller.Row row) => this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + + /// + /// Gets the element corresponding to the primary key of the given table. + /// + /// The table corresponding to the element. + /// The primary key corresponding to the element. + /// The indexed element. + private XElement GetIndexedElement(string table, params string[] primaryKey) => this.IndexedElements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))]; + + /// + /// Tries to get the element corresponding to the primary key of the given table. + /// + /// The table corresponding to the element. + /// The indexed element. + /// Whether the element was found. + private bool TryGetIndexedElement(WixToolset.Data.WindowsInstaller.Row row, out XElement xElement) => this.TryGetIndexedElement(row.TableDefinition.Name, out xElement, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + + /// + /// Tries to get the element corresponding to the primary key of the given table. + /// + /// The table corresponding to the element. + /// The indexed element. + /// The primary key corresponding to the element. + /// Whether the element was found. + private bool TryGetIndexedElement(string table, out XElement xElement, params string[] primaryKey) => this.IndexedElements.TryGetValue(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), out xElement); + + /// + /// Index an element by its corresponding row. + /// + /// The row corresponding to the element. + /// The element to index. + private void IndexElement(WixToolset.Data.WindowsInstaller.Row row, XElement element) + { + this.IndexedElements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element); + } + + /// + /// Index an element by its corresponding row. + /// + /// The element to index. + /// + /// + private void IndexElement(XElement element, string table, params string[] primaryKey) + { + this.IndexedElements.Add(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), element); + } + + private Dictionary> IndexTableOneToMany(IEnumerable rows, int column = 0) + { + return rows + .ToLookup(row => row.FieldAsString(column), row => this.GetIndexedElement(row)) + .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); + } + + private Dictionary> IndexTableOneToMany(TableIndexedCollection tables, string tableName, int column = 0) => this.IndexTableOneToMany(tables[tableName]?.Rows ?? Enumerable.Empty(), column); + + private Dictionary> IndexTableOneToMany(Table table, int column = 0) => this.IndexTableOneToMany(table?.Rows ?? Enumerable.Empty(), column); + + private void AddChildToParent(string parentName, XElement xChild, Row row, int column) + { + var key = row.FieldAsString(column); + if (this.TryGetIndexedElement(parentName, out var xParent, key)) + { + xParent.Add(xChild); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row.Fields[column].Column.Name, key, parentName)); + } + } + + private static XAttribute XAttributeIfNotNull(string attributeName, Row row, int column) => row.IsColumnNull(column) ? null : new XAttribute(attributeName, row.FieldAsString(column)); + + private static void SetAttributeIfNotNull(XElement xElement, string attributeName, string value) + { + if (!String.IsNullOrEmpty(value)) + { + xElement.SetAttributeValue(attributeName, value); + } + } + + private static void SetAttributeIfNotNull(XElement xElement, string attributeName, int? value) + { + if (value.HasValue) + { + xElement.SetAttributeValue(attributeName, value); + } + } + + /// + /// Convert an Int32 into a DateTime. + /// + /// The Int32 value. + /// The DateTime. + private static DateTime ConvertIntegerToDateTime(int value) + { + var date = value / 65536; + var time = value % 65536; + + return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2); + } + + /// + /// Set the common control attributes in a control element. + /// + /// The control attributes. + /// The control element. + private static void SetControlAttributes(int attributes, XElement xControl) + { + if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesEnabled)) + { + xControl.SetAttributeValue("Disabled", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesIndirect == (attributes & WindowsInstallerConstants.MsidbControlAttributesIndirect)) + { + xControl.SetAttributeValue("Indirect", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesInteger == (attributes & WindowsInstallerConstants.MsidbControlAttributesInteger)) + { + xControl.SetAttributeValue("Integer", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbControlAttributesLeftScroll)) + { + xControl.SetAttributeValue("LeftScroll", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbControlAttributesRightAligned)) + { + xControl.SetAttributeValue("RightAligned", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbControlAttributesRTLRO)) + { + xControl.SetAttributeValue("RightToLeft", "yes"); + } + + if (WindowsInstallerConstants.MsidbControlAttributesSunken == (attributes & WindowsInstallerConstants.MsidbControlAttributesSunken)) + { + xControl.SetAttributeValue("Sunken", "yes"); + } + + if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesVisible)) + { + xControl.SetAttributeValue("Hidden", "yes"); + } + } + + /// + /// Creates an action element. + /// + /// The action from which the element should be created. + private void CreateActionElement(WixActionSymbol actionSymbol) + { + XElement xAction; + + if (this.TryGetIndexedElement("CustomAction", out var _, actionSymbol.Action)) // custom action + { + xAction = new XElement(Names.CustomElement, + new XAttribute("Action", actionSymbol.Action), + String.IsNullOrEmpty(actionSymbol.Condition) ? null : new XAttribute("Condition", actionSymbol.Condition)); + + switch (actionSymbol.Sequence) + { + case (-4): + xAction.SetAttributeValue("OnExit", "suspend"); + break; + case (-3): + xAction.SetAttributeValue("OnExit", "error"); + break; + case (-2): + xAction.SetAttributeValue("OnExit", "cancel"); + break; + case (-1): + xAction.SetAttributeValue("OnExit", "success"); + break; + default: + if (null != actionSymbol.Before) + { + xAction.SetAttributeValue("Before", actionSymbol.Before); + } + else if (null != actionSymbol.After) + { + xAction.SetAttributeValue("After", actionSymbol.After); + } + else if (actionSymbol.Sequence.HasValue) + { + xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); + } + break; + } + } + else if (this.TryGetIndexedElement("Dialog", out var _, actionSymbol.Action)) // dialog + { + xAction = new XElement(Names.CustomElement, + new XAttribute("Dialog", actionSymbol.Action), + new XAttribute("Condition", actionSymbol.Condition)); + + switch (actionSymbol.Sequence) + { + case (-4): + xAction.SetAttributeValue("OnExit", "suspend"); + break; + case (-3): + xAction.SetAttributeValue("OnExit", "error"); + break; + case (-2): + xAction.SetAttributeValue("OnExit", "cancel"); + break; + case (-1): + xAction.SetAttributeValue("OnExit", "success"); + break; + default: + SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); + SetAttributeIfNotNull(xAction, "After", actionSymbol.After); + SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); + break; + } + } + else // possibly a standard action without suggested sequence information + { + xAction = this.CreateStandardActionElement(actionSymbol); + } + + // add the action element to the appropriate sequence element + if (null != xAction) + { + var sequenceTable = actionSymbol.SequenceTable.ToString(); + if (!this.Singletons.TryGetValue(sequenceTable, out var xSequence)) + { + xSequence = new XElement(Names.WxsNamespace + sequenceTable); + + this.RootElement.Add(xSequence); + this.Singletons.Add(sequenceTable, xSequence); + } + + try + { + xSequence.Add(xAction); + } + catch (ArgumentException) // action/dialog is not valid for this sequence + { + this.Messaging.Write(WarningMessages.IllegalActionInSequence(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + } + } + } + + /// + /// Creates a standard action element. + /// + /// The action row from which the element should be created. + /// The created element. + private XElement CreateStandardActionElement(WixActionSymbol actionSymbol) + { + XElement xStandardAction = null; + + switch (actionSymbol.Action) + { + case "AllocateRegistrySpace": + case "BindImage": + case "CostFinalize": + case "CostInitialize": + case "CreateFolders": + case "CreateShortcuts": + case "DeleteServices": + case "DuplicateFiles": + case "ExecuteAction": + case "FileCost": + case "InstallAdminPackage": + case "InstallFiles": + case "InstallFinalize": + case "InstallInitialize": + case "InstallODBC": + case "InstallServices": + case "InstallValidate": + case "IsolateComponents": + case "MigrateFeatureStates": + case "MoveFiles": + case "MsiPublishAssemblies": + case "MsiUnpublishAssemblies": + case "PatchFiles": + case "ProcessComponents": + case "PublishComponents": + case "PublishFeatures": + case "PublishProduct": + case "RegisterClassInfo": + case "RegisterComPlus": + case "RegisterExtensionInfo": + case "RegisterFonts": + case "RegisterMIMEInfo": + case "RegisterProduct": + case "RegisterProgIdInfo": + case "RegisterTypeLibraries": + case "RegisterUser": + case "RemoveDuplicateFiles": + case "RemoveEnvironmentStrings": + case "RemoveFiles": + case "RemoveFolders": + case "RemoveIniValues": + case "RemoveODBC": + case "RemoveRegistryValues": + case "RemoveShortcuts": + case "SelfRegModules": + case "SelfUnregModules": + case "SetODBCFolders": + case "StartServices": + case "StopServices": + case "UnpublishComponents": + case "UnpublishFeatures": + case "UnregisterClassInfo": + case "UnregisterComPlus": + case "UnregisterExtensionInfo": + case "UnregisterFonts": + case "UnregisterMIMEInfo": + case "UnregisterProgIdInfo": + case "UnregisterTypeLibraries": + case "ValidateProductID": + case "WriteEnvironmentStrings": + case "WriteIniValues": + case "WriteRegistryValues": + xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); + break; + + case "AppSearch": + this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var appSearchActionRow); + + if (null != actionSymbol.Before || null != actionSymbol.After || (null != appSearchActionRow && actionSymbol.Sequence != appSearchActionRow.Sequence)) + { + xStandardAction = new XElement(Names.AppSearchElement); + + SetAttributeIfNotNull(xStandardAction, "Condition", actionSymbol.Condition); + SetAttributeIfNotNull(xStandardAction, "Before", actionSymbol.Before); + SetAttributeIfNotNull(xStandardAction, "After", actionSymbol.After); + SetAttributeIfNotNull(xStandardAction, "Sequence", actionSymbol.Sequence); + + return xStandardAction; + } + break; + + case "CCPSearch": + case "DisableRollback": + case "FindRelatedProducts": + case "ForceReboot": + case "InstallExecute": + case "InstallExecuteAgain": + case "LaunchConditions": + case "RemoveExistingProducts": + case "ResolveSource": + case "RMCCPSearch": + case "ScheduleReboot": + xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action); + Decompiler.SequenceRelativeAction(actionSymbol, xStandardAction); + return xStandardAction; + + default: + this.Messaging.Write(WarningMessages.UnknownAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + return null; + } + + if (xStandardAction != null) + { + this.SequenceStandardAction(actionSymbol, xStandardAction); + } + + return xStandardAction; + } + + /// + /// Applies the condition and sequence to a standard action element based on the action symbol data. + /// + /// Action data from the database. + /// Element to be sequenced. + private void SequenceStandardAction(WixActionSymbol actionSymbol, XElement xAction) + { + xAction.SetAttributeValue("Condition", actionSymbol.Condition); + + if ((null != actionSymbol.Before || null != actionSymbol.After) && 0 == actionSymbol.Sequence) + { + this.Messaging.Write(WarningMessages.DecompiledStandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); + } + else if (actionSymbol.Sequence.HasValue) + { + xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value); + } + } + + /// + /// Applies the condition and relative sequence to an action element based on the action row data. + /// + /// Action data from the database. + /// Element to be sequenced. + private static void SequenceRelativeAction(WixActionSymbol actionSymbol, XElement xAction) + { + SetAttributeIfNotNull(xAction, "Condition", actionSymbol.Condition); + SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before); + SetAttributeIfNotNull(xAction, "After", actionSymbol.After); + SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence); + } + + /// + /// Ensure that a particular property exists in the decompiled output. + /// + /// The identifier of the property. + /// The property element. + private XElement EnsureProperty(string id) + { + XElement xProperty; + + if (!this.TryGetIndexedElement("Property", out xProperty, id)) + { + xProperty = new XElement(Names.PropertyElement, new XAttribute("Id", id)); + + this.RootElement.Add(xProperty); + this.IndexElement(xProperty, "Property", id); + } + + return xProperty; + } + + /// + /// Finalize decompilation. + /// + /// The collection of all tables. + private void FinalizeDecompile(TableIndexedCollection tables) + { + if (OutputType.PatchCreation == this.OutputType) + { + this.FinalizeFamilyFileRangesTable(tables); + } + else + { + this.FinalizeSummaryInformationStream(tables); + this.FinalizeCheckBoxTable(tables); + this.FinalizeComponentTable(tables); + this.FinalizeDialogTable(tables); + this.FinalizeDuplicateMoveFileTables(tables); + this.FinalizeFeatureComponentsTable(tables); + this.FinalizeFileTable(tables); + this.FinalizeMIMETable(tables); + this.FinalizeMsiLockPermissionsExTable(tables); + this.FinalizeLockPermissionsTable(tables); + this.FinalizeProgIdTable(tables); + this.FinalizePropertyTable(tables); + this.FinalizeRemoveFileTable(tables); + this.FinalizeSearchTables(tables); + this.FinalizeShortcutTable(tables); + this.FinalizeUpgradeTable(tables); + this.FinalizeSequenceTables(tables); + this.FinalizeVerbTable(tables); + } + } + + /// + /// Finalize the CheckBox table. + /// + /// The collection of all tables. + /// + /// Enumerates through all the Control rows, looking for controls of type "CheckBox" with + /// a value in the Property column. This is then possibly matched up with a CheckBox row + /// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table. + /// + private void FinalizeCheckBoxTable(TableIndexedCollection tables) + { + // if the user has requested to suppress the UI elements, we have nothing to do + if (this.SuppressUI) + { + return; + } + + var checkBoxTable = tables["CheckBox"]; + var controlTable = tables["Control"]; + + var checkBoxes = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + var checkBoxProperties = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row => false); + + // enumerate through the Control table, adding CheckBox values where appropriate + if (null != controlTable) + { + foreach (var row in controlTable.Rows) + { + var xControl = this.GetIndexedElement(row); + + if ("CheckBox" == row.FieldAsString(2)) + { + var property = row.FieldAsString(8); + if (!String.IsNullOrEmpty(property) && checkBoxes.TryGetValue(property, out var checkBoxRow)) + { + // if we've seen this property already, create a reference to it + if (checkBoxProperties.TryGetValue(property, out var seen) && seen) + { + xControl.SetAttributeValue("CheckBoxPropertyRef", property); + } + else + { + xControl.SetAttributeValue("Property", property); + checkBoxProperties[property] = true; + } + + xControl.SetAttributeValue("CheckBoxValue", checkBoxRow.FieldAsString(1)); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", row.FieldAsString(8), "CheckBox")); + } + } + } + } + } + + /// + /// Finalize the Component table. + /// + /// The collection of all tables. + /// + /// Set the keypaths for each component. + /// + private void FinalizeComponentTable(TableIndexedCollection tables) + { + var componentTable = tables["Component"]; + var fileTable = tables["File"]; + var odbcDataSourceTable = tables["ODBCDataSource"]; + var registryTable = tables["Registry"]; + + // set the component keypaths + if (null != componentTable) + { + foreach (var row in componentTable.Rows) + { + var attributes = row.FieldAsInteger(3); + var keyPath = row.FieldAsString(5); + + if (String.IsNullOrEmpty(keyPath)) + { + var xComponent = this.GetIndexedElement("Component", row.FieldAsString(0)); + xComponent.SetAttributeValue("KeyPath", "yes"); + } + else if (WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath == (attributes & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) + { + if (this.TryGetIndexedElement("Registry", out var xRegistry, keyPath)) + { + if (xRegistry.Name.LocalName == "RegistryValue") + { + xRegistry.SetAttributeValue("KeyPath", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", keyPath)); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "Registry")); + } + } + else if (WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource == (attributes & WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource)) + { + if (this.TryGetIndexedElement("ODBCDataSource", out var xOdbcDataSource, keyPath)) + { + xOdbcDataSource.SetAttributeValue("KeyPath", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "ODBCDataSource")); + } + } + else + { + if (this.TryGetIndexedElement("File", out var xFile, keyPath)) + { + xFile.SetAttributeValue("KeyPath", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "File")); + } + } + } + } + + // add the File children elements + if (null != fileTable) + { + foreach (FileRow fileRow in fileTable.Rows) + { + if (this.TryGetIndexedElement("Component", out var xComponent, fileRow.Component) + && this.TryGetIndexedElement(fileRow, out var xFile)) + { + xComponent.Add(xFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component")); + } + } + } + + // add the ODBCDataSource children elements + if (null != odbcDataSourceTable) + { + foreach (var row in odbcDataSourceTable.Rows) + { + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(1)) + && this.TryGetIndexedElement(row, out var xOdbcDataSource)) + { + xComponent.Add(xOdbcDataSource); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); + } + } + } + + // add the Registry children elements + if (null != registryTable) + { + foreach (var row in registryTable.Rows) + { + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(5)) + && this.TryGetIndexedElement(row, out var xRegistry)) + { + xComponent.Add(xRegistry); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(5), "Component")); + } + } + } + } + + /// + /// Finalize the Dialog table. + /// + /// The collection of all tables. + /// + /// Sets the first, default, and cancel control for each dialog and adds all child control + /// elements to the dialog. + /// + private void FinalizeDialogTable(TableIndexedCollection tables) + { + // if the user has requested to suppress the UI elements, we have nothing to do + if (this.SuppressUI) + { + return; + } + + var addedControls = new HashSet(); + + var controlTable = tables["Control"]; + var controlRows = controlTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)); + + var dialogTable = tables["Dialog"]; + if (null != dialogTable) + { + foreach (var dialogRow in dialogTable.Rows) + { + var xDialog = this.GetIndexedElement(dialogRow); + var dialogId = dialogRow.FieldAsString(0); + + if (!this.TryGetIndexedElement("Control", out var xControl, dialogId, dialogRow.FieldAsString(7))) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", dialogRow.FieldAsString(7), "Control")); + } + + // add tabbable controls + while (null != xControl) + { + var controlId = xControl.Attribute("Id"); + var controlRow = controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, controlId)]; + + xControl.SetAttributeValue("TabSkip", "no"); + + xDialog.Add(xControl); + addedControls.Add(xControl); + + var controlNext = controlRow.FieldAsString(10); + if (!String.IsNullOrEmpty(controlNext)) + { + if (this.TryGetIndexedElement("Control", out xControl, dialogId, controlNext)) + { + // looped back to the first control in the dialog + if (addedControls.Contains(xControl)) + { + xControl = null; + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", controlNext, "Control")); + } + } + else + { + xControl = null; + } + } + + // set default control + var controlDefault = dialogRow.FieldAsString(8); + if (!String.IsNullOrEmpty(controlDefault)) + { + if (this.TryGetIndexedElement("Control", out var xDefaultControl, dialogId, controlDefault)) + { + xDefaultControl.SetAttributeValue("Default", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(dialogRow[8]), "Control")); + } + } + + // set cancel control + var controlCancel = dialogRow.FieldAsString(8); + if (!String.IsNullOrEmpty(controlCancel)) + { + if (this.TryGetIndexedElement("Control", out var xCancelControl, dialogId, controlCancel)) + { + xCancelControl.SetAttributeValue("Cancel", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(dialogRow[9]), "Control")); + } + } + } + } + + // add the non-tabbable controls to the dialog + if (null != controlTable) + { + foreach (var controlRow in controlTable.Rows) + { + var dialogId = controlRow.FieldAsString(0); + if (!this.TryGetIndexedElement("Dialog", out var xDialog, dialogId)) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Dialog")); + continue; + } + + var xControl = this.GetIndexedElement(controlRow); + if (!addedControls.Contains(xControl)) + { + xControl.SetAttributeValue("TabSkip", "yes"); + xDialog.Add(xControl); + } + } + } + } + + /// + /// Finalize the DuplicateFile and MoveFile tables. + /// + /// The collection of all tables. + /// + /// Sets the source/destination property/directory for each DuplicateFile or + /// MoveFile row. + /// + private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables) + { + var duplicateFileTable = tables["DuplicateFile"]; + if (null != duplicateFileTable) + { + foreach (var row in duplicateFileTable.Rows) + { + var xCopyFile = this.GetIndexedElement(row); + var destination = row.FieldAsString(4); + if (!String.IsNullOrEmpty(destination)) + { + if (this.TryGetIndexedElement("Directory", out var _, destination)) + { + xCopyFile.SetAttributeValue("DestinationDirectory", destination); + } + else + { + xCopyFile.SetAttributeValue("DestinationProperty", destination); + } + } + } + } + + var moveFileTable = tables["MoveFile"]; + if (null != moveFileTable) + { + foreach (var row in moveFileTable.Rows) + { + var xCopyFile = this.GetIndexedElement(row); + var source = row.FieldAsString(4); + if (!String.IsNullOrEmpty(source)) + { + if (this.TryGetIndexedElement("Directory", out var _, source)) + { + xCopyFile.SetAttributeValue("SourceDirectory", source); + } + else + { + xCopyFile.SetAttributeValue("SourceProperty", source); + } + } + + var destination = row.FieldAsString(5); + if (this.TryGetIndexedElement("Directory", out var _, destination)) + { + xCopyFile.SetAttributeValue("DestinationDirectory", destination); + } + else + { + xCopyFile.SetAttributeValue("DestinationProperty", destination); + } + } + } + } + + /// + /// Finalize the FamilyFileRanges table. + /// + /// The collection of all tables. + private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables) + { + var familyFileRangesTable = tables["FamilyFileRanges"]; + if (null != familyFileRangesTable) + { + foreach (var row in familyFileRangesTable.Rows) + { + var xProtectRange = new XElement(Names.ProtectRangeElement); + + if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) + { + var retainOffsets = row.FieldAsString(2).Split(','); + var retainLengths = row.FieldAsString(3).Split(','); + + if (retainOffsets.Length == retainLengths.Length) + { + for (var i = 0; i < retainOffsets.Length; i++) + { + if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal)) + { + xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i].Substring(2), 16)); + } + else + { + xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture)); + } + + if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal)) + { + xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i].Substring(2), 16)); + } + else + { + xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture)); + } + } + } + else + { + // TODO: warn + } + } + else if (!row.IsColumnNull(2) || !row.IsColumnNull(3)) + { + // TODO: warn about mismatch between columns + } + + this.IndexElement(row, xProtectRange); + } + } + + var usedProtectRanges = new HashSet(); + var externalFilesTable = tables["ExternalFiles"]; + if (null != externalFilesTable) + { + foreach (var row in externalFilesTable.Rows) + { + if (this.TryGetIndexedElement(row, out var xExternalFile) + && this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, row.FieldAsString(0), row.FieldAsString(0))) + { + xExternalFile.Add(xProtectRange); + usedProtectRanges.Add(xProtectRange); + } + } + } + + var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"]; + if (null != targetFiles_OptionalDataTable) + { + var targetImagesTable = tables["TargetImages"]; + var targetImageRows = targetImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); + + var upgradedImagesTable = tables["UpgradedImages"]; + var upgradedImagesRows = upgradedImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0)); + + foreach (var row in targetFiles_OptionalDataTable.Rows) + { + var xTargetFile = this.PatchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)]; + + if (!targetImageRows.TryGetValue(row.FieldAsString(0), out var targetImageRow)) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); + continue; + } + + if (!upgradedImagesRows.TryGetValue(row.FieldAsString(3), out var upgradedImagesRow)) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", row.FieldAsString(3), "UpgradedImages")); + continue; + } + + if (this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, upgradedImagesRow.FieldAsString(4), row.FieldAsString(1))) + { + xTargetFile.Add(xProtectRange); + usedProtectRanges.Add(xProtectRange); + } + } + } + + if (null != familyFileRangesTable) + { + foreach (var row in familyFileRangesTable.Rows) + { + var xProtectRange = this.GetIndexedElement(row); + + if (!usedProtectRanges.Contains(xProtectRange)) + { + var xProtectFile = new XElement(Names.ProtectFileElement, new XAttribute("File", row.FieldAsString(1))); + xProtectFile.Add(xProtectRange); + + this.AddChildToParent("ImageFamilies", xProtectFile, row, 0); + } + } + } + } + + /// + /// Finalize the FeatureComponents table. + /// + /// The collection of all tables. + /// + /// Since tables specifying references to the FeatureComponents table have references to + /// the Feature and Component table separately, but not the FeatureComponents table specifically, + /// the FeatureComponents table and primary features must be decompiled during finalization. + /// + private void FinalizeFeatureComponentsTable(TableIndexedCollection tables) + { + var classTable = tables["Class"]; + if (null != classTable) + { + foreach (var row in classTable.Rows) + { + this.SetPrimaryFeature(row, 11, 2); + } + } + + var extensionTable = tables["Extension"]; + if (null != extensionTable) + { + foreach (var row in extensionTable.Rows) + { + this.SetPrimaryFeature(row, 4, 1); + } + } + + var msiAssemblyTable = tables["MsiAssembly"]; + if (null != msiAssemblyTable) + { + foreach (var row in msiAssemblyTable.Rows) + { + this.SetPrimaryFeature(row, 1, 0); + } + } + + var publishComponentTable = tables["PublishComponent"]; + if (null != publishComponentTable) + { + foreach (var row in publishComponentTable.Rows) + { + this.SetPrimaryFeature(row, 4, 2); + } + } + + var typeLibTable = tables["TypeLib"]; + if (null != typeLibTable) + { + foreach (var row in typeLibTable.Rows) + { + this.SetPrimaryFeature(row, 6, 2); + } + } + } + + /// + /// Finalize the File table. + /// + /// The collection of all tables. + /// + /// Sets the source, diskId, and assembly information for each file. + /// + private void FinalizeFileTable(TableIndexedCollection tables) + { + // index the media table by media id + var mediaTable = tables["Media"]; + var mediaRows = new RowDictionary(mediaTable); + + // set the disk identifiers and sources for files + foreach (var fileRow in tables["File"]?.Rows.Cast() ?? Enumerable.Empty()) + { + var xFile = this.GetIndexedElement("File", fileRow.File); + + // Don't bother processing files that are orphaned (and won't show up in the output anyway) + if (null != xFile.Parent) + { + // set the diskid + if (null != mediaTable) + { + foreach (MediaRow mediaRow in mediaTable.Rows) + { + if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1) + { + xFile.SetAttributeValue("DiskId", mediaRow.DiskId); + break; + } + } + } + + var fileId = xFile?.Attribute("Id")?.Value; + var fileCompressed = xFile?.Attribute("Compressed")?.Value; + var fileShortName = xFile?.Attribute("ShortName")?.Value; + var fileName = xFile?.Attribute("Name")?.Value; + + // set the source (done here because it requires information from the Directory table) + if (OutputType.Module == this.OutputType) + { + xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId, '.', this.ModularizationGuid.Substring(1, 36).Replace('-', '_'))); + } + else if (fileCompressed == "yes" || (fileCompressed != "no" && this.Compressed) || (OutputType.Product == this.OutputType && this.TreatProductAsModule)) + { + xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId)); + } + else // uncompressed + { + var name = (!this.ShortNames && !String.IsNullOrEmpty(fileName)) ? fileName : fileShortName ?? fileName; + + if (this.Compressed) // uncompressed at the root of the source image + { + xFile.SetAttributeValue("Source", String.Concat("SourceDir", Path.DirectorySeparatorChar, name)); + } + else + { + var sourcePath = this.GetSourcePath(xFile); + xFile.SetAttributeValue("Source", Path.Combine(sourcePath, name)); + } + } + } + } + + // set the file assemblies and manifests + foreach (var row in tables["MsiAssembly"]?.Rows ?? Enumerable.Empty()) + { + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) + { + foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) + { + xFile.SetAttributeValue("AssemblyManifest", row.FieldAsString(2)); + xFile.SetAttributeValue("AssemblyApplication", row.FieldAsString(3)); + xFile.SetAttributeValue("Assembly", row.FieldAsInteger(4) == 0 ? ".net" : "win32"); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); + } + } + + // nest the TypeLib elements + foreach (var row in tables["TypeLib"]?.Rows ?? Enumerable.Empty()) + { + var xComponent = this.GetIndexedElement("Component", row.FieldAsString(2)); + var xTypeLib = this.GetIndexedElement(row); + + foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes")) + { + xFile.Add(xTypeLib); + } + } + } + + /// + /// Finalize the MIME table. + /// + /// The collection of all tables. + /// + /// There is a foreign key shared between the MIME and Extension + /// tables so either one would be valid to be decompiled first, so + /// the only safe way to nest the MIME elements is to do it during finalize. + /// + private void FinalizeMIMETable(TableIndexedCollection tables) + { + var extensionRows = tables["Extension"]?.Rows ?? Enumerable.Empty(); + foreach (var row in extensionRows) + { + // set the default MIME element for this extension + var mimeRef = row.FieldAsString(3); + if (null != mimeRef) + { + if (this.TryGetIndexedElement("MIME", out var xMime, mimeRef)) + { + xMime.SetAttributeValue("Default", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); + } + } + } + + var extensionsByExtensionId = this.IndexTableOneToMany(extensionRows); + + foreach (var row in tables["MIME"]?.Rows ?? Enumerable.Empty()) + { + var xMime = this.GetIndexedElement(row); + + if (extensionsByExtensionId.TryGetValue(row.FieldAsString(1), out var xExtensions)) + { + foreach (var extension in xExtensions) + { + extension.Add(xMime); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(1), "Extension")); + } + } + } + + /// + /// Finalize the ProgId table. + /// + /// The collection of all tables. + /// + /// Enumerates through all the Class rows, looking for child ProgIds (these are the + /// default ProgIds for a given Class). Then go through the ProgId table and add any + /// remaining ProgIds for each Class. This happens during finalize because there is + /// a circular dependency between the Class and ProgId tables. + /// + private void FinalizeProgIdTable(TableIndexedCollection tables) + { + // add the default ProgIds for each class (and index the class table) + var classRows = tables["Class"]?.Rows?.Where(row => row.FieldAsString(3) != null) ?? Enumerable.Empty(); + + var classesByCLSID = this.IndexTableOneToMany(classRows); + + var addedProgIds = new Dictionary(); + + foreach (var row in classRows) + { + var clsid = row.FieldAsString(0); + var xClass = this.GetIndexedElement(row); + + if (this.TryGetIndexedElement("ProgId", out var xProgId, row.FieldAsString(3))) + { + if (addedProgIds.TryGetValue(xProgId, out var progid)) + { + this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, row.FieldAsString(0), row.FieldAsString(3), progid)); + } + else + { + xClass.Add(xProgId); + addedProgIds.Add(xProgId, clsid); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", row.FieldAsString(3), "ProgId")); + } + } + + // add the remaining non-default ProgId entries for each class + foreach (var row in tables["ProgId"]?.Rows ?? Enumerable.Empty()) + { + var clsid = row.FieldAsString(2); + var xProgId = this.GetIndexedElement(row); + + if (!addedProgIds.ContainsKey(xProgId) && null != clsid && null == xProgId.Parent) + { + if (classesByCLSID.TryGetValue(clsid, out var xClasses)) + { + foreach (var xClass in xClasses) + { + xClass.Add(xProgId); + addedProgIds.Add(xProgId, clsid); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", row.FieldAsString(2), "Class")); + } + } + } + + // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension + var componentsById = this.IndexTableOneToMany(tables, "Component"); + + foreach (var row in tables["Extension"]?.Rows?.Where(row => row.FieldAsString(2) != null) ?? Enumerable.Empty()) + { + var xProgId = this.GetIndexedElement("ProgId", row.FieldAsString(2)); + + // Haven't added the progId yet and it doesn't have a parent progId + if (!addedProgIds.ContainsKey(xProgId) && null == xProgId.Parent) + { + if (componentsById.TryGetValue(row.FieldAsString(1), out var xComponents)) + { + foreach (var xComponent in xComponents) + { + xComponent.Add(xProgId); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component")); + } + } + } + } + + /// + /// Finalize the Property table. + /// + /// The collection of all tables. + /// + /// Removes properties that are generated from other entries. + /// + private void FinalizePropertyTable(TableIndexedCollection tables) + { + foreach (var row in tables["CustomAction"]?.Rows ?? Enumerable.Empty()) + { + // If no other fields on the property are set we must have created it in the backend. + var bits = row.FieldAsInteger(1); + if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (bits & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget) + && WindowsInstallerConstants.MsidbCustomActionTypeInScript == (bits & WindowsInstallerConstants.MsidbCustomActionTypeInScript) + && this.TryGetIndexedElement("Property", out var xProperty, row.FieldAsString(0)) + && String.IsNullOrEmpty(xProperty.Attribute("Value")?.Value) + && xProperty.Attribute("Secure")?.Value != "yes" + && xProperty.Attribute("SuppressModularization")?.Value != "yes") + { + xProperty.Remove(); + } + } + } + + /// + /// Finalize the RemoveFile table. + /// + /// The collection of all tables. + /// + /// Sets the directory/property for each RemoveFile row. + /// + private void FinalizeRemoveFileTable(TableIndexedCollection tables) + { + foreach (var row in tables["RemoveFile"]?.Rows ?? Enumerable.Empty()) + { + var xRemove = this.GetIndexedElement(row); + var property = row.FieldAsString(3); + + if (this.TryGetIndexedElement("Directory", out var _, property)) + { + xRemove.SetAttributeValue("Directory", property); + } + else + { + xRemove.SetAttributeValue("Property", property); + } + } + } + + /// + /// Finalize the LockPermissions or MsiLockPermissionsEx table. + /// + /// The collection of all tables. + /// Which table to finalize. + /// + /// Nests the Permission elements below their parent elements. There are no declared foreign + /// keys for the parents of the LockPermissions table. + /// + private void FinalizePermissionsTable(TableIndexedCollection tables, string tableName) + { + var createFoldersById = this.IndexTableOneToMany(tables, tableName); + + foreach (var row in tables[tableName]?.Rows ?? Enumerable.Empty()) + { + var id = row.FieldAsString(0); + var table = row.FieldAsString(1); + var xPermission = this.GetIndexedElement(row); + + if ("CreateFolder" == table) + { + if (createFoldersById.TryGetValue(id, out var xCreateFolders)) + { + foreach (var xCreateFolder in xCreateFolders) + { + xCreateFolder.Add(xPermission); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); + } + } + else + { + if (this.TryGetIndexedElement(table, out var xParent, id)) + { + xParent.Add(xPermission); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table)); + } + } + } + } + + /// + /// Finalize the LockPermissions table. + /// + /// The collection of all tables. + /// + /// Nests the Permission elements below their parent elements. There are no declared foreign + /// keys for the parents of the LockPermissions table. + /// + private void FinalizeLockPermissionsTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "LockPermissions"); + + /// + /// Finalize the MsiLockPermissionsEx table. + /// + /// The collection of all tables. + /// + /// Nests the PermissionEx elements below their parent elements. There are no declared foreign + /// keys for the parents of the MsiLockPermissionsEx table. + /// + private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "MsiLockPermissionsEx"); + + private static Dictionary> IndexTable(Table table, int keyColumn, int? dataColumn) + { + if (table == null) + { + return new Dictionary>(); + } + + return table.Rows + .ToLookup(row => row.FieldAsString(keyColumn), row => dataColumn.HasValue ? row.FieldAsString(dataColumn.Value) : null) + .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); + } + + private static XElement FindComplianceDrive(XElement xSearch) + { + var xComplianceDrive = xSearch.Element(Names.ComplianceDriveElement); + if (null == xComplianceDrive) + { + xComplianceDrive = new XElement(Names.ComplianceDriveElement); + xSearch.Add(xComplianceDrive); + } + + return xComplianceDrive; + } + + /// + /// Finalize the search tables. + /// + /// The collection of all tables. + /// Does all the complex linking required for the search tables. + private void FinalizeSearchTables(TableIndexedCollection tables) + { + var appSearches = IndexTable(tables["AppSearch"], keyColumn: 1, dataColumn: 0); + var ccpSearches = IndexTable(tables["CCPSearch"], keyColumn: 0, dataColumn: null); + var drLocators = tables["DrLocator"]?.Rows.ToDictionary(row => this.GetIndexedElement(row), row => row); + + var xComplianceCheck = new XElement(Names.ComplianceCheckElement); + if (ccpSearches.Keys.Any(ccpSignature => !appSearches.ContainsKey(ccpSignature))) + { + this.RootElement.Add(xComplianceCheck); + } + + // index the locator tables by their signatures + var locators = + new[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" } + .SelectMany(table => tables[table]?.Rows ?? Enumerable.Empty()) + .ToLookup(row => row.FieldAsString(0), row => row) + .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList()); + + // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef) + foreach (var locatorRows in locators.Values) + { + var firstDrLocator = -1; + + for (var i = 0; i < locatorRows.Count; i++) + { + var locatorRow = (Row)locatorRows[i]; + + if ("DrLocator" == locatorRow.TableDefinition.Name) + { + if (-1 == firstDrLocator) + { + firstDrLocator = i; + } + + if ("CCP_DRIVE" == Convert.ToString(locatorRow[1])) + { + locatorRows.RemoveAt(i); + locatorRows.Insert(firstDrLocator, locatorRow); + break; + } + } + } + } + + var xUsedSearches = new HashSet(); + var xUnusedSearches = new Dictionary(); + + foreach (var signature in locators.Keys) + { + var locatorRows = locators[signature]; + var xSignatureSearches = new List(); + + foreach (var locatorRow in locatorRows) + { + var used = true; + var xSearch = this.GetIndexedElement(locatorRow); + + if ("Signature" == locatorRow.TableDefinition.Name && 0 < xSignatureSearches.Count) + { + foreach (var xSearchParent in xSignatureSearches) + { + if (!xUsedSearches.Contains(xSearch)) + { + xSearchParent.Add(xSearch); + xUsedSearches.Add(xSearch); + } + else + { + var xFileSearchRef = new XElement(Names.FileSearchRefElement, + new XAttribute("Id", signature)); + + xSearchParent.Add(xFileSearchRef); + } + } + } + else if ("DrLocator" == locatorRow.TableDefinition.Name && !locatorRow.IsColumnNull(1)) + { + var parentSignature = locatorRow.FieldAsString(1); + + if ("CCP_DRIVE" == parentSignature) + { + if (appSearches.ContainsKey(signature) + && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) + { + foreach (var propertyId in appSearchPropertyIds) + { + var xProperty = this.EnsureProperty(propertyId); + + if (ccpSearches.ContainsKey(signature)) + { + xProperty.SetAttributeValue("ComplianceCheck", "yes"); + } + + var xComplianceDrive = FindComplianceDrive(xProperty); + + if (!xUsedSearches.Contains(xSearch)) + { + xComplianceDrive.Add(xSearch); + xUsedSearches.Add(xSearch); + } + else + { + var directorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", signature), + XAttributeIfNotNull("Parent", locatorRow, 1), + XAttributeIfNotNull("Path", locatorRow, 2)); + + xComplianceDrive.Add(directorySearchRef); + xSignatureSearches.Add(directorySearchRef); + } + } + } + else if (ccpSearches.ContainsKey(signature)) + { + var xComplianceDrive = FindComplianceDrive(xComplianceCheck); + + if (!xUsedSearches.Contains(xSearch)) + { + xComplianceDrive.Add(xSearch); + xUsedSearches.Add(xSearch); + } + else + { + var directorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", signature), + XAttributeIfNotNull("Parent", locatorRow, 1), + XAttributeIfNotNull("Path", locatorRow, 2)); + + xComplianceDrive.Add(directorySearchRef); + xSignatureSearches.Add(directorySearchRef); + } + } + } + else + { + var usedDrLocator = false; + + if (locators.TryGetValue(parentSignature, out var parentLocatorRows)) + { + foreach (var parentLocatorRow in parentLocatorRows) + { + if ("DrLocator" == parentLocatorRow.TableDefinition.Name) + { + var xParentSearch = this.GetIndexedElement(parentLocatorRow); + + if (xParentSearch.HasElements) + { + var parentDrLocatorRow = drLocators[xParentSearch]; + var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", parentSignature), + XAttributeIfNotNull("Parent", parentDrLocatorRow, 1), + XAttributeIfNotNull("Path", parentDrLocatorRow, 2)); + + xParentSearch = xDirectorySearchRef; + xUnusedSearches.Add(parentSignature, xDirectorySearchRef); + } + + if (!xUsedSearches.Contains(xSearch)) + { + xParentSearch.Add(xSearch); + xUsedSearches.Add(xSearch); + usedDrLocator = true; + } + else + { + var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement, + new XAttribute("Id", signature), + new XAttribute("Parent", parentSignature), + XAttributeIfNotNull("Path", locatorRow, 2)); + + xParentSearch.Add(xSearch); + usedDrLocator = true; + } + } + else if ("RegLocator" == parentLocatorRow.TableDefinition.Name) + { + var xParentSearch = this.GetIndexedElement(parentLocatorRow); + + xParentSearch.Add(xSearch); + xUsedSearches.Add(xSearch); + usedDrLocator = true; + } + } + + // keep track of unused DrLocator rows + if (!usedDrLocator) + { + xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); + } + } + else + { + // TODO: warn + } + } + } + else if (appSearches.ContainsKey(signature) + && appSearches.TryGetValue(signature, out var appSearchPropertyIds)) + { + foreach (var propertyId in appSearchPropertyIds) + { + var xProperty = this.EnsureProperty(propertyId); + + if (ccpSearches.ContainsKey(signature)) + { + xProperty.SetAttributeValue("ComplianceCheck", "yes"); + } + + if (!xUsedSearches.Contains(xSearch)) + { + xProperty.Add(xSearch); + xUsedSearches.Add(xSearch); + } + else if ("RegLocator" == locatorRow.TableDefinition.Name) + { + var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, + new XAttribute("Id", signature)); + + xProperty.Add(xRegistrySearchRef); + xSignatureSearches.Add(xRegistrySearchRef); + } + else + { + // TODO: warn about unavailable Ref element + } + } + } + else if (ccpSearches.ContainsKey(signature)) + { + if (!xUsedSearches.Contains(xSearch)) + { + xComplianceCheck.Add(xSearch); + xUsedSearches.Add(xSearch); + } + else if ("RegLocator" == locatorRow.TableDefinition.Name) + { + var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement, + new XAttribute("Id", signature)); + + xComplianceCheck.Add(xRegistrySearchRef); + xSignatureSearches.Add(xRegistrySearchRef); + } + else + { + // TODO: warn about unavailable Ref element + } + } + else + { + if (xSearch.Name.LocalName == "DirectorySearch" || xSearch.Name.LocalName == "RegistrySearch") + { + xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch); + } + else + { + // TODO: warn + used = false; + } + } + + // keep track of the search elements for this signature so that nested searches go in the proper parents + if (used) + { + xSignatureSearches.Add(xSearch); + } + } + } + + // Iterate through the unused elements through a sorted list of their ids so the output is deterministic. + foreach (var unusedSearch in xUnusedSearches.OrderBy(kvp => kvp.Key)) + { + var used = false; + + XElement xLeafDirectorySearch = null; + var xUnusedSearch = unusedSearch.Value; + var xParent = xUnusedSearch; + var updatedLeaf = true; + while (updatedLeaf) + { + updatedLeaf = false; + + var xDirectorySearch = xParent.Element(Names.DirectorySearchElement); + if (xDirectorySearch != null) + { + xParent = xLeafDirectorySearch = xDirectorySearch; + updatedLeaf = true; + } + } + + if (xLeafDirectorySearch != null) + { + var leafDirectorySearchId = xLeafDirectorySearch.Attribute("Id").Value; + if (appSearches.TryGetValue(leafDirectorySearchId, out var appSearchPropertyIds)) + { + var xProperty = this.EnsureProperty(appSearchPropertyIds[0]); + xProperty.Add(xUnusedSearch); + used = true; + } + else if (ccpSearches.ContainsKey(leafDirectorySearchId)) + { + xComplianceCheck.Add(xUnusedSearch); + used = true; + } + else + { + // TODO: warn + } + } + + if (!used) + { + // TODO: warn + } + } + } + + /// + /// Finalize the Shortcut table. + /// + /// The collection of all tables. + /// + /// Sets Advertise to yes if Target points to a Feature. + /// Occurs during finalization because it has to check against every feature row. + /// + private void FinalizeShortcutTable(TableIndexedCollection tables) + { + var shortcutTable = tables["Shortcut"]; + if (null == shortcutTable) + { + return; + } + + foreach (var row in shortcutTable.Rows) + { + var xShortcut = this.GetIndexedElement(row); + + var target = row.FieldAsString(4); + + if (this.TryGetIndexedElement("Feature", out var _, target)) + { + xShortcut.SetAttributeValue("Advertise", "yes"); + this.SetPrimaryFeature(row, 4, 3); + } + else + { + // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element + xShortcut.SetAttributeValue("Target", target); + } + } + } + + /// + /// Finalize the sequence tables. + /// + /// The collection of all tables. + /// + /// Creates the sequence elements. Occurs during finalization because its + /// not known if sequences refer to custom actions or dialogs during decompilation. + /// + private void FinalizeSequenceTables(TableIndexedCollection tables) + { + // finalize the normal sequence tables + if (OutputType.Product == this.OutputType && !this.TreatProductAsModule) + { + foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) + { + var sequenceTableName = sequenceTable.WindowsInstallerTableName(); + + // if suppressing UI elements, skip UI-related sequence tables + if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName)) + { + continue; + } + + var table = tables[sequenceTableName]; + + if (null != table) + { + var actionSymbols = new List(); + var needAbsoluteScheduling = this.SuppressRelativeActionSequencing; + var nonSequencedActionRows = new Dictionary(); + var suppressedRelativeActionRows = new Dictionary(); + + // create a sorted array of actions in this table + foreach (var row in table.Rows) + { + var action = row.FieldAsString(0); + var actionSymbol = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, action)); + + actionSymbol.Action = action; + + if (!row.IsColumnNull(1)) + { + actionSymbol.Condition = row.FieldAsString(1); + } + + actionSymbol.Sequence = row.FieldAsInteger(2); + + actionSymbol.SequenceTable = sequenceTable; + + actionSymbols.Add(actionSymbol); + } + actionSymbols = actionSymbols.OrderBy(t => t.Sequence).ToList(); + + for (var i = 0; i < actionSymbols.Count && !needAbsoluteScheduling; i++) + { + var actionSymbol = actionSymbols[i]; + this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var standardActionRow); + + // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions + if ("AppSearch" == actionSymbol.Action || null == standardActionRow || actionSymbol.Condition != standardActionRow.Condition) + { + WixActionSymbol previousActionSymbol = null; + WixActionSymbol nextActionSymbol = null; + + // find the previous action row if there is one + if (0 <= i - 1) + { + previousActionSymbol = actionSymbols[i - 1]; + } + + // find the next action row if there is one + if (actionSymbols.Count > i + 1) + { + nextActionSymbol = actionSymbols[i + 1]; + } + + // the logic for setting the before or after attribute for an action: + // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced. + // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it. + // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it. + // 4. If this action is not standard and the previous action is 1 sequence number lower and does not occur before this action, this action occurs after it. + // 5. If this action is not standard and the previous action does not have the same sequence number and the next action is 1 sequence number higher, this action occurs before it. + // 6. If this action is AppSearch and has all standard information, ignore it. + // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information. + // 8. Everything must be absolutely sequenced. + if ((null != previousActionSymbol && actionSymbol.Sequence == previousActionSymbol.Sequence) || (null != nextActionSymbol && actionSymbol.Sequence == nextActionSymbol.Sequence)) + { + needAbsoluteScheduling = true; + } + else if (null != nextActionSymbol && this.StandardActions.ContainsKey(nextActionSymbol.Id.Id) && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence) + { + actionSymbol.Before = nextActionSymbol.Action; + } + else if (null != previousActionSymbol && this.StandardActions.ContainsKey(previousActionSymbol.Id.Id) && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence) + { + actionSymbol.After = previousActionSymbol.Action; + } + else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence && previousActionSymbol.Before != actionSymbol.Action) + { + actionSymbol.After = previousActionSymbol.Action; + } + else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence != previousActionSymbol.Sequence && null != nextActionSymbol && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence) + { + actionSymbol.Before = nextActionSymbol.Action; + } + else if ("AppSearch" == actionSymbol.Action && null != standardActionRow && actionSymbol.Sequence == standardActionRow.Sequence && actionSymbol.Condition == standardActionRow.Condition) + { + // ignore an AppSearch row which has the WiX standard sequence and a standard condition + } + else if (null != standardActionRow && actionSymbol.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers + { + nonSequencedActionRows.Add(actionSymbol.Id.Id, actionSymbol); + } + else if (0 < actionSymbol.Sequence) + { + needAbsoluteScheduling = true; + } + } + else + { + suppressedRelativeActionRows.Add(actionSymbol.Id.Id, actionSymbol); + } + } + + // create the actions now that we know if they must be absolutely or relatively scheduled + foreach (var actionRow in actionSymbols) + { + var key = actionRow.Id.Id; + + if (needAbsoluteScheduling) + { + // remove any before/after information to ensure this is absolutely sequenced + actionRow.Before = null; + actionRow.After = null; + } + else if (nonSequencedActionRows.ContainsKey(key)) + { + // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after) + actionRow.Sequence = 0; + } + else if (suppressedRelativeActionRows.ContainsKey(key)) + { + // skip the suppressed relatively scheduled action rows + continue; + } + + // create the action element + this.CreateActionElement(actionRow); + } + } + } + } + else if (OutputType.Module == this.OutputType || this.TreatProductAsModule) // finalize the Module sequence tables + { + foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) + { + var sequenceTableName = sequenceTable.WindowsInstallerTableName(); + + // if suppressing UI elements, skip UI-related sequence tables + if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName)) + { + continue; + } + + var table = tables[String.Concat("Module", sequenceTableName)]; + + if (null != table) + { + foreach (var row in table.Rows) + { + var actionRow = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, row.FieldAsString(0))); + + actionRow.Action = row.FieldAsString(0); + + if (!row.IsColumnNull(1)) + { + actionRow.Sequence = row.FieldAsInteger(1); + } + + if (!row.IsColumnNull(2) && !row.IsColumnNull(3)) + { + switch (row.FieldAsInteger(3)) + { + case 0: + actionRow.Before = row.FieldAsString(2); + break; + case 1: + actionRow.After = row.FieldAsString(2); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3])); + break; + } + } + + if (!row.IsColumnNull(4)) + { + actionRow.Condition = row.FieldAsString(4); + } + + actionRow.SequenceTable = sequenceTable; + + // create action elements for non-standard actions + if (!this.StandardActions.ContainsKey(actionRow.Id.Id) || null != actionRow.After || null != actionRow.Before) + { + this.CreateActionElement(actionRow); + } + } + } + } + } + } + + /// + /// Finalize the Upgrade table. + /// + /// The collection of all tables. + /// + /// Decompile the rows from the Upgrade and LaunchCondition tables + /// created by the MajorUpgrade element. + /// + private void FinalizeUpgradeTable(TableIndexedCollection tables) + { + var launchConditionTable = tables["LaunchCondition"]; + var upgradeTable = tables["Upgrade"]; + string downgradeErrorMessage = null; + string disallowUpgradeErrorMessage = null; + + // find the DowngradePreventedCondition launch condition message + if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count) + { + foreach (var launchRow in launchConditionTable.Rows) + { + if (WixUpgradeConstants.DowngradePreventedCondition == Convert.ToString(launchRow[0])) + { + downgradeErrorMessage = Convert.ToString(launchRow[1]); + } + else if (WixUpgradeConstants.UpgradePreventedCondition == Convert.ToString(launchRow[0])) + { + disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]); + } + } + } + + if (null != upgradeTable && 0 < upgradeTable.Rows.Count) + { + XElement xMajorUpgrade = null; + + foreach (UpgradeRow upgradeRow in upgradeTable.Rows) + { + if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty) + { + var attr = upgradeRow.Attributes; + var removeFeatures = upgradeRow.Remove; + xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); + + if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) + { + xMajorUpgrade.SetAttributeValue("AllowSameVersionUpgrades", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures != (attr & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) + { + xMajorUpgrade.SetAttributeValue("MigrateFeatures", "no"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) + { + xMajorUpgrade.SetAttributeValue("IgnoreRemoveFailure", "yes"); + } + + if (!String.IsNullOrEmpty(removeFeatures)) + { + xMajorUpgrade.SetAttributeValue("RemoveFeatures", removeFeatures); + } + } + else if (WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty) + { + xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement); + xMajorUpgrade.SetAttributeValue("DowngradeErrorMessage", downgradeErrorMessage); + } + } + + if (xMajorUpgrade != null) + { + if (String.IsNullOrEmpty(downgradeErrorMessage)) + { + xMajorUpgrade.SetAttributeValue("AllowDowngrades", "yes"); + } + + if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage)) + { + xMajorUpgrade.SetAttributeValue("Disallow", "yes"); + xMajorUpgrade.SetAttributeValue("DisallowUpgradeErrorMessage", disallowUpgradeErrorMessage); + } + + var scheduledType = DetermineMajorUpgradeScheduling(tables); + if (scheduledType != "afterInstallValidate") + { + xMajorUpgrade.SetAttributeValue("Schedule", scheduledType); + } + + this.RootElement.Add(xMajorUpgrade); + } + } + } + + /// + /// Finalize the Verb table. + /// + /// The collection of all tables. + /// + /// The Extension table is a foreign table for the Verb table, but the + /// foreign key is only part of the primary key of the Extension table, + /// so it needs special logic to be nested properly. + /// + private void FinalizeVerbTable(TableIndexedCollection tables) + { + var xExtensions = this.IndexTableOneToMany(tables["Extension"]); + + var verbTable = tables["Verb"]; + if (null != verbTable) + { + foreach (var row in verbTable.Rows) + { + if (xExtensions.TryGetValue(row.FieldAsString(0), out var xVerbExtensions)) + { + var xVerb = this.GetIndexedElement(row); + + foreach (var xVerbExtension in xVerbExtensions) + { + xVerbExtension.Add(xVerb); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(0), "Extension")); + } + } + } + } + + /// + /// Get the path to a file in the source image. + /// + /// The file. + /// The path to the file in the source image. + private string GetSourcePath(XElement xFile) + { + var sourcePath = new StringBuilder(); + + var component = xFile.Parent; + + for (var xDirectory = component.Parent; null != xDirectory && xDirectory.Name.LocalName == "Directory"; xDirectory = xDirectory.Parent) + { + string name; + + var dirSourceName = xDirectory.Attribute("SourceName")?.Value; + var dirShortSourceName = xDirectory.Attribute("ShortSourceName")?.Value; + var dirShortName = xDirectory.Attribute("ShortName")?.Value; + var dirName = xDirectory.Attribute("Name")?.Value; + + if (!this.ShortNames && null != dirSourceName) + { + name = dirSourceName; + } + else if (null != dirShortSourceName) + { + name = dirShortSourceName; + } + else if (!this.ShortNames || null == dirShortName) + { + name = dirName; + } + else + { + name = dirShortName; + } + + if (0 == sourcePath.Length) + { + sourcePath.Append(name); + } + else + { + sourcePath.Insert(0, Path.DirectorySeparatorChar); + sourcePath.Insert(0, name); + } + } + + return sourcePath.ToString(); + } + + /// + /// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames). + /// + /// The name of the table to resolve. + /// The unsorted table names. + /// The sorted table names. + private void ResolveTableDependencies(string tableName, List unsortedTableNames, HashSet sortedTableNames) + { + unsortedTableNames.Remove(tableName); + + foreach (var columnDefinition in this.TableDefinitions[tableName].Columns) + { + // no dependency to resolve because this column doesn't reference another table + if (null == columnDefinition.KeyTable) + { + continue; + } + + foreach (var keyTable in columnDefinition.KeyTable.Split(';')) + { + if (tableName == keyTable) + { + continue; // self-referencing dependency + } + else if (sortedTableNames.Contains(keyTable)) + { + continue; // dependent table has already been sorted + } + else if (!this.TableDefinitions.Contains(keyTable)) + { + this.Messaging.Write(ErrorMessages.MissingTableDefinition(keyTable)); + } + else if (unsortedTableNames.Contains(keyTable)) + { + this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames); + } + else + { + // found a circular dependency, so ignore it (this assumes that the tables will + // use a finalize method to nest their elements since the ordering will not be + // deterministic + } + } + } + + sortedTableNames.Add(tableName); + } + + /// + /// Get the names of the tables to process in the order they should be processed, according to their dependencies. + /// + /// A StringCollection containing the ordered table names. + private HashSet GetOrderedTableNames() + { + var orderedTableNames = new HashSet(); + var unsortedTableNames = new List(this.TableDefinitions.Select(t => t.Name)); + + // resolve the dependencies for each table + while (0 < unsortedTableNames.Count) + { + this.ResolveTableDependencies(unsortedTableNames[0], unsortedTableNames, orderedTableNames); + } + + return orderedTableNames; + } + + /// + /// Initialize decompilation. + /// + /// The collection of all tables. + /// + private void InitializeDecompile(TableIndexedCollection tables, int codepage) + { + // reset all the state information + this.Compressed = false; + this.ShortNames = false; + + this.Singletons.Clear(); + this.IndexedElements.Clear(); + this.PatchTargetFiles.Clear(); + + // set the codepage if its not neutral (0) + if (0 != codepage) + { + this.RootElement.SetAttributeValue("Codepage", codepage); + } + + if (this.OutputType == OutputType.Module) + { + var table = tables["_SummaryInformation"]; + var row = table.Rows.SingleOrDefault(r => r.FieldAsInteger(0) == 9); + this.ModularizationGuid = row?.FieldAsString(1); + } + + // index the rows from the extension libraries + var indexedExtensionTables = new Dictionary>(); +#if TODO_DECOMPILER_EXTENSIONS + foreach (IDecompilerExtension extension in this.extensions) + { + // Get the optional library from the extension with the rows to be removed. + Library library = extension.GetLibraryToRemove(this.tableDefinitions); + if (null != library) + { + foreach (var section in library.Sections) + { + foreach (Table table in section.Tables) + { + foreach (Row row in table.Rows) + { + string primaryKey; + string tableName; + + // the Actions table needs to be handled specially + if ("WixAction" == table.Name) + { + primaryKey = row.FieldAsString(1); + + if (OutputType.Module == this.outputType) + { + tableName = String.Concat("Module", row.FieldAsString(0)); + } + else + { + tableName = row.FieldAsString(0); + } + } + else + { + primaryKey = row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter); + tableName = table.Name; + } + + if (null != primaryKey) + { + HashSet indexedExtensionRows; + if (!indexedExtensionTables.TryGetValue(tableName, out indexedExtensionRows)) + { + indexedExtensionRows = new HashSet(); + indexedExtensionTables.Add(tableName, indexedExtensionRows); + } + + indexedExtensionRows.Add(primaryKey); + } + } + } + } + } + } +#endif + + // remove the rows from the extension libraries (to allow full round-tripping) + foreach (var kvp in indexedExtensionTables) + { + var tableName = kvp.Key; + var indexedExtensionRows = kvp.Value; + + var table = tables[tableName]; + if (null != table) + { + var originalRows = new RowDictionary(table); + + // remove the original rows so that they can be added back if they should remain + table.Rows.Clear(); + + foreach (var row in originalRows.Values) + { + if (!indexedExtensionRows.Contains(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter))) + { + table.Rows.Add(row); + } + } + } + } + } + + /// + /// Decompile the tables. + /// + /// The output being decompiled. + private void DecompileTables(WindowsInstallerData output) + { + var orderedTableNames = this.GetOrderedTableNames(); + foreach (var tableName in orderedTableNames) + { + var table = output.Tables[tableName]; + + // table does not exist in this database or should not be decompiled + if (null == table || !this.DecompilableTable(output, tableName)) + { + continue; + } + + this.Messaging.Write(VerboseMessages.DecompilingTable(table.Name)); + + // empty tables may be kept with EnsureTable if the user set the proper option + if (0 == table.Rows.Count && this.SuppressDroppingEmptyTables) + { + this.RootElement.Add(new XElement(Names.EnsureTableElement, new XAttribute("Id", table.Name))); + } + + switch (table.Name) + { + case "_SummaryInformation": + // handled in FinalizeDecompile + break; + case "AdminExecuteSequence": + case "AdminUISequence": + case "AdvtExecuteSequence": + case "InstallExecuteSequence": + case "InstallUISequence": + case "ModuleAdminExecuteSequence": + case "ModuleAdminUISequence": + case "ModuleAdvtExecuteSequence": + case "ModuleInstallExecuteSequence": + case "ModuleInstallUISequence": + // handled in FinalizeSequenceTables + break; + case "ActionText": + this.DecompileActionTextTable(table); + break; + case "AdvtUISequence": + this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); + break; + case "AppId": + this.DecompileAppIdTable(table); + break; + case "AppSearch": + // handled in FinalizeSearchTables + break; + case "BBControl": + this.DecompileBBControlTable(table); + break; + case "Billboard": + this.DecompileBillboardTable(table); + break; + case "Binary": + this.DecompileBinaryTable(table); + break; + case "BindImage": + this.DecompileBindImageTable(table); + break; + case "CCPSearch": + // handled in FinalizeSearchTables + break; + case "CheckBox": + // handled in FinalizeCheckBoxTable + break; + case "Class": + this.DecompileClassTable(table); + break; + case "ComboBox": + this.DecompileComboBoxTable(table); + break; + case "Control": + this.DecompileControlTable(table); + break; + case "ControlCondition": + this.DecompileControlConditionTable(table); + break; + case "ControlEvent": + this.DecompileControlEventTable(table); + break; + case "CreateFolder": + this.DecompileCreateFolderTable(table); + break; + case "CustomAction": + this.DecompileCustomActionTable(table); + break; + case "CompLocator": + this.DecompileCompLocatorTable(table); + break; + case "Complus": + this.DecompileComplusTable(table); + break; + case "Component": + this.DecompileComponentTable(table); + break; + case "Condition": + this.DecompileConditionTable(table); + break; + case "Dialog": + this.DecompileDialogTable(table); + break; + case "Directory": + this.DecompileDirectoryTable(table); + break; + case "DrLocator": + this.DecompileDrLocatorTable(table); + break; + case "DuplicateFile": + this.DecompileDuplicateFileTable(table); + break; + case "Environment": + this.DecompileEnvironmentTable(table); + break; + case "Error": + this.DecompileErrorTable(table); + break; + case "EventMapping": + this.DecompileEventMappingTable(table); + break; + case "Extension": + this.DecompileExtensionTable(table); + break; + case "ExternalFiles": + this.DecompileExternalFilesTable(table); + break; + case "FamilyFileRanges": + // handled in FinalizeFamilyFileRangesTable + break; + case "Feature": + this.DecompileFeatureTable(table); + break; + case "FeatureComponents": + this.DecompileFeatureComponentsTable(table); + break; + case "File": + this.DecompileFileTable(table); + break; + case "FileSFPCatalog": + this.DecompileFileSFPCatalogTable(table); + break; + case "Font": + this.DecompileFontTable(table); + break; + case "Icon": + this.DecompileIconTable(table); + break; + case "ImageFamilies": + this.DecompileImageFamiliesTable(table); + break; + case "IniFile": + this.DecompileIniFileTable(table); + break; + case "IniLocator": + this.DecompileIniLocatorTable(table); + break; + case "IsolatedComponent": + this.DecompileIsolatedComponentTable(table); + break; + case "LaunchCondition": + this.DecompileLaunchConditionTable(table); + break; + case "ListBox": + this.DecompileListBoxTable(table); + break; + case "ListView": + this.DecompileListViewTable(table); + break; + case "LockPermissions": + this.DecompileLockPermissionsTable(table); + break; + case "Media": + this.DecompileMediaTable(table); + break; + case "MIME": + this.DecompileMIMETable(table); + break; + case "ModuleAdvtUISequence": + this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name)); + break; + case "ModuleComponents": + // handled by DecompileComponentTable (since the ModuleComponents table + // rows are created by nesting components under the Module element) + break; + case "ModuleConfiguration": + this.DecompileModuleConfigurationTable(table); + break; + case "ModuleDependency": + this.DecompileModuleDependencyTable(table); + break; + case "ModuleExclusion": + this.DecompileModuleExclusionTable(table); + break; + case "ModuleIgnoreTable": + this.DecompileModuleIgnoreTableTable(table); + break; + case "ModuleSignature": + this.DecompileModuleSignatureTable(table); + break; + case "ModuleSubstitution": + this.DecompileModuleSubstitutionTable(table); + break; + case "MoveFile": + this.DecompileMoveFileTable(table); + break; + case "MsiAssembly": + // handled in FinalizeFileTable + break; + case "MsiDigitalCertificate": + this.DecompileMsiDigitalCertificateTable(table); + break; + case "MsiDigitalSignature": + this.DecompileMsiDigitalSignatureTable(table); + break; + case "MsiEmbeddedChainer": + this.DecompileMsiEmbeddedChainerTable(table); + break; + case "MsiEmbeddedUI": + this.DecompileMsiEmbeddedUITable(table); + break; + case "MsiLockPermissionsEx": + this.DecompileMsiLockPermissionsExTable(table); + break; + case "MsiPackageCertificate": + this.DecompileMsiPackageCertificateTable(table); + break; + case "MsiPatchCertificate": + this.DecompileMsiPatchCertificateTable(table); + break; + case "MsiShortcutProperty": + this.DecompileMsiShortcutPropertyTable(table); + break; + case "ODBCAttribute": + this.DecompileODBCAttributeTable(table); + break; + case "ODBCDataSource": + this.DecompileODBCDataSourceTable(table); + break; + case "ODBCDriver": + this.DecompileODBCDriverTable(table); + break; + case "ODBCSourceAttribute": + this.DecompileODBCSourceAttributeTable(table); + break; + case "ODBCTranslator": + this.DecompileODBCTranslatorTable(table); + break; + case "PatchMetadata": + this.DecompilePatchMetadataTable(table); + break; + case "PatchSequence": + this.DecompilePatchSequenceTable(table); + break; + case "ProgId": + this.DecompileProgIdTable(table); + break; + case "Properties": + this.DecompilePropertiesTable(table); + break; + case "Property": + this.DecompilePropertyTable(table); + break; + case "PublishComponent": + this.DecompilePublishComponentTable(table); + break; + case "RadioButton": + this.DecompileRadioButtonTable(table); + break; + case "Registry": + this.DecompileRegistryTable(table); + break; + case "RegLocator": + this.DecompileRegLocatorTable(table); + break; + case "RemoveFile": + this.DecompileRemoveFileTable(table); + break; + case "RemoveIniFile": + this.DecompileRemoveIniFileTable(table); + break; + case "RemoveRegistry": + this.DecompileRemoveRegistryTable(table); + break; + case "ReserveCost": + this.DecompileReserveCostTable(table); + break; + case "SelfReg": + this.DecompileSelfRegTable(table); + break; + case "ServiceControl": + this.DecompileServiceControlTable(table); + break; + case "ServiceInstall": + this.DecompileServiceInstallTable(table); + break; + case "SFPCatalog": + this.DecompileSFPCatalogTable(table); + break; + case "Shortcut": + this.DecompileShortcutTable(table); + break; + case "Signature": + this.DecompileSignatureTable(table); + break; + case "TargetFiles_OptionalData": + this.DecompileTargetFiles_OptionalDataTable(table); + break; + case "TargetImages": + this.DecompileTargetImagesTable(table); + break; + case "TextStyle": + this.DecompileTextStyleTable(table); + break; + case "TypeLib": + this.DecompileTypeLibTable(table); + break; + case "Upgrade": + this.DecompileUpgradeTable(table); + break; + case "UpgradedFiles_OptionalData": + this.DecompileUpgradedFiles_OptionalDataTable(table); + break; + case "UpgradedFilesToIgnore": + this.DecompileUpgradedFilesToIgnoreTable(table); + break; + case "UpgradedImages": + this.DecompileUpgradedImagesTable(table); + break; + case "UIText": + this.DecompileUITextTable(table); + break; + case "Verb": + this.DecompileVerbTable(table); + break; + + default: +#if TODO_DECOMPILER_EXTENSIONS + if (this.ExtensionsByTableName.TryGetValue(table.Name, out var extension) + { + extension.DecompileTable(table); + } + else +#endif + if (!this.SuppressCustomTables) + { + this.DecompileCustomTable(table); + } + break; + } + } + } + + /// + /// Determine if a particular table should be decompiled with the current settings. + /// + /// The output being decompiled. + /// The name of a table. + /// true if the table should be decompiled; false otherwise. + private bool DecompilableTable(WindowsInstallerData output, string tableName) + { + switch (tableName) + { + case "ActionText": + case "BBControl": + case "Billboard": + case "CheckBox": + case "Control": + case "ControlCondition": + case "ControlEvent": + case "Dialog": + case "Error": + case "EventMapping": + case "RadioButton": + case "TextStyle": + case "UIText": + return !this.SuppressUI; + case "ModuleAdminExecuteSequence": + case "ModuleAdminUISequence": + case "ModuleAdvtExecuteSequence": + case "ModuleAdvtUISequence": + case "ModuleComponents": + case "ModuleConfiguration": + case "ModuleDependency": + case "ModuleIgnoreTable": + case "ModuleInstallExecuteSequence": + case "ModuleInstallUISequence": + case "ModuleExclusion": + case "ModuleSignature": + case "ModuleSubstitution": + if (OutputType.Module != output.Type) + { + this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + case "ExternalFiles": + case "FamilyFileRanges": + case "ImageFamilies": + case "PatchMetadata": + case "PatchSequence": + case "Properties": + case "TargetFiles_OptionalData": + case "TargetImages": + case "UpgradedFiles_OptionalData": + case "UpgradedFilesToIgnore": + case "UpgradedImages": + if (OutputType.PatchCreation != output.Type) + { + this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + case "MsiPatchHeaders": + case "MsiPatchMetadata": + case "MsiPatchOldAssemblyName": + case "MsiPatchOldAssemblyFile": + case "MsiPatchSequence": + case "Patch": + case "PatchPackage": + this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName)); + return false; + case "_SummaryInformation": + return true; + case "_Validation": + case "MsiAssemblyName": + case "MsiFileHash": + return false; + default: // all other tables are allowed in any output except for a patch creation package + if (OutputType.PatchCreation == output.Type) + { + this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName)); + return false; + } + else + { + return true; + } + } + } + + /// + /// Decompile the _SummaryInformation table. + /// + /// The tables to decompile. + private void FinalizeSummaryInformationStream(TableIndexedCollection tables) + { + var table = tables["_SummaryInformation"]; + + if (OutputType.Module == this.OutputType || OutputType.Product == this.OutputType) + { + var xSummaryInformation = new XElement(Names.SummaryInformationElement); + + foreach (var row in table.Rows) + { + var value = row.FieldAsString(1); + + if (!String.IsNullOrEmpty(value)) + { + switch (row.FieldAsInteger(0)) + { + case 1: + if ("1252" != value) + { + xSummaryInformation.SetAttributeValue("Codepage", value); + } + break; + case 3: + { + var productName = this.RootElement.Attribute("Name")?.Value; + if (value != productName) + { + xSummaryInformation.SetAttributeValue("Description", value); + } + break; + } + case 4: + { + var productManufacturer = this.RootElement.Attribute("Manufacturer")?.Value; + if (value != productManufacturer) + { + xSummaryInformation.SetAttributeValue("Manufacturer", value); + } + break; + } + case 5: + if ("Installer" != value) + { + xSummaryInformation.SetAttributeValue("Keywords", value); + } + break; + case 7: + var template = value.Split(';'); + if (0 < template.Length && 0 < template[template.Length - 1].Length) + { + this.RootElement.SetAttributeValue("Language", template[template.Length - 1]); + } + break; + case 14: + var installerVersion = row.FieldAsInteger(1); + // Default InstallerVersion. + if (installerVersion != 500) + { + this.RootElement.SetAttributeValue("InstallerVersion", installerVersion); + } + break; + case 15: + var wordCount = row.FieldAsInteger(1); + if (0x1 == (wordCount & 0x1)) + { + this.ShortNames = true; + if (OutputType.Product == this.OutputType) + { + this.RootElement.SetAttributeValue("ShortNames", "yes"); + } + } + + if (0x2 == (wordCount & 0x2)) + { + this.Compressed = true; + + if (OutputType.Product == this.OutputType) + { + this.RootElement.SetAttributeValue("Compressed", "yes"); + } + } + + if (OutputType.Product == this.OutputType) + { + if (0x8 == (wordCount & 0x8)) + { + this.RootElement.SetAttributeValue("Scope", "perUser"); + } + else + { + var xAllUsers = this.RootElement.Elements(Names.PropertyElement).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS"); + if (xAllUsers?.Attribute("Value")?.Value == "1") + { + xAllUsers?.Remove(); + } + } + } + + break; + } + } + } + + if (xSummaryInformation.HasAttributes) + { + this.RootElement.Add(xSummaryInformation); + } + } + else + { + var xPatchInformation = new XElement(Names.PatchInformationElement); + + foreach (var row in table.Rows) + { + var propertyId = row.FieldAsInteger(0); + var value = row.FieldAsString(1); + + if (!String.IsNullOrEmpty(value)) + { + switch (propertyId) + { + case 1: + if ("1252" != value) + { + xPatchInformation.SetAttributeValue("SummaryCodepage", value); + } + break; + case 3: + xPatchInformation.SetAttributeValue("Description", value); + break; + case 4: + xPatchInformation.SetAttributeValue("Manufacturer", value); + break; + case 5: + if ("Installer,Patching,PCP,Database" != value) + { + xPatchInformation.SetAttributeValue("Keywords", value); + } + break; + case 6: + xPatchInformation.SetAttributeValue("Comments", value); + break; + case 19: + var security = Convert.ToInt32(value, CultureInfo.InvariantCulture); + switch (security) + { + case 0: + xPatchInformation.SetAttributeValue("ReadOnly", "no"); + break; + case 4: + xPatchInformation.SetAttributeValue("ReadOnly", "yes"); + break; + } + break; + } + } + } + + this.RootElement.Add(xPatchInformation); + } + } + + /// + /// Decompile the ActionText table. + /// + /// The table to decompile. + private void DecompileActionTextTable(Table table) + { + foreach (var row in table.Rows) + { + var progressText = new XElement(Names.ProgressTextElement, + new XAttribute("Action", row.FieldAsString(0)), + row.IsColumnNull(1) ? null : new XAttribute("Message", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("Template", row.FieldAsString(2))); + + this.UIElement.Add(progressText); + } + } + + /// + /// Decompile the AppId table. + /// + /// The table to decompile. + private void DecompileAppIdTable(Table table) + { + foreach (var row in table.Rows) + { + var appId = new XElement(Names.AppIdElement, + new XAttribute("Advertise", "yes"), + new XAttribute("Id", row.FieldAsString(0)), + row.IsColumnNull(1) ? null : new XAttribute("RemoteServerName", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("LocalService", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("ServiceParameters", row.FieldAsString(3)), + row.IsColumnNull(4) ? null : new XAttribute("DllSurrogate", row.FieldAsString(4)), + row.IsColumnNull(5) || row.FieldAsInteger(5) != 1 ? null : new XAttribute("ActivateAtStorage", "yes"), + row.IsColumnNull(6) || row.FieldAsInteger(6) != 1 ? null : new XAttribute("RunAsInteractiveUser", "yes")); + + this.RootElement.Add(appId); + this.IndexElement(row, appId); + } + } + + /// + /// Decompile the BBControl table. + /// + /// The table to decompile. + private void DecompileBBControlTable(Table table) + { + foreach (BBControlRow bbControlRow in table.Rows) + { + var xControl = new XElement(Names.ControlElement, + new XAttribute("Id", bbControlRow.BBControl), + new XAttribute("Type", bbControlRow.Type), + new XAttribute("X", bbControlRow.X), + new XAttribute("Y", bbControlRow.Y), + new XAttribute("Width", bbControlRow.Width), + new XAttribute("Height", bbControlRow.Height), + null == bbControlRow.Text ? null : new XAttribute("Text", bbControlRow.Text)); + + if (null != bbControlRow[7]) + { + SetControlAttributes(bbControlRow.Attributes, xControl); + } + + if (this.TryGetIndexedElement("Billboard", out var xBillboard, bbControlRow.Billboard)) + { + xBillboard.Add(xControl); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard")); + } + } + } + + /// + /// Decompile the Billboard table. + /// + /// The table to decompile. + private void DecompileBillboardTable(Table table) + { + var billboards = new SortedList(); + + foreach (var row in table.Rows) + { + var xBillboard = new XElement(Names.BillboardElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Feature", row.FieldAsString(1))); + + this.IndexElement(row, xBillboard); + billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row); + } + + var billboardActions = new Dictionary(); + + foreach (var row in billboards.Values) + { + var xBillboard = this.GetIndexedElement(row); + + if (!billboardActions.TryGetValue(row.FieldAsString(2), out var xBillboardAction)) + { + xBillboardAction = new XElement(Names.BillboardActionElement, + new XAttribute("Id", row.FieldAsString(2))); + + this.UIElement.Add(xBillboardAction); + billboardActions.Add(row.FieldAsString(2), xBillboardAction); + } + + xBillboardAction.Add(xBillboard); + } + } + + /// + /// Decompile the Binary table. + /// + /// The table to decompile. + private void DecompileBinaryTable(Table table) + { + foreach (var row in table.Rows) + { + var xBinary = new XElement(Names.BinaryElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); + + this.RootElement.Add(xBinary); + } + } + + /// + /// Decompile the BindImage table. + /// + /// The table to decompile. + private void DecompileBindImageTable(Table table) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) + { + xFile.SetAttributeValue("BindPath", row.FieldAsString(1)); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); + } + } + } + + /// + /// Decompile the Class table. + /// + /// The table to decompile. + private void DecompileClassTable(Table table) + { + foreach (var row in table.Rows) + { + var xClass = new XElement(Names.ClassElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Advertise", "yes"), + new XAttribute("Context", row.FieldAsString(1)), + row.IsColumnNull(4) ? null : new XAttribute("Description", row.FieldAsString(4)), + row.IsColumnNull(5) ? null : new XAttribute("AppId", row.FieldAsString(5)), + row.IsColumnNull(7) ? null : new XAttribute("Icon", row.FieldAsString(7)), + row.IsColumnNull(8) ? null : new XAttribute("IconIndex", row.FieldAsString(8)), + row.IsColumnNull(9) ? null : new XAttribute("Handler", row.FieldAsString(9)), + row.IsColumnNull(10) ? null : new XAttribute("Argument", row.FieldAsString(10))); + + if (!row.IsColumnNull(6)) + { + var fileTypeMaskStrings = row.FieldAsString(6).Split(';'); + + try + { + foreach (var fileTypeMaskString in fileTypeMaskStrings) + { + var fileTypeMaskParts = fileTypeMaskString.Split(','); + + if (4 == fileTypeMaskParts.Length) + { + var xFileTypeMask = new XElement(Names.FileTypeMaskElement, + new XAttribute("Offset", Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture)), + new XAttribute("Mask", fileTypeMaskParts[2]), + new XAttribute("Value", fileTypeMaskParts[3])); + + xClass.Add(xFileTypeMask); + } + else + { + // TODO: warn + } + } + } + catch (FormatException) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + } + catch (OverflowException) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + } + } + + if (!row.IsColumnNull(12)) + { + if (1 == row.FieldAsInteger(12)) + { + xClass.SetAttributeValue("RelativePath", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12])); + } + } + + this.AddChildToParent("Component", xClass, row, 2); + this.IndexElement(row, xClass); + } + } + + /// + /// Decompile the ComboBox table. + /// + /// The table to decompile. + private void DecompileComboBoxTable(Table table) + { + // sort the combo boxes by their property and order + var comboBoxRows = table.Rows.Select(row => row).OrderBy(row => String.Format("{0}|{1:0000000000}", row.FieldAsString(0), row.FieldAsInteger(1))); + + XElement xComboBox = null; + string property = null; + foreach (var row in comboBoxRows) + { + if (null == xComboBox || row.FieldAsString(0) != property) + { + property = row.FieldAsString(0); + + xComboBox = new XElement(Names.ComboBoxElement, + new XAttribute("Property", property)); + + this.UIElement.Add(xComboBox); + } + + var xListItem = new XElement(Names.ListItemElement, + new XAttribute("Value", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); + xComboBox.Add(xListItem); + } + } + + /// + /// Decompile the Control table. + /// + /// The table to decompile. + private void DecompileControlTable(Table table) + { + foreach (ControlRow controlRow in table.Rows) + { + var xControl = new XElement(Names.ControlElement, + new XAttribute("Id", controlRow.Control), + new XAttribute("Type", controlRow.Type), + new XAttribute("X", controlRow.X), + new XAttribute("Y", controlRow.Y), + new XAttribute("Width", controlRow.Width), + new XAttribute("Height", controlRow.Height), + new XAttribute("Text", controlRow.Text)); + + if (!controlRow.IsColumnNull(7)) + { + string[] specialAttributes; + + // sets various common attributes like Disabled, Indirect, Integer, ... + SetControlAttributes(controlRow.Attributes, xControl); + + switch (controlRow.Type) + { + case "Bitmap": + specialAttributes = BitmapControlAttributes; + break; + case "CheckBox": + specialAttributes = CheckboxControlAttributes; + break; + case "ComboBox": + specialAttributes = ComboboxControlAttributes; + break; + case "DirectoryCombo": + specialAttributes = VolumeControlAttributes; + break; + case "Edit": + specialAttributes = EditControlAttributes; + break; + case "Icon": + specialAttributes = IconControlAttributes; + break; + case "ListBox": + specialAttributes = ListboxControlAttributes; + break; + case "ListView": + specialAttributes = ListviewControlAttributes; + break; + case "MaskedEdit": + specialAttributes = EditControlAttributes; + break; + case "PathEdit": + specialAttributes = EditControlAttributes; + break; + case "ProgressBar": + specialAttributes = ProgressControlAttributes; + break; + case "PushButton": + specialAttributes = ButtonControlAttributes; + break; + case "RadioButtonGroup": + specialAttributes = RadioControlAttributes; + break; + case "Text": + specialAttributes = TextControlAttributes; + break; + case "VolumeCostList": + specialAttributes = VolumeControlAttributes; + break; + case "VolumeSelectCombo": + specialAttributes = VolumeControlAttributes; + break; + default: + specialAttributes = null; + break; + } + + if (null != specialAttributes) + { + var iconSizeSet = false; + + for (var i = 16; 32 > i; i++) + { + if (1 == ((controlRow.Attributes >> i) & 1)) + { + string attribute = null; + + if (specialAttributes.Length > (i - 16)) + { + attribute = specialAttributes[i - 16]; + } + + // unknown attribute + if (null == attribute) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); + continue; + } + + switch (attribute) + { + case "Bitmap": + xControl.SetAttributeValue("Bitmap", "yes"); + break; + case "CDROM": + xControl.SetAttributeValue("CDROM", "yes"); + break; + case "ComboList": + xControl.SetAttributeValue("ComboList", "yes"); + break; + case "ElevationShield": + xControl.SetAttributeValue("ElevationShield", "yes"); + break; + case "Fixed": + xControl.SetAttributeValue("Fixed", "yes"); + break; + case "FixedSize": + xControl.SetAttributeValue("FixedSize", "yes"); + break; + case "Floppy": + xControl.SetAttributeValue("Floppy", "yes"); + break; + case "FormatSize": + xControl.SetAttributeValue("FormatSize", "yes"); + break; + case "HasBorder": + xControl.SetAttributeValue("HasBorder", "yes"); + break; + case "Icon": + xControl.SetAttributeValue("Icon", "yes"); + break; + case "Icon16": + if (iconSizeSet) + { + xControl.SetAttributeValue("IconSize", "48"); + } + else + { + iconSizeSet = true; + xControl.SetAttributeValue("IconSize", "16"); + } + break; + case "Icon32": + if (iconSizeSet) + { + xControl.SetAttributeValue("IconSize", "48"); + } + else + { + iconSizeSet = true; + xControl.SetAttributeValue("IconSize", "32"); + } + break; + case "Image": + xControl.SetAttributeValue("Image", "yes"); + break; + case "Multiline": + xControl.SetAttributeValue("Multiline", "yes"); + break; + case "NoPrefix": + xControl.SetAttributeValue("NoPrefix", "yes"); + break; + case "NoWrap": + xControl.SetAttributeValue("NoWrap", "yes"); + break; + case "Password": + xControl.SetAttributeValue("Password", "yes"); + break; + case "ProgressBlocks": + xControl.SetAttributeValue("ProgressBlocks", "yes"); + break; + case "PushLike": + xControl.SetAttributeValue("PushLike", "yes"); + break; + case "RAMDisk": + xControl.SetAttributeValue("RAMDisk", "yes"); + break; + case "Remote": + xControl.SetAttributeValue("Remote", "yes"); + break; + case "Removable": + xControl.SetAttributeValue("Removable", "yes"); + break; + case "ShowRollbackCost": + xControl.SetAttributeValue("ShowRollbackCost", "yes"); + break; + case "Sorted": + xControl.SetAttributeValue("Sorted", "yes"); + break; + case "Transparent": + xControl.SetAttributeValue("Transparent", "yes"); + break; + case "UserLanguage": + xControl.SetAttributeValue("UserLanguage", "yes"); + break; + default: + throw new InvalidOperationException($"Unknown control attribute: '{attribute}'."); + } + } + } + } + else if (0 < (controlRow.Attributes & 0xFFFF0000)) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes)); + } + } + + // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef + if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", controlRow.Type)) + { + xControl.SetAttributeValue("Property", controlRow.Property); + } + + if (null != controlRow.Help) + { + var help = controlRow.Help.Split('|'); + + if (2 == help.Length) + { + if (0 < help[0].Length) + { + xControl.SetAttributeValue("ToolTip", help[0]); + } + + if (0 < help[1].Length) + { + xControl.SetAttributeValue("Help", help[1]); + } + } + } + + this.IndexElement(controlRow, xControl); + } + } + + /// + /// Decompile the ControlCondition table. + /// + /// The table to decompile. + private void DecompileControlConditionTable(Table table) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) + { + switch (row.FieldAsString(2)) + { + case "Default": + xControl.SetAttributeValue("DefaultCondition", row.FieldAsString(3)); + break; + case "Disable": + xControl.SetAttributeValue("DisableCondition", row.FieldAsString(3)); + break; + case "Enable": + xControl.SetAttributeValue("EnableCondition", row.FieldAsString(3)); + break; + case "Hide": + xControl.SetAttributeValue("HideCondition", row.FieldAsString(3)); + break; + case "Show": + xControl.SetAttributeValue("ShowCondition", row.FieldAsString(3)); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + break; + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); + } + } + } + + /// + /// Decompile the ControlEvent table. + /// + /// The table to decompile. + private void DecompileControlEventTable(Table table) + { + var controlEvents = new SortedList(); + + foreach (var row in table.Rows) + { + var xPublish = new XElement(Names.PublishElement, + new XAttribute("Condition", row.FieldAsString(4))); + + var publishEvent = row.FieldAsString(2); + if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal)) + { + xPublish.SetAttributeValue("Property", publishEvent.Substring(1, publishEvent.Length - 2)); + + if ("{}" != row.FieldAsString(3)) + { + xPublish.SetAttributeValue("Value", row.FieldAsString(3)); + } + } + else + { + xPublish.SetAttributeValue("Event", publishEvent); + xPublish.SetAttributeValue("Value", row.FieldAsString(3)); + } + + controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row.FieldAsString(0), row.FieldAsString(1), row.FieldAsNullableInteger(5) ?? 0, row.FieldAsString(2), row.FieldAsString(3), row.FieldAsString(4)), row); + + this.IndexElement(row, xPublish); + } + + foreach (var row in controlEvents.Values) + { + if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) + { + var xPublish = this.GetIndexedElement(row); + xControl.Add(xPublish); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); + } + } + } + + /// + /// Decompile a custom table. + /// + /// The table to decompile. + private void DecompileCustomTable(Table table) + { + if (0 < table.Rows.Count || this.SuppressDroppingEmptyTables) + { + this.Messaging.Write(WarningMessages.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name)); + + var xCustomTable = new XElement(Names.CustomTableElement, + new XAttribute("Id", table.Name)); + + foreach (var columnDefinition in table.Definition.Columns) + { + var xColumn = new XElement(Names.ColumnElement, + new XAttribute("Id", columnDefinition.Name), + columnDefinition.Description == null ? null : new XAttribute("Description", columnDefinition.Description), + columnDefinition.KeyTable == null ? null : new XAttribute("KeyTable", columnDefinition.KeyTable), + !columnDefinition.KeyColumn.HasValue ? null : new XAttribute("KeyColumn", columnDefinition.KeyColumn.Value), + !columnDefinition.IsLocalizable ? null : new XAttribute("Localizable", "yes"), + !columnDefinition.MaxValue.HasValue ? null : new XAttribute("MaxValue", columnDefinition.MaxValue.Value), + !columnDefinition.MinValue.HasValue ? null : new XAttribute("MinValue", columnDefinition.MinValue.Value), + !columnDefinition.Nullable ? null : new XAttribute("Nullable", "yes"), + !columnDefinition.PrimaryKey ? null : new XAttribute("PrimaryKey", "yes"), + columnDefinition.Possibilities == null ? null : new XAttribute("Possibilities", "yes"), + new XAttribute("Width", columnDefinition.Length)); + + if (ColumnCategory.Unknown != columnDefinition.Category) + { + switch (columnDefinition.Category) + { + case ColumnCategory.Text: + xColumn.SetAttributeValue("Category", "text"); + break; + case ColumnCategory.UpperCase: + xColumn.SetAttributeValue("Category", "upperCase"); + break; + case ColumnCategory.LowerCase: + xColumn.SetAttributeValue("Category", "lowerCase"); + break; + case ColumnCategory.Integer: + xColumn.SetAttributeValue("Category", "integer"); + break; + case ColumnCategory.DoubleInteger: + xColumn.SetAttributeValue("Category", "doubleInteger"); + break; + case ColumnCategory.TimeDate: + xColumn.SetAttributeValue("Category", "timeDate"); + break; + case ColumnCategory.Identifier: + xColumn.SetAttributeValue("Category", "identifier"); + break; + case ColumnCategory.Property: + xColumn.SetAttributeValue("Category", "property"); + break; + case ColumnCategory.Filename: + xColumn.SetAttributeValue("Category", "filename"); + break; + case ColumnCategory.WildCardFilename: + xColumn.SetAttributeValue("Category", "wildCardFilename"); + break; + case ColumnCategory.Path: + xColumn.SetAttributeValue("Category", "path"); + break; + case ColumnCategory.Paths: + xColumn.SetAttributeValue("Category", "paths"); + break; + case ColumnCategory.AnyPath: + xColumn.SetAttributeValue("Category", "anyPath"); + break; + case ColumnCategory.DefaultDir: + xColumn.SetAttributeValue("Category", "defaultDir"); + break; + case ColumnCategory.RegPath: + xColumn.SetAttributeValue("Category", "regPath"); + break; + case ColumnCategory.Formatted: + xColumn.SetAttributeValue("Category", "formatted"); + break; + case ColumnCategory.FormattedSDDLText: + xColumn.SetAttributeValue("Category", "formattedSddl"); + break; + case ColumnCategory.Template: + xColumn.SetAttributeValue("Category", "template"); + break; + case ColumnCategory.Condition: + xColumn.SetAttributeValue("Category", "condition"); + break; + case ColumnCategory.Guid: + xColumn.SetAttributeValue("Category", "guid"); + break; + case ColumnCategory.Version: + xColumn.SetAttributeValue("Category", "version"); + break; + case ColumnCategory.Language: + xColumn.SetAttributeValue("Category", "language"); + break; + case ColumnCategory.Binary: + xColumn.SetAttributeValue("Category", "binary"); + break; + case ColumnCategory.CustomSource: + xColumn.SetAttributeValue("Category", "customSource"); + break; + case ColumnCategory.Cabinet: + xColumn.SetAttributeValue("Category", "cabinet"); + break; + case ColumnCategory.Shortcut: + xColumn.SetAttributeValue("Category", "shortcut"); + break; + default: + throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'."); + } + } + + if (ColumnModularizeType.None != columnDefinition.ModularizeType) + { + switch (columnDefinition.ModularizeType) + { + case ColumnModularizeType.Column: + xColumn.SetAttributeValue("Modularize", "Column"); + break; + case ColumnModularizeType.Condition: + xColumn.SetAttributeValue("Modularize", "Condition"); + break; + case ColumnModularizeType.Icon: + xColumn.SetAttributeValue("Modularize", "Icon"); + break; + case ColumnModularizeType.Property: + xColumn.SetAttributeValue("Modularize", "Property"); + break; + case ColumnModularizeType.SemicolonDelimited: + xColumn.SetAttributeValue("Modularize", "SemicolonDelimited"); + break; + default: + throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'."); + } + } + + if (ColumnType.Unknown != columnDefinition.Type) + { + switch (columnDefinition.Type) + { + case ColumnType.Localized: + xColumn.SetAttributeValue("Localizable", "yes"); + xColumn.SetAttributeValue("Type", "string"); + break; + case ColumnType.Number: + xColumn.SetAttributeValue("Type", "int"); + break; + case ColumnType.Object: + xColumn.SetAttributeValue("Type", "binary"); + break; + case ColumnType.Preserved: + case ColumnType.String: + xColumn.SetAttributeValue("Type", "string"); + break; + default: + throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type}'."); + } + } + + xCustomTable.Add(xColumn); + } + + foreach (var row in table.Rows) + { + var xRow = new XElement(Names.RowElement); + + foreach (var field in row.Fields.Where(f => f.Data != null)) + { + var xData = new XElement(Names.DataElement, + new XAttribute("Column", field.Column.Name), + new XAttribute("Value", field.AsString())); + + xRow.Add(xData); + } + + xCustomTable.Add(xRow); + } + + this.RootElement.Add(xCustomTable); + } + } + + /// + /// Decompile the CreateFolder table. + /// + /// The table to decompile. + private void DecompileCreateFolderTable(Table table) + { + foreach (var row in table.Rows) + { + var xCreateFolder = new XElement(Names.CreateFolderElement, + new XAttribute("Directory", row.FieldAsString(0))); + + this.AddChildToParent("Component", xCreateFolder, row, 1); + this.IndexElement(row, xCreateFolder); + } + } + + /// + /// Decompile the CustomAction table. + /// + /// The table to decompile. + private void DecompileCustomActionTable(Table table) + { + foreach (var row in table.Rows) + { + var xCustomAction = new XElement(Names.CustomActionElement, + new XAttribute("Id", row.FieldAsString(0))); + + var type = row.FieldAsInteger(1); + + if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (type & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget)) + { + xCustomAction.SetAttributeValue("HideTarget", "yes"); + } + + if (WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate == (type & WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate)) + { + xCustomAction.SetAttributeValue("Impersonate", "no"); + } + + if (WindowsInstallerConstants.MsidbCustomActionTypeTSAware == (type & WindowsInstallerConstants.MsidbCustomActionTypeTSAware)) + { + xCustomAction.SetAttributeValue("TerminalServerAware", "yes"); + } + + if (WindowsInstallerConstants.MsidbCustomActionType64BitScript == (type & WindowsInstallerConstants.MsidbCustomActionType64BitScript)) + { + xCustomAction.SetAttributeValue("Bitness", "always64"); + } + else if (WindowsInstallerConstants.MsidbCustomActionTypeVBScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) || + WindowsInstallerConstants.MsidbCustomActionTypeJScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeJScript)) + { + xCustomAction.SetAttributeValue("Bitness", "always32"); + } + + switch (type & WindowsInstallerConstants.MsidbCustomActionTypeExecuteBits) + { + case 0: + // this is the default value + break; + case WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence: + xCustomAction.SetAttributeValue("Execute", "firstSequence"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess: + xCustomAction.SetAttributeValue("Execute", "oncePerProcess"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat: + xCustomAction.SetAttributeValue("Execute", "secondSequence"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInScript: + xCustomAction.SetAttributeValue("Execute", "deferred"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeRollback: + xCustomAction.SetAttributeValue("Execute", "rollback"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeCommit: + xCustomAction.SetAttributeValue("Execute", "commit"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + switch (type & WindowsInstallerConstants.MsidbCustomActionTypeReturnBits) + { + case 0: + // this is the default value + break; + case WindowsInstallerConstants.MsidbCustomActionTypeContinue: + xCustomAction.SetAttributeValue("Return", "ignore"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeAsync: + xCustomAction.SetAttributeValue("Return", "asyncWait"); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeAsync + WindowsInstallerConstants.MsidbCustomActionTypeContinue: + xCustomAction.SetAttributeValue("Return", "asyncNoWait"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + var source = type & WindowsInstallerConstants.MsidbCustomActionTypeSourceBits; + switch (source) + { + case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: + xCustomAction.SetAttributeValue("BinaryRef", row.FieldAsString(2)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: + if (!row.IsColumnNull(2)) + { + xCustomAction.SetAttributeValue("FileRef", row.FieldAsString(2)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeDirectory: + if (!row.IsColumnNull(2)) + { + xCustomAction.SetAttributeValue("Directory", row.FieldAsString(2)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeProperty: + xCustomAction.SetAttributeValue("Property", row.FieldAsString(2)); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + switch (type & WindowsInstallerConstants.MsidbCustomActionTypeTargetBits) + { + case WindowsInstallerConstants.MsidbCustomActionTypeDll: + xCustomAction.SetAttributeValue("DllEntry", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeExe: + xCustomAction.SetAttributeValue("ExeCommand", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeTextData: + if (WindowsInstallerConstants.MsidbCustomActionTypeSourceFile == source) + { + xCustomAction.SetAttributeValue("Error", row.FieldAsString(3)); + } + else + { + xCustomAction.SetAttributeValue("Value", row.FieldAsString(3)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeJScript: + if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) + { + xCustomAction.SetAttributeValue("Script", "jscript"); + // TODO: Extract to @ScriptFile? + // xCustomAction.Content = row.FieldAsString(3); + } + else + { + xCustomAction.SetAttributeValue("JScriptCall", row.FieldAsString(3)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeVBScript: + if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source) + { + xCustomAction.SetAttributeValue("Script", "vbscript"); + // TODO: Extract to @ScriptFile? + // xCustomAction.Content = row.FieldAsString(3); + } + else + { + xCustomAction.SetAttributeValue("VBScriptCall", row.FieldAsString(3)); + } + break; + case WindowsInstallerConstants.MsidbCustomActionTypeInstall: + this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + continue; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + var extype = 4 < row.Fields.Length && !row.IsColumnNull(4) ? row.FieldAsInteger(4) : 0; + if (WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall == (extype & WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall)) + { + xCustomAction.SetAttributeValue("PatchUninstall", "yes"); + } + + this.RootElement.Add(xCustomAction); + this.IndexElement(row, xCustomAction); + } + } + + /// + /// Decompile the CompLocator table. + /// + /// The table to decompile. + private void DecompileCompLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xComponentSearch = new XElement(Names.ComponentSearchElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Guid", row.FieldAsString(1))); + + if (!row.IsColumnNull(2)) + { + switch (row.FieldAsInteger(2)) + { + case WindowsInstallerConstants.MsidbLocatorTypeDirectory: + xComponentSearch.SetAttributeValue("Type", "directory"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeFileName: + // this is the default value + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + break; + } + } + + this.IndexElement(row, xComponentSearch); + } + } + + /// + /// Decompile the Complus table. + /// + /// The table to decompile. + private void DecompileComplusTable(Table table) + { + foreach (var row in table.Rows) + { + if (!row.IsColumnNull(1)) + { + if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0))) + { + xComponent.SetAttributeValue("ComPlusFlags", row.FieldAsInteger(1)); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component")); + } + } + } + } + + /// + /// Decompile the Component table. + /// + /// The table to decompile. + private void DecompileComponentTable(Table table) + { + foreach (var row in table.Rows) + { + var xComponent = new XElement(Names.ComponentElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Guid", row.FieldAsString(1) ?? String.Empty)); + + var attributes = row.FieldAsInteger(3); + + if (WindowsInstallerConstants.MsidbComponentAttributesSourceOnly == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSourceOnly)) + { + xComponent.SetAttributeValue("Location", "source"); + } + else if (WindowsInstallerConstants.MsidbComponentAttributesOptional == (attributes & WindowsInstallerConstants.MsidbComponentAttributesOptional)) + { + xComponent.SetAttributeValue("Location", "either"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount)) + { + xComponent.SetAttributeValue("SharedDllRefCount", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesPermanent == (attributes & WindowsInstallerConstants.MsidbComponentAttributesPermanent)) + { + xComponent.SetAttributeValue("Permanent", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesTransitive == (attributes & WindowsInstallerConstants.MsidbComponentAttributesTransitive)) + { + xComponent.SetAttributeValue("Transitive", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite == (attributes & WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite)) + { + xComponent.SetAttributeValue("NeverOverwrite", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributes64bit == (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit)) + { + xComponent.SetAttributeValue("Bitness", "always64"); + } + else + { + xComponent.SetAttributeValue("Bitness", "always32"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection == (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection)) + { + xComponent.SetAttributeValue("DisableRegistryReflection", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence == (attributes & WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence)) + { + xComponent.SetAttributeValue("UninstallWhenSuperseded", "yes"); + } + + if (WindowsInstallerConstants.MsidbComponentAttributesShared == (attributes & WindowsInstallerConstants.MsidbComponentAttributesShared)) + { + xComponent.SetAttributeValue("Shared", "yes"); + } + + if (!row.IsColumnNull(4)) + { + xComponent.SetAttributeValue("Condition", row.FieldAsString(4)); + } + + this.AddChildToParent("Directory", xComponent, row, 2); + this.IndexElement(row, xComponent); + } + } + + /// + /// Decompile the Condition table. + /// + /// The table to decompile. + private void DecompileConditionTable(Table table) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("Feature", out var xFeature, row.FieldAsString(0))) + { + var xLevel = new XElement(Names.LevelElement, + row.IsColumnNull(2) ? null : new XAttribute("Condition", row.FieldAsString(2)), + new XAttribute("Level", row.FieldAsInteger(1))); + + xFeature.Add(xLevel); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", row.FieldAsString(0), "Feature")); + } + } + } + + /// + /// Decompile the Dialog table. + /// + /// The table to decompile. + private void DecompileDialogTable(Table table) + { + foreach (var row in table.Rows) + { + var attributes = row.FieldAsNullableInteger(5) ?? 0; + + var xDialog = new XElement(Names.DialogElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("X", row.FieldAsString(1)), + new XAttribute("Y", row.FieldAsString(2)), + new XAttribute("Width", row.FieldAsString(3)), + new XAttribute("Height", row.FieldAsString(4)), + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesVisible) ? new XAttribute("Hidden", "yes") : null, + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesModal) ? new XAttribute("Modeless", "yes") : null, + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, + 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesSysModal == (attributes & WindowsInstallerConstants.MsidbDialogAttributesSysModal) ? new XAttribute("SystemModal", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesKeepModeless == (attributes & WindowsInstallerConstants.MsidbDialogAttributesKeepModeless) ? new XAttribute("KeepModeless", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace == (attributes & WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace) ? new XAttribute("TrackDiskSpace", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette == (attributes & WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette) ? new XAttribute("CustomPalette", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbDialogAttributesLeftScroll) ? new XAttribute("LeftScroll", "yes") : null, + WindowsInstallerConstants.MsidbDialogAttributesError == (attributes & WindowsInstallerConstants.MsidbDialogAttributesError) ? new XAttribute("ErrorDialog", "yes") : null, + !row.IsColumnNull(6) ? new XAttribute("Title", row.FieldAsString(6)) : null); + + this.UIElement.Add(xDialog); + this.IndexElement(row, xDialog); + } + } + + /// + /// Decompile the Directory table. + /// + /// The table to decompile. + private void DecompileDirectoryTable(Table table) + { + foreach (var row in table.Rows) + { + var id = row.FieldAsString(0); + var elementName = WindowsInstallerStandard.IsStandardDirectory(id) ? Names.StandardDirectoryElement : Names.DirectoryElement; + var xDirectory = new XElement(elementName, + new XAttribute("Id", id)); + + if (!WindowsInstallerStandard.IsStandardDirectory(id)) + { + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); + + if (id == "TARGETDIR" && names[0] != "SourceDir") + { + this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir()); + xDirectory.SetAttributeValue("Name", "SourceDir"); + } + else + { + if (null != names[0] && "." != names[0]) + { + if (null != names[1]) + { + xDirectory.SetAttributeValue("ShortName", names[0]); + } + else + { + xDirectory.SetAttributeValue("Name", names[0]); + } + } + + if (null != names[1]) + { + xDirectory.SetAttributeValue("Name", names[1]); + } + } + + if (null != names[2]) + { + if (null != names[3]) + { + xDirectory.SetAttributeValue("ShortSourceName", names[2]); + } + else + { + xDirectory.SetAttributeValue("SourceName", names[2]); + } + } + + if (null != names[3]) + { + xDirectory.SetAttributeValue("SourceName", names[3]); + } + } + + this.IndexElement(row, xDirectory); + } + + // nest the directories + foreach (var row in table.Rows) + { + var xDirectory = this.GetIndexedElement(row); + + var id = row.FieldAsString(0); + + if (id == "TARGETDIR") + { + // Skip TARGETDIR (but see below!). + } + else if (row.IsColumnNull(1) || WindowsInstallerStandard.IsStandardDirectory(id)) + { + this.RootElement.Add(xDirectory); + } + else + { + var parentDirectoryId = row.FieldAsString(1); + + if (!this.TryGetIndexedElement("Directory", out var xParentDirectory, parentDirectoryId)) + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", row.FieldAsString(1), "Directory")); + } + else if (xParentDirectory == xDirectory) // another way to specify a root directory + { + this.RootElement.Add(xDirectory); + } + else + { + // TARGETDIR is omitted but if this directory is a first-generation descendant, add it as a root. + if (parentDirectoryId == "TARGETDIR") + { + this.RootElement.Add(xDirectory); + } + else + { + xParentDirectory.Add(xDirectory); + } + } + } + } + } + + /// + /// Decompile the DrLocator table. + /// + /// The table to decompile. + private void DecompileDrLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xDirectorySearch = new XElement(Names.DirectorySearchElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Path", row, 2), + XAttributeIfNotNull("Depth", row, 3)); + + this.IndexElement(row, xDirectorySearch); + } + } + + /// + /// Decompile the DuplicateFile table. + /// + /// The table to decompile. + private void DecompileDuplicateFileTable(Table table) + { + foreach (var row in table.Rows) + { + var xCopyFile = new XElement(Names.CopyFileElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("FileId", row.FieldAsString(2))); + + if (!row.IsColumnNull(3)) + { + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3)); + if (null != names[0] && null != names[1]) + { + xCopyFile.SetAttributeValue("DestinationShortName", names[0]); + xCopyFile.SetAttributeValue("DestinationName", names[1]); + } + else if (null != names[0]) + { + xCopyFile.SetAttributeValue("DestinationName", names[0]); + } + } + + // destination directory/property is set in FinalizeDuplicateMoveFileTables + + this.AddChildToParent("Component", xCopyFile, row, 1); + this.IndexElement(row, xCopyFile); + } + } + + /// + /// Decompile the Environment table. + /// + /// The table to decompile. + private void DecompileEnvironmentTable(Table table) + { + foreach (var row in table.Rows) + { + var xEnvironment = new XElement(Names.EnvironmentElement, + new XAttribute("Id", row.FieldAsString(0))); + + var done = false; + var permanent = true; + var name = row.FieldAsString(1); + for (var i = 0; i < name.Length && !done; i++) + { + switch (name[i]) + { + case '=': + xEnvironment.SetAttributeValue("Action", "set"); + break; + case '+': + xEnvironment.SetAttributeValue("Action", "create"); + break; + case '-': + permanent = false; + break; + case '!': + xEnvironment.SetAttributeValue("Action", "remove"); + break; + case '*': + xEnvironment.SetAttributeValue("System", "yes"); + break; + default: + xEnvironment.SetAttributeValue("Name", name.Substring(i)); + done = true; + break; + } + } + + if (permanent) + { + xEnvironment.SetAttributeValue("Permanent", "yes"); + } + + if (!row.IsColumnNull(2)) + { + var value = row.FieldAsString(2); + + if (value.StartsWith("[~]", StringComparison.Ordinal)) + { + xEnvironment.SetAttributeValue("Part", "last"); + + if (3 < value.Length) + { + xEnvironment.SetAttributeValue("Separator", value.Substring(3, 1)); + xEnvironment.SetAttributeValue("Value", value.Substring(4)); + } + } + else if (value.EndsWith("[~]", StringComparison.Ordinal)) + { + xEnvironment.SetAttributeValue("Part", "first"); + + if (3 < value.Length) + { + xEnvironment.SetAttributeValue("Separator", value.Substring(value.Length - 4, 1)); + xEnvironment.SetAttributeValue("Value", value.Substring(0, value.Length - 4)); + } + } + else + { + xEnvironment.SetAttributeValue("Value", value); + } + } + + this.AddChildToParent("Component", xEnvironment, row, 3); + } + } + + /// + /// Decompile the Error table. + /// + /// The table to decompile. + private void DecompileErrorTable(Table table) + { + foreach (var row in table.Rows) + { + var xError = new XElement(Names.ErrorElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Message", row.FieldAsString(1))); + + this.UIElement.Add(xError); + } + } + + /// + /// Decompile the EventMapping table. + /// + /// The table to decompile. + private void DecompileEventMappingTable(Table table) + { + foreach (var row in table.Rows) + { + var xSubscribe = new XElement(Names.SubscribeElement, + new XAttribute("Event", row.FieldAsString(2)), + new XAttribute("Attribute", row.FieldAsString(3))); + + if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1))) + { + xControl.Add(xSubscribe); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control")); + } + } + } + + /// + /// Decompile the Extension table. + /// + /// The table to decompile. + private void DecompileExtensionTable(Table table) + { + foreach (var row in table.Rows) + { + var xExtension = new XElement(Names.ExtensionElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Advertise", "yes")); + + if (!row.IsColumnNull(3)) + { + if (this.TryGetIndexedElement("MIME", out var xMime, row.FieldAsString(3))) + { + xMime.SetAttributeValue("Default", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME")); + } + } + + if (!row.IsColumnNull(2)) + { + this.AddChildToParent("ProgId", xExtension, row, 2); + } + else + { + this.AddChildToParent("Component", xExtension, row, 1); + } + + this.IndexElement(row, xExtension); + } + } + + /// + /// Decompile the ExternalFiles table. + /// + /// The table to decompile. + private void DecompileExternalFilesTable(Table table) + { + foreach (var row in table.Rows) + { + var xExternalFile = new XElement(Names.ExternalFileElement, + new XAttribute("File", row.FieldAsString(1)), + new XAttribute("Source", row.FieldAsString(2))); + + AddSymbolPaths(row, 3, xExternalFile); + + if (!row.IsColumnNull(4) && !row.IsColumnNull(5)) + { + var ignoreOffsets = row.FieldAsString(4).Split(','); + var ignoreLengths = row.FieldAsString(5).Split(','); + + if (ignoreOffsets.Length == ignoreLengths.Length) + { + for (var i = 0; i < ignoreOffsets.Length; i++) + { + var xIgnoreRange = new XElement(Names.IgnoreRangeElement); + + if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) + { + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); + } + else + { + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); + } + + if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) + { + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); + } + else + { + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); + } + + xExternalFile.Add(xIgnoreRange); + } + } + else + { + // TODO: warn + } + } + else if (!row.IsColumnNull(4) || !row.IsColumnNull(5)) + { + // TODO: warn about mismatch between columns + } + + // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable + + if (!row.IsColumnNull(7)) + { + xExternalFile.SetAttributeValue("Order", row.FieldAsInteger(7)); + } + + this.AddChildToParent("ImageFamilies", xExternalFile, row, 0); + this.IndexElement(row, xExternalFile); + } + } + + /// + /// Decompile the Feature table. + /// + /// The table to decompile. + private void DecompileFeatureTable(Table table) + { + var sortedFeatures = new SortedList(); + + foreach (var row in table.Rows) + { + var feature = new XElement(Names.FeatureElement, + new XAttribute("Id", row.FieldAsString(0)), + row.IsColumnNull(2) ? null : new XAttribute("Title", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Description", row.FieldAsString(3)), + new XAttribute("Level", row.FieldAsInteger(5)), + row.IsColumnNull(6) ? null : new XAttribute("ConfigurableDirectory", row.FieldAsString(6))); + + if (row.IsColumnNull(4)) + { + feature.SetAttributeValue("Display", "hidden"); + } + else + { + var display = row.FieldAsInteger(4); + + if (0 == display) + { + feature.SetAttributeValue("Display", "hidden"); + } + else if (1 == display % 2) + { + feature.SetAttributeValue("Display", "expand"); + } + } + + var attributes = row.FieldAsInteger(7); + + if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource) && WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) + { + // TODO: display a warning for setting favor local and follow parent together + } + else if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource)) + { + feature.SetAttributeValue("InstallDefault", "source"); + } + else if (WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent)) + { + feature.SetAttributeValue("InstallDefault", "followParent"); + } + + if (WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise)) + { + feature.SetAttributeValue("InstallDefault", "advertise"); + } + + if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise) && + WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) + { + this.Messaging.Write(WarningMessages.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no")); + feature.SetAttributeValue("AllowAdvertise", "no"); + } + else if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise)) + { + feature.SetAttributeValue("AllowAdvertise", "no"); + } + else if (WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise)) + { + feature.SetAttributeValue("AllowAdvertise", "system"); + } + + if (WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent)) + { + feature.SetAttributeValue("Absent", "disallow"); + } + + this.IndexElement(row, feature); + + // sort the features by their display column (and append the identifier to ensure unique keys) + sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", row.FieldAsInteger(4), row[0]), row); + } + + // nest the features + foreach (var row in sortedFeatures.Values) + { + var xFeature = this.GetIndexedElement("Feature", row.FieldAsString(0)); + + if (row.IsColumnNull(1)) + { + this.RootElement.Add(xFeature); + } + else + { + if (this.TryGetIndexedElement("Feature", out var xParentFeature, row.FieldAsString(1))) + { + if (xParentFeature == xFeature) + { + // TODO: display a warning about self-nesting + } + else + { + xParentFeature.Add(xFeature); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", row.FieldAsString(1), "Feature")); + } + } + } + } + + /// + /// Decompile the FeatureComponents table. + /// + /// The table to decompile. + private void DecompileFeatureComponentsTable(Table table) + { + foreach (var row in table.Rows) + { + var xComponentRef = new XElement(Names.ComponentRefElement, + new XAttribute("Id", row.FieldAsString(1))); + + this.AddChildToParent("Feature", xComponentRef, row, 0); + this.IndexElement(row, xComponentRef); + } + } + + /// + /// Decompile the File table. + /// + /// The table to decompile. + private void DecompileFileTable(Table table) + { + foreach (FileRow fileRow in table.Rows) + { + var xFile = new XElement(Names.FileElement, + new XAttribute("Id", fileRow.File), + WindowsInstallerConstants.MsidbFileAttributesReadOnly == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly) ? new XAttribute("ReadOnly", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesHidden == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesHidden) ? new XAttribute("Hidden", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesSystem == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesSystem) ? new XAttribute("System", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesChecksum == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum) ? new XAttribute("Checksum", "yes") : null, + WindowsInstallerConstants.MsidbFileAttributesVital != (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesVital) ? new XAttribute("Vital", "no") : null, + null != fileRow.Version && 0 < fileRow.Version.Length && !Char.IsDigit(fileRow.Version[0]) ? new XAttribute("CompanionFile", fileRow.Version) : null); + + var names = this.BackendHelper.SplitMsiFileName(fileRow.FileName); + if (null != names[0] && null != names[1]) + { + xFile.SetAttributeValue("ShortName", names[0]); + xFile.SetAttributeValue("Name", names[1]); + } + else if (null != names[0]) + { + xFile.SetAttributeValue("Name", names[0]); + } + + if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) && + WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) + { + // TODO: error + } + else if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed)) + { + xFile.SetAttributeValue("Compressed", "no"); + } + else if (WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed)) + { + xFile.SetAttributeValue("Compressed", "yes"); + } + + this.IndexElement(fileRow, xFile); + } + } + + /// + /// Decompile the FileSFPCatalog table. + /// + /// The table to decompile. + private void DecompileFileSFPCatalogTable(Table table) + { + foreach (var row in table.Rows) + { + var xSfpFile = new XElement(Names.SFPFileElement, + new XAttribute("Id", row.FieldAsString(0))); + + this.AddChildToParent("SFPCatalog", xSfpFile, row, 1); + } + } + + /// + /// Decompile the Font table. + /// + /// The table to decompile. + private void DecompileFontTable(Table table) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) + { + if (!row.IsColumnNull(1)) + { + xFile.SetAttributeValue("FontTitle", row.FieldAsString(1)); + } + else + { + xFile.SetAttributeValue("TrueType", "yes"); + } + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); + } + } + } + + /// + /// Decompile the Icon table. + /// + /// The table to decompile. + private void DecompileIconTable(Table table) + { + foreach (var row in table.Rows) + { + var icon = new XElement(Names.IconElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); + + this.RootElement.Add(icon); + } + } + + /// + /// Decompile the ImageFamilies table. + /// + /// The table to decompile. + private void DecompileImageFamiliesTable(Table table) + { + foreach (var row in table.Rows) + { + var family = new XElement(Names.FamilyElement, + new XAttribute("Name", row.FieldAsString(0)), + row.IsColumnNull(1) ? null : new XAttribute("MediaSrcProp", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("DiskId", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("SequenceStart", row.FieldAsString(3)), + row.IsColumnNull(4) ? null : new XAttribute("DiskPrompt", row.FieldAsString(4)), + row.IsColumnNull(5) ? null : new XAttribute("VolumeLabel", row.FieldAsString(5))); + + this.RootElement.Add(family); + this.IndexElement(row, family); + } + } + + /// + /// Decompile the IniFile table. + /// + /// The table to decompile. + private void DecompileIniFileTable(Table table) + { + foreach (var row in table.Rows) + { + var xIniFile = new XElement(Names.IniFileElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Section", row.FieldAsString(3)), + new XAttribute("Key", row.FieldAsString(4)), + new XAttribute("Value", row.FieldAsString(5)), + row.IsColumnNull(2) ? null : new XAttribute("Directory", row.FieldAsString(2))); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); + + if (null != names[0]) + { + if (null == names[1]) + { + xIniFile.SetAttributeValue("Name", names[0]); + } + else + { + xIniFile.SetAttributeValue("ShortName", names[0]); + } + } + + if (null != names[1]) + { + xIniFile.SetAttributeValue("Name", names[1]); + } + + switch (row.FieldAsInteger(6)) + { + case WindowsInstallerConstants.MsidbIniFileActionAddLine: + xIniFile.SetAttributeValue("Action", "addLine"); + break; + case WindowsInstallerConstants.MsidbIniFileActionCreateLine: + xIniFile.SetAttributeValue("Action", "createLine"); + break; + case WindowsInstallerConstants.MsidbIniFileActionAddTag: + xIniFile.SetAttributeValue("Action", "addTag"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; + } + + this.AddChildToParent("Component", xIniFile, row, 7); + } + } + + /// + /// Decompile the IniLocator table. + /// + /// The table to decompile. + private void DecompileIniLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xIniFileSearch = new XElement(Names.IniFileSearchElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Section", row.FieldAsString(2)), + new XAttribute("Key", row.FieldAsString(3)), + row.IsColumnNull(4) || row.FieldAsInteger(4) == 0 ? null : new XAttribute("Field", row.FieldAsInteger(4))); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); + if (null != names[0] && null != names[1]) + { + xIniFileSearch.SetAttributeValue("ShortName", names[0]); + xIniFileSearch.SetAttributeValue("Name", names[1]); + } + else if (null != names[0]) + { + xIniFileSearch.SetAttributeValue("Name", names[0]); + } + + if (!row.IsColumnNull(5)) + { + switch (row.FieldAsInteger(5)) + { + case WindowsInstallerConstants.MsidbLocatorTypeDirectory: + xIniFileSearch.SetAttributeValue("Type", "directory"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeFileName: + // this is the default value + break; + case WindowsInstallerConstants.MsidbLocatorTypeRawValue: + xIniFileSearch.SetAttributeValue("Type", "raw"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); + break; + } + } + + this.IndexElement(row, xIniFileSearch); + } + } + + /// + /// Decompile the IsolatedComponent table. + /// + /// The table to decompile. + private void DecompileIsolatedComponentTable(Table table) + { + foreach (var row in table.Rows) + { + var xIsolateComponent = new XElement(Names.IsolateComponentElement, + new XAttribute("Shared", row.FieldAsString(0))); + + this.AddChildToParent("Component", xIsolateComponent, row, 1); + } + } + + /// + /// Decompile the LaunchCondition table. + /// + /// The table to decompile. + private void DecompileLaunchConditionTable(Table table) + { + foreach (var row in table.Rows) + { + if (WixUpgradeConstants.DowngradePreventedCondition == row.FieldAsString(0) || WixUpgradeConstants.UpgradePreventedCondition == row.FieldAsString(0)) + { + continue; // MajorUpgrade rows processed in FinalizeUpgradeTable + } + + var condition = new XElement(Names.LaunchElement, + new XAttribute("Condition", row.FieldAsString(0)), + new XAttribute("Message", row.FieldAsString(1))); + + this.RootElement.Add(condition); + } + } + + /// + /// Decompile the ListBox table. + /// + /// The table to decompile. + private void DecompileListBoxTable(Table table) + { + // sort the list boxes by their property and order + var listBoxRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); + + XElement xListBox = null; + foreach (Row row in listBoxRows) + { + if (null == xListBox || row.FieldAsString(0) != xListBox.Attribute("Property")?.Value) + { + xListBox = new XElement(Names.ListBoxElement, + new XAttribute("Property", row.FieldAsString(0))); + + this.UIElement.Add(xListBox); + } + + var listItem = new XElement(Names.ListItemElement, + new XAttribute("Value", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3))); + + xListBox.Add(listItem); + } + } + + /// + /// Decompile the ListView table. + /// + /// The table to decompile. + private void DecompileListViewTable(Table table) + { + // sort the list views by their property and order + var listViewRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList(); + + XElement xListView = null; + foreach (var row in listViewRows) + { + if (null == xListView || row.FieldAsString(0) != xListView.Attribute("Property")?.Value) + { + xListView = new XElement(Names.ListViewElement, + new XAttribute("Property", row.FieldAsString(0))); + + this.UIElement.Add(xListView); + } + + var listItem = new XElement(Names.ListItemElement, + new XAttribute("Value", row.FieldAsString(2)), + row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)), + row.IsColumnNull(4) ? null : new XAttribute("Icon", row.FieldAsString(4))); + + xListView.Add(listItem); + } + } + + /// + /// Decompile the LockPermissions table. + /// + /// The table to decompile. + private void DecompileLockPermissionsTable(Table table) + { + foreach (var row in table.Rows) + { + var xPermission = new XElement(Names.PermissionElement, + row.IsColumnNull(2) ? null : new XAttribute("Domain", row.FieldAsString(2)), + new XAttribute("User", row.FieldAsString(3))); + + string[] specialPermissions; + + switch (row.FieldAsString(1)) + { + case "CreateFolder": + specialPermissions = LockPermissionConstants.FolderPermissions; + break; + case "File": + specialPermissions = LockPermissionConstants.FilePermissions; + break; + case "Registry": + specialPermissions = LockPermissionConstants.RegistryPermissions; + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); + return; + } + + var permissionBits = row.FieldAsInteger(4); + for (var i = 0; i < 32; i++) + { + if (0 != ((permissionBits >> i) & 1)) + { + string name = null; + + if (specialPermissions.Length > i) + { + name = specialPermissions[i]; + } + else if (16 > i && specialPermissions.Length <= i) + { + name = "SpecificRightsAll"; + } + else if (28 > i && LockPermissionConstants.StandardPermissions.Length > (i - 16)) + { + name = LockPermissionConstants.StandardPermissions[i - 16]; + } + else if (0 <= (i - 28) && LockPermissionConstants.GenericPermissions.Length > (i - 28)) + { + name = LockPermissionConstants.GenericPermissions[i - 28]; + } + + if (null == name) + { + this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i)); + } + else + { + switch (name) + { + case "Append": + case "ChangePermission": + case "CreateChild": + case "CreateFile": + case "CreateLink": + case "CreateSubkeys": + case "Delete": + case "DeleteChild": + case "EnumerateSubkeys": + case "Execute": + case "FileAllRights": + case "GenericAll": + case "GenericExecute": + case "GenericRead": + case "GenericWrite": + case "Notify": + case "Read": + case "ReadAttributes": + case "ReadExtendedAttributes": + case "ReadPermission": + case "SpecificRightsAll": + case "Synchronize": + case "TakeOwnership": + case "Traverse": + case "Write": + case "WriteAttributes": + case "WriteExtendedAttributes": + xPermission.SetAttributeValue(name, "yes"); + break; + default: + throw new InvalidOperationException($"Unknown permission attribute '{name}'."); + } + } + } + } + + this.IndexElement(row, xPermission); + } + } + + /// + /// Decompile the Media table. + /// + /// The table to decompile. + private void DecompileMediaTable(Table table) + { + foreach (MediaRow mediaRow in table.Rows) + { + var xMedia = new XElement(Names.MediaElement, + new XAttribute("Id", mediaRow.DiskId), + mediaRow.DiskPrompt == null ? null : new XAttribute("DiskPrompt", mediaRow.DiskPrompt), + mediaRow.VolumeLabel == null ? null : new XAttribute("VolumeLabel", mediaRow.VolumeLabel)); + + if (null != mediaRow.Cabinet) + { + var cabinet = mediaRow.Cabinet; + + if (cabinet.StartsWith("#", StringComparison.Ordinal)) + { + xMedia.SetAttributeValue("EmbedCab", "yes"); + cabinet = cabinet.Substring(1); + } + + xMedia.SetAttributeValue("Cabinet", cabinet); + } + + this.RootElement.Add(xMedia); + this.IndexElement(mediaRow, xMedia); + } + } + + /// + /// Decompile the MIME table. + /// + /// The table to decompile. + private void DecompileMIMETable(Table table) + { + foreach (var row in table.Rows) + { + var mime = new XElement(Names.MIMEElement, + new XAttribute("ContentType", row.FieldAsString(0)), + row.IsColumnNull(2) ? null : new XAttribute("Class", row.FieldAsString(2))); + + this.IndexElement(row, mime); + } + } + + /// + /// Decompile the ModuleConfiguration table. + /// + /// The table to decompile. + private void DecompileModuleConfigurationTable(Table table) + { + foreach (var row in table.Rows) + { + var configuration = new XElement(Names.ConfigurationElement, + new XAttribute("Name", row.FieldAsString(0)), + XAttributeIfNotNull("Type", row, 2), + XAttributeIfNotNull("ContextData", row, 3), + XAttributeIfNotNull("DefaultValue", row, 4), + XAttributeIfNotNull("DisplayName", row, 6), + XAttributeIfNotNull("Description", row, 7), + XAttributeIfNotNull("HelpLocation", row, 8), + XAttributeIfNotNull("HelpKeyword", row, 9)); + + switch (row.FieldAsInteger(1)) + { + case 0: + configuration.SetAttributeValue("Format", "Text"); + break; + case 1: + configuration.SetAttributeValue("Format", "Key"); + break; + case 2: + configuration.SetAttributeValue("Format", "Integer"); + break; + case 3: + configuration.SetAttributeValue("Format", "Bitfield"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + if (!row.IsColumnNull(5)) + { + var attributes = row.FieldAsInteger(5); + + if (WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan)) + { + configuration.SetAttributeValue("KeyNoOrphan", "yes"); + } + + if (WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable)) + { + configuration.SetAttributeValue("NonNullable", "yes"); + } + + if (3 < attributes) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5])); + } + } + + this.RootElement.Add(configuration); + } + } + + /// + /// Decompile the ModuleDependency table. + /// + /// The table to decompile. + private void DecompileModuleDependencyTable(Table table) + { + foreach (var row in table.Rows) + { + var xDependency = new XElement(Names.DependencyElement, + new XAttribute("RequiredId", row.FieldAsString(2)), + new XAttribute("RequiredLanguage", row.FieldAsString(3)), + XAttributeIfNotNull("RequiredVersion", row, 4)); + + this.RootElement.Add(xDependency); + } + } + + /// + /// Decompile the ModuleExclusion table. + /// + /// The table to decompile. + private void DecompileModuleExclusionTable(Table table) + { + foreach (var row in table.Rows) + { + var xExclusion = new XElement(Names.ExclusionElement, + new XAttribute("ExcludedId", row.FieldAsString(2)), + XAttributeIfNotNull("ExcludedMinVersion", row, 4), + XAttributeIfNotNull("ExcludedMaxVersion", row, 5)); + + var excludedLanguage = row.FieldAsInteger(3); + if (0 < excludedLanguage) + { + xExclusion.SetAttributeValue("ExcludeLanguage", excludedLanguage); + } + else if (0 > excludedLanguage) + { + xExclusion.SetAttributeValue("ExcludeExceptLanguage", -excludedLanguage); + } + + this.RootElement.Add(xExclusion); + } + } + + /// + /// Decompile the ModuleIgnoreTable table. + /// + /// The table to decompile. + private void DecompileModuleIgnoreTableTable(Table table) + { + foreach (var row in table.Rows) + { + var tableName = row.FieldAsString(0); + + // the linker automatically adds a ModuleIgnoreTable row for some tables + if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName) + { + var xIgnoreTable = new XElement(Names.IgnoreTableElement, + new XAttribute("Id", tableName)); + + this.RootElement.Add(xIgnoreTable); + } + } + } + + /// + /// Decompile the ModuleSignature table. + /// + /// The table to decompile. + private void DecompileModuleSignatureTable(Table table) + { + if (1 == table.Rows.Count) + { + var row = table.Rows[0]; + + this.RootElement.SetAttributeValue("Id", row.FieldAsString(0)); + // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability) + this.RootElement.SetAttributeValue("Language", row.FieldAsString(1)); + this.RootElement.SetAttributeValue("Version", row.FieldAsString(2)); + } + else + { + // TODO: warn + } + } + + /// + /// Decompile the ModuleSubstitution table. + /// + /// The table to decompile. + private void DecompileModuleSubstitutionTable(Table table) + { + foreach (var row in table.Rows) + { + var xSubstitution = new XElement(Names.SubstitutionElement, + new XAttribute("Table", row.FieldAsString(0)), + new XAttribute("Row", row.FieldAsString(1)), + new XAttribute("Column", row.FieldAsString(2)), + XAttributeIfNotNull("Value", row, 3)); + + this.RootElement.Add(xSubstitution); + } + } + + /// + /// Decompile the MoveFile table. + /// + /// The table to decompile. + private void DecompileMoveFileTable(Table table) + { + foreach (var row in table.Rows) + { + var xCopyFile = new XElement(Names.CopyFileElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("SourceName", row, 2)); + + if (!row.IsColumnNull(3)) + { + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3)); + if (null != names[0] && null != names[1]) + { + xCopyFile.SetAttributeValue("DestinationShortName", names[0]); + xCopyFile.SetAttributeValue("DestinationName", names[1]); + } + else if (null != names[0]) + { + xCopyFile.SetAttributeValue("DestinationName", names[0]); + } + } + + // source/destination directory/property is set in FinalizeDuplicateMoveFileTables + + switch (row.FieldAsInteger(6)) + { + case 0: + break; + case WindowsInstallerConstants.MsidbMoveFileOptionsMove: + xCopyFile.SetAttributeValue("Delete", "yes"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; + } + + this.AddChildToParent("Component", xCopyFile, row, 1); + this.IndexElement(row, xCopyFile); + } + } + + /// + /// Decompile the MsiDigitalCertificate table. + /// + /// The table to decompile. + private void DecompileMsiDigitalCertificateTable(Table table) + { + foreach (var row in table.Rows) + { + var xDigitalCertificate = new XElement(Names.DigitalCertificateElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); + + this.IndexElement(row, xDigitalCertificate); + } + } + + /// + /// Decompile the MsiDigitalSignature table. + /// + /// The table to decompile. + private void DecompileMsiDigitalSignatureTable(Table table) + { + foreach (var row in table.Rows) + { + var xDigitalSignature = new XElement(Names.DigitalSignatureElement, + XAttributeIfNotNull("SourceFile", row, 3)); + + this.AddChildToParent("MsiDigitalCertificate", xDigitalSignature, row, 2); + + if (this.TryGetIndexedElement(row.FieldAsString(0), out var xParentElement, row.FieldAsString(1))) + { + xParentElement.Add(xDigitalSignature); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", row.FieldAsString(1), row.FieldAsString(0))); + } + } + } + + /// + /// Decompile the MsiEmbeddedChainer table. + /// + /// The table to decompile. + private void DecompileMsiEmbeddedChainerTable(Table table) + { + foreach (var row in table.Rows) + { + var xEmbeddedChainer = new XElement(Names.EmbeddedChainerElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Condition", row.FieldAsString(1)), + XAttributeIfNotNull("CommandLine", row, 2)); + + switch (row.FieldAsInteger(4)) + { + case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeBinaryData: + xEmbeddedChainer.SetAttributeValue("BinarySource", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeSourceFile: + xEmbeddedChainer.SetAttributeValue("FileSource", row.FieldAsString(3)); + break; + case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeProperty: + xEmbeddedChainer.SetAttributeValue("PropertySource", row.FieldAsString(3)); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + this.RootElement.Add(xEmbeddedChainer); + } + } + + /// + /// Decompile the MsiEmbeddedUI table. + /// + /// The table to decompile. + private void DecompileMsiEmbeddedUITable(Table table) + { + var xEmbeddedUI = new XElement(Names.EmbeddedUIElement); + + var foundEmbeddedUI = false; + var foundEmbeddedResources = false; + + foreach (var row in table.Rows) + { + var attributes = row.FieldAsInteger(2); + + if (WindowsInstallerConstants.MsidbEmbeddedUI == (attributes & WindowsInstallerConstants.MsidbEmbeddedUI)) + { + if (foundEmbeddedUI) + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2])); + } + else + { + xEmbeddedUI.SetAttributeValue("Id", row.FieldAsString(0)); + xEmbeddedUI.SetAttributeValue("Name", row.FieldAsString(1)); + + var messageFilter = row.FieldAsInteger(3); + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT)) + { + xEmbeddedUI.SetAttributeValue("IgnoreFatalExit", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ERROR)) + { + xEmbeddedUI.SetAttributeValue("IgnoreError", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_WARNING)) + { + xEmbeddedUI.SetAttributeValue("IgnoreWarning", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_USER)) + { + xEmbeddedUI.SetAttributeValue("IgnoreUser", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INFO)) + { + xEmbeddedUI.SetAttributeValue("IgnoreInfo", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreFilesInUse", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreResolveSource", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreOutOfDiskSpace", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART)) + { + xEmbeddedUI.SetAttributeValue("IgnoreActionStart", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA)) + { + xEmbeddedUI.SetAttributeValue("IgnoreActionData", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS)) + { + xEmbeddedUI.SetAttributeValue("IgnoreProgress", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA)) + { + xEmbeddedUI.SetAttributeValue("IgnoreCommonData", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreInitialize", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreTerminate", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG)) + { + xEmbeddedUI.SetAttributeValue("IgnoreShowDialog", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE)) + { + xEmbeddedUI.SetAttributeValue("IgnoreRMFilesInUse", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART)) + { + xEmbeddedUI.SetAttributeValue("IgnoreInstallStart", "yes"); + } + + if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND)) + { + xEmbeddedUI.SetAttributeValue("IgnoreInstallEnd", "yes"); + } + + if (WindowsInstallerConstants.MsidbEmbeddedHandlesBasic == (attributes & WindowsInstallerConstants.MsidbEmbeddedHandlesBasic)) + { + xEmbeddedUI.SetAttributeValue("SupportBasicUI", "yes"); + } + + xEmbeddedUI.SetAttributeValue("SourceFile", row.FieldAsString(4)); + + this.UIElement.Add(xEmbeddedUI); + foundEmbeddedUI = true; + } + } + else + { + var xEmbeddedResource = new XElement(Names.EmbeddedUIResourceElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(1)), + new XAttribute("SourceFile", row.FieldAsString(4))); + + xEmbeddedUI.Add(xEmbeddedResource); + foundEmbeddedResources = true; + } + } + + if (!foundEmbeddedUI && foundEmbeddedResources) + { + // TODO: warn + } + } + + /// + /// Decompile the MsiLockPermissionsEx table. + /// + /// The table to decompile. + private void DecompileMsiLockPermissionsExTable(Table table) + { + foreach (var row in table.Rows) + { + var xPermissionEx = new XElement(Names.PermissionExElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Sddl", row.FieldAsString(3)), + XAttributeIfNotNull("Condition", row, 4)); + + switch (row.FieldAsString(2)) + { + case "CreateFolder": + case "File": + case "Registry": + case "ServiceInstall": + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1])); + return; + } + + this.IndexElement(row, xPermissionEx); + } + } + + /// + /// Decompile the MsiPackageCertificate table. + /// + /// The table to decompile. + private void DecompileMsiPackageCertificateTable(Table table) + { + if (0 < table.Rows.Count) + { + var xPackageCertificates = new XElement(Names.PatchCertificatesElement); + this.RootElement.Add(xPackageCertificates); + this.AddCertificates(table, xPackageCertificates); + } + } + + /// + /// Decompile the MsiPatchCertificate table. + /// + /// The table to decompile. + private void DecompileMsiPatchCertificateTable(Table table) + { + if (0 < table.Rows.Count) + { + var xPatchCertificates = new XElement(Names.PatchCertificatesElement); + this.RootElement.Add(xPatchCertificates); + this.AddCertificates(table, xPatchCertificates); + } + } + + /// + /// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table. + /// + /// The table being decompiled. + /// DigitalCertificate parent + private void AddCertificates(Table table, XElement parent) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("MsiDigitalCertificate", out var xDigitalCertificate, row.FieldAsString(1))) + { + parent.Add(xDigitalCertificate); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", row.FieldAsString(1), "MsiDigitalCertificate")); + } + } + } + + /// + /// Decompile the MsiShortcutProperty table. + /// + /// The table to decompile. + private void DecompileMsiShortcutPropertyTable(Table table) + { + foreach (var row in table.Rows) + { + var xProperty = new XElement(Names.ShortcutPropertyElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + new XAttribute("Value", row.FieldAsString(3))); + + this.AddChildToParent("Shortcut", xProperty, row, 1); + } + } + + /// + /// Decompile the ODBCAttribute table. + /// + /// The table to decompile. + private void DecompileODBCAttributeTable(Table table) + { + foreach (var row in table.Rows) + { + var xProperty = new XElement(Names.PropertyElement, + new XAttribute("Id", row.FieldAsString(1)), + row.IsColumnNull(2) ? null : new XAttribute("Value", row.FieldAsString(2))); + + this.AddChildToParent("ODBCDriver", xProperty, row, 0); + } + } + + /// + /// Decompile the ODBCDataSource table. + /// + /// The table to decompile. + private void DecompileODBCDataSourceTable(Table table) + { + foreach (var row in table.Rows) + { + var xOdbcDataSource = new XElement(Names.ODBCDataSourceElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(2)), + new XAttribute("DriverName", row.FieldAsString(3))); + + switch (row.FieldAsInteger(4)) + { + case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerMachine: + xOdbcDataSource.SetAttributeValue("Registration", "machine"); + break; + case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerUser: + xOdbcDataSource.SetAttributeValue("Registration", "user"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + this.IndexElement(row, xOdbcDataSource); + } + } + + /// + /// Decompile the ODBCDriver table. + /// + /// The table to decompile. + private void DecompileODBCDriverTable(Table table) + { + foreach (var row in table.Rows) + { + var xOdbcDriver = new XElement(Names.ODBCDriverElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(2)), + new XAttribute("File", row.FieldAsString(3)), + XAttributeIfNotNull("SetupFile", row, 4)); + + this.AddChildToParent("Component", xOdbcDriver, row, 1); + this.IndexElement(row, xOdbcDriver); + } + } + + /// + /// Decompile the ODBCSourceAttribute table. + /// + /// The table to decompile. + private void DecompileODBCSourceAttributeTable(Table table) + { + foreach (var row in table.Rows) + { + var xProperty = new XElement(Names.PropertyElement, + new XAttribute("Id", row.FieldAsString(1)), + XAttributeIfNotNull("Value", row, 2)); + + this.AddChildToParent("ODBCDataSource", xProperty, row, 0); + } + } + + /// + /// Decompile the ODBCTranslator table. + /// + /// The table to decompile. + private void DecompileODBCTranslatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xOdbcTranslator = new XElement(Names.ODBCTranslatorElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(2)), + new XAttribute("File", row.FieldAsString(3)), + XAttributeIfNotNull("SetupFile", row, 4)); + + this.AddChildToParent("Component", xOdbcTranslator, row, 1); + } + } + + /// + /// Decompile the PatchMetadata table. + /// + /// The table to decompile. + private void DecompilePatchMetadataTable(Table table) + { + if (0 < table.Rows.Count) + { + var xPatchMetadata = new XElement(Names.PatchMetadataElement); + + foreach (var row in table.Rows) + { + var value = row.FieldAsString(2); + + switch (row.FieldAsString(1)) + { + case "AllowRemoval": + if ("1" == value) + { + xPatchMetadata.SetAttributeValue("AllowRemoval", "yes"); + } + break; + case "Classification": + if (null != value) + { + xPatchMetadata.SetAttributeValue("Classification", value); + } + break; + case "CreationTimeUTC": + if (null != value) + { + xPatchMetadata.SetAttributeValue("CreationTimeUTC", value); + } + break; + case "Description": + if (null != value) + { + xPatchMetadata.SetAttributeValue("Description", value); + } + break; + case "DisplayName": + if (null != value) + { + xPatchMetadata.SetAttributeValue("DisplayName", value); + } + break; + case "ManufacturerName": + if (null != value) + { + xPatchMetadata.SetAttributeValue("ManufacturerName", value); + } + break; + case "MinorUpdateTargetRTM": + if (null != value) + { + xPatchMetadata.SetAttributeValue("MinorUpdateTargetRTM", value); + } + break; + case "MoreInfoURL": + if (null != value) + { + xPatchMetadata.SetAttributeValue("MoreInfoURL", value); + } + break; + case "OptimizeCA": + var xOptimizeCustomActions = new XElement(Names.OptimizeCustomActionsElement); + var optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture); + if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipAssignment) & optimizeCA)) + { + xOptimizeCustomActions.SetAttributeValue("SkipAssignment", "yes"); + } + + if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipImmediate) & optimizeCA)) + { + xOptimizeCustomActions.SetAttributeValue("SkipImmediate", "yes"); + } + + if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipDeferred) & optimizeCA)) + { + xOptimizeCustomActions.SetAttributeValue("SkipDeferred", "yes"); + } + + xPatchMetadata.Add(xOptimizeCustomActions); + break; + case "OptimizedInstallMode": + if ("1" == value) + { + xPatchMetadata.SetAttributeValue("OptimizedInstallMode", "yes"); + } + break; + case "TargetProductName": + if (null != value) + { + xPatchMetadata.SetAttributeValue("TargetProductName", value); + } + break; + default: + var xCustomProperty = new XElement(Names.CustomPropertyElement, + XAttributeIfNotNull("Company", row, 0), + XAttributeIfNotNull("Property", row, 1), + XAttributeIfNotNull("Value", row, 2)); + + xPatchMetadata.Add(xCustomProperty); + break; + } + } + + this.RootElement.Add(xPatchMetadata); + } + } + + /// + /// Decompile the PatchSequence table. + /// + /// The table to decompile. + private void DecompilePatchSequenceTable(Table table) + { + foreach (var row in table.Rows) + { + var patchSequence = new XElement(Names.PatchSequenceElement, + new XAttribute("PatchFamily", row.FieldAsString(0))); + + if (!row.IsColumnNull(1)) + { + try + { + var guid = new Guid(row.FieldAsString(1)); + + patchSequence.SetAttributeValue("ProductCode", row.FieldAsString(1)); + } + catch // non-guid value + { + patchSequence.SetAttributeValue("TargetImage", row.FieldAsString(1)); + } + } + + if (!row.IsColumnNull(2)) + { + patchSequence.SetAttributeValue("Sequence", row.FieldAsString(2)); + } + + if (!row.IsColumnNull(3) && 0x1 == row.FieldAsInteger(3)) + { + patchSequence.SetAttributeValue("Supersede", "yes"); + } + + this.RootElement.Add(patchSequence); + } + } + + /// + /// Decompile the ProgId table. + /// + /// The table to decompile. + private void DecompileProgIdTable(Table table) + { + foreach (var row in table.Rows) + { + var xProgId = new XElement(Names.ProgIdElement, + new XAttribute("Advertise", "yes"), + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Description", row, 3), + XAttributeIfNotNull("Icon", row, 4), + XAttributeIfNotNull("IconIndex", row, 5)); + + this.IndexElement(row, xProgId); + } + + // nest the ProgIds + foreach (var row in table.Rows) + { + var xProgId = this.GetIndexedElement(row); + + if (!row.IsColumnNull(1)) + { + this.AddChildToParent("ProgId", xProgId, row, 1); + } + else if (!row.IsColumnNull(2)) + { + // nesting is handled in FinalizeProgIdTable + } + else + { + // TODO: warn for orphaned ProgId + } + } + } + + /// + /// Decompile the Properties table. + /// + /// The table to decompile. + private void DecompilePropertiesTable(Table table) + { + foreach (var row in table.Rows) + { + var name = row.FieldAsString(0); + var value = row.FieldAsString(1); + + switch (name) + { + case "AllowProductCodeMismatches": + if ("1" == value) + { + this.RootElement.SetAttributeValue("AllowProductCodeMismatches", "yes"); + } + break; + case "AllowProductVersionMajorMismatches": + if ("1" == value) + { + this.RootElement.SetAttributeValue("AllowMajorVersionMismatches", "yes"); + } + break; + case "ApiPatchingSymbolFlags": + if (null != value) + { + try + { + // remove the leading "0x" if its present + if (value.StartsWith("0x", StringComparison.Ordinal)) + { + value = value.Substring(2); + } + + this.RootElement.SetAttributeValue("SymbolFlags", Convert.ToInt32(value, 16)); + } + catch + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + } + } + break; + case "DontRemoveTempFolderWhenFinished": + if ("1" == value) + { + this.RootElement.SetAttributeValue("CleanWorkingFolder", "no"); + } + break; + case "IncludeWholeFilesOnly": + if ("1" == value) + { + this.RootElement.SetAttributeValue("WholeFilesOnly", "yes"); + } + break; + case "ListOfPatchGUIDsToReplace": + if (null != value) + { + var guidRegex = 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}\}"); + var guidMatches = guidRegex.Matches(value); + + foreach (Match guidMatch in guidMatches) + { + var xReplacePatch = new XElement(Names.ReplacePatchElement, + new XAttribute("Id", guidMatch.Value)); + + this.RootElement.Add(xReplacePatch); + } + } + break; + case "ListOfTargetProductCodes": + if (null != value) + { + var targetProductCodes = value.Split(';'); + + foreach (var targetProductCodeString in targetProductCodes) + { + var xTargetProductCode = new XElement(Names.TargetProductCodeElement, + new XAttribute("Id", targetProductCodeString)); + + this.RootElement.Add(xTargetProductCode); + } + } + break; + case "PatchGUID": + this.RootElement.SetAttributeValue("Id", value); + break; + case "PatchSourceList": + this.RootElement.SetAttributeValue("SourceList", value); + break; + case "PatchOutputPath": + this.RootElement.SetAttributeValue("OutputPath", value); + break; + default: + var patchProperty = new XElement(Names.PatchPropertyElement, + new XAttribute("Name", name), + new XAttribute("Value", value)); + + this.RootElement.Add(patchProperty); + break; + } + } + } + + /// + /// Decompile the Property table. + /// + /// The table to decompile. + private void DecompilePropertyTable(Table table) + { + foreach (var row in table.Rows) + { + var id = row.FieldAsString(0); + var value = row.FieldAsString(1); + + if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id) + { + if (0 < value.Length) + { + foreach (var propertyId in value.Split(';')) + { + if (WixUpgradeConstants.DowngradeDetectedProperty == propertyId || WixUpgradeConstants.UpgradeDetectedProperty == propertyId) + { + continue; + } + + var property = propertyId; + var suppressModulularization = false; + if (OutputType.Module == this.OutputType) + { + if (propertyId.EndsWith(this.ModularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal)) + { + property = propertyId.Substring(0, propertyId.Length - this.ModularizationGuid.Length + 1); + } + else + { + suppressModulularization = true; + } + } + + var xSpecialProperty = this.EnsureProperty(property); + if (suppressModulularization) + { + xSpecialProperty.SetAttributeValue("SuppressModularization", "yes"); + } + + switch (id) + { + case "AdminProperties": + xSpecialProperty.SetAttributeValue("Admin", "yes"); + break; + case "MsiHiddenProperties": + xSpecialProperty.SetAttributeValue("Hidden", "yes"); + break; + case "SecureCustomProperties": + xSpecialProperty.SetAttributeValue("Secure", "yes"); + break; + } + } + } + + continue; + } + else if (OutputType.Product == this.OutputType) + { + switch (id) + { + case "Manufacturer": + this.RootElement.SetAttributeValue("Manufacturer", value); + continue; + case "ProductCode": + this.RootElement.SetAttributeValue("ProductCode", value.ToUpper(CultureInfo.InvariantCulture)); + continue; + case "ProductLanguage": + this.RootElement.SetAttributeValue("Language", value); + continue; + case "ProductName": + this.RootElement.SetAttributeValue("Name", value); + continue; + case "ProductVersion": + this.RootElement.SetAttributeValue("Version", value); + continue; + case "UpgradeCode": + this.RootElement.SetAttributeValue("UpgradeCode", value); + continue; + } + } + + if (!this.SuppressUI || "ErrorDialog" != id) + { + var xProperty = this.EnsureProperty(id); + + xProperty.SetAttributeValue("Value", value); + } + } + } + + /// + /// Decompile the PublishComponent table. + /// + /// The table to decompile. + private void DecompilePublishComponentTable(Table table) + { + foreach (var row in table.Rows) + { + var category = new XElement(Names.CategoryElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Qualifier", row.FieldAsString(1)), + XAttributeIfNotNull("AppData", row, 3)); + + this.AddChildToParent("Component", category, row, 2); + } + } + + /// + /// Decompile the RadioButton table. + /// + /// The table to decompile. + private void DecompileRadioButtonTable(Table table) + { + foreach (var row in table.Rows) + { + var radioButton = new XElement(Names.RadioButtonElement, + new XAttribute("Value", row.FieldAsString(2)), + new XAttribute("X", row.FieldAsInteger(3)), + new XAttribute("Y", row.FieldAsInteger(4)), + new XAttribute("Width", row.FieldAsInteger(5)), + new XAttribute("Height", row.FieldAsInteger(6)), + XAttributeIfNotNull("Text", row, 7)); + + if (!row.IsColumnNull(8)) + { + var help = (row.FieldAsString(8)).Split('|'); + + if (2 == help.Length) + { + if (0 < help[0].Length) + { + radioButton.SetAttributeValue("ToolTip", help[0]); + } + + if (0 < help[1].Length) + { + radioButton.SetAttributeValue("Help", help[1]); + } + } + } + + this.IndexElement(row, radioButton); + } + + // nest the radio buttons + var xRadioButtonGroups = new Dictionary(); + foreach (var row in table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1))) + { + var xRadioButton = this.GetIndexedElement(row); + + if (!xRadioButtonGroups.TryGetValue(row.FieldAsString(0), out var xRadioButtonGroup)) + { + xRadioButtonGroup = new XElement(Names.RadioButtonGroupElement, + new XAttribute("Property", row.FieldAsString(0))); + + this.UIElement.Add(xRadioButtonGroup); + xRadioButtonGroups.Add(row.FieldAsString(0), xRadioButtonGroup); + } + + xRadioButtonGroup.Add(xRadioButton); + } + } + + /// + /// Decompile the Registry table. + /// + /// The table to decompile. + private void DecompileRegistryTable(Table table) + { + foreach (var row in table.Rows) + { + if (("-" == row.FieldAsString(3) || "+" == row.FieldAsString(3) || "*" == row.FieldAsString(3)) && row.IsColumnNull(4)) + { + var xRegistryKey = new XElement(Names.RegistryKeyElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2))); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + xRegistryKey.SetAttributeValue("Root", registryRootType); + } + + switch (row.FieldAsString(3)) + { + case "+": + xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); + break; + case "-": + xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); + break; + case "*": + xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes"); + xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes"); + break; + } + + this.IndexElement(row, xRegistryKey); + } + else + { + var xRegistryValue = new XElement(Names.RegistryValueElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + XAttributeIfNotNull("Name", row, 3)); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + xRegistryValue.SetAttributeValue("Root", registryRootType); + } + + if (!row.IsColumnNull(4)) + { + var value = row.FieldAsString(4); + + if (value.StartsWith("#x", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Type", "binary"); + xRegistryValue.SetAttributeValue("Value", value.Substring(2)); + } + else if (value.StartsWith("#%", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Type", "expandable"); + xRegistryValue.SetAttributeValue("Value", value.Substring(2)); + } + else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Type", "integer"); + xRegistryValue.SetAttributeValue("Value", value.Substring(1)); + } + else + { + if (value.StartsWith("##", StringComparison.Ordinal)) + { + value = value.Substring(1); + } + + if (0 <= value.IndexOf("[~]", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Type", "multiString"); + + if ("[~]" == value) + { + value = String.Empty; + } + else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal)) + { + value = value.Substring(3, value.Length - 6); + } + else if (value.StartsWith("[~]", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Action", "append"); + value = value.Substring(3); + } + else if (value.EndsWith("[~]", StringComparison.Ordinal)) + { + xRegistryValue.SetAttributeValue("Action", "prepend"); + value = value.Substring(0, value.Length - 3); + } + + var multiValues = NullSplitter.Split(value); + foreach (var multiValue in multiValues) + { + var xMultiStringValue = new XElement(Names.MultiStringElement, + new XAttribute("Value", multiValue)); + + xRegistryValue.Add(xMultiStringValue); + } + } + else + { + xRegistryValue.SetAttributeValue("Type", "string"); + xRegistryValue.SetAttributeValue("Value", value); + } + } + } + else + { + xRegistryValue.SetAttributeValue("Type", "string"); + xRegistryValue.SetAttributeValue("Value", String.Empty); + } + + this.IndexElement(row, xRegistryValue); + } + } + } + + /// + /// Decompile the RegLocator table. + /// + /// The table to decompile. + private void DecompileRegLocatorTable(Table table) + { + foreach (var row in table.Rows) + { + var xRegistrySearch = new XElement(Names.RegistrySearchElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + XAttributeIfNotNull("Name", row, 3)); + + switch (row.FieldAsInteger(1)) + { + case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: + xRegistrySearch.SetAttributeValue("Root", "HKCR"); + break; + case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: + xRegistrySearch.SetAttributeValue("Root", "HKCU"); + break; + case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: + xRegistrySearch.SetAttributeValue("Root", "HKLM"); + break; + case WindowsInstallerConstants.MsidbRegistryRootUsers: + xRegistrySearch.SetAttributeValue("Root", "HKU"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1])); + break; + } + + if (row.IsColumnNull(4)) + { + xRegistrySearch.SetAttributeValue("Type", "file"); + } + else + { + var type = row.FieldAsInteger(4); + + if (WindowsInstallerConstants.MsidbLocatorType64bit == (type & WindowsInstallerConstants.MsidbLocatorType64bit)) + { + xRegistrySearch.SetAttributeValue("Bitness", "always64"); + type &= ~WindowsInstallerConstants.MsidbLocatorType64bit; + } + else + { + xRegistrySearch.SetAttributeValue("Bitness", "always32"); + } + + switch (type) + { + case WindowsInstallerConstants.MsidbLocatorTypeDirectory: + xRegistrySearch.SetAttributeValue("Type", "directory"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeFileName: + xRegistrySearch.SetAttributeValue("Type", "file"); + break; + case WindowsInstallerConstants.MsidbLocatorTypeRawValue: + xRegistrySearch.SetAttributeValue("Type", "raw"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + } + + this.IndexElement(row, xRegistrySearch); + } + } + + /// + /// Decompile the RemoveFile table. + /// + /// The table to decompile. + private void DecompileRemoveFileTable(Table table) + { + foreach (var row in table.Rows) + { + if (row.IsColumnNull(2)) + { + var xRemoveFolder = new XElement(Names.RemoveFolderElement, + new XAttribute("Id", row.FieldAsString(0))); + + // directory/property is set in FinalizeDecompile + + switch (row.FieldAsInteger(4)) + { + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: + xRemoveFolder.SetAttributeValue("On", "install"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: + xRemoveFolder.SetAttributeValue("On", "uninstall"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: + xRemoveFolder.SetAttributeValue("On", "both"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + this.AddChildToParent("Component", xRemoveFolder, row, 1); + this.IndexElement(row, xRemoveFolder); + } + else + { + var xRemoveFile = new XElement(Names.RemoveFileElement, + new XAttribute("Id", row.FieldAsString(0))); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); + if (null != names[0] && null != names[1]) + { + xRemoveFile.SetAttributeValue("ShortName", names[0]); + xRemoveFile.SetAttributeValue("Name", names[1]); + } + else if (null != names[0]) + { + xRemoveFile.SetAttributeValue("Name", names[0]); + } + + // directory/property is set in FinalizeDecompile + + switch (row.FieldAsInteger(4)) + { + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall: + xRemoveFile.SetAttributeValue("On", "install"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove: + xRemoveFile.SetAttributeValue("On", "uninstall"); + break; + case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth: + xRemoveFile.SetAttributeValue("On", "both"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + break; + } + + this.AddChildToParent("Component", xRemoveFile, row, 1); + this.IndexElement(row, xRemoveFile); + } + } + } + + /// + /// Decompile the RemoveIniFile table. + /// + /// The table to decompile. + private void DecompileRemoveIniFileTable(Table table) + { + foreach (var row in table.Rows) + { + var xIniFile = new XElement(Names.IniFileElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Directory", row, 2), + new XAttribute("Section", row.FieldAsString(3)), + new XAttribute("Key", row.FieldAsString(4)), + XAttributeIfNotNull("Value", row, 5)); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); + if (null != names[0] && null != names[1]) + { + xIniFile.SetAttributeValue("ShortName", names[0]); + xIniFile.SetAttributeValue("Name", names[1]); + } + else if (null != names[0]) + { + xIniFile.SetAttributeValue("Name", names[0]); + } + + switch (row.FieldAsInteger(6)) + { + case WindowsInstallerConstants.MsidbIniFileActionRemoveLine: + xIniFile.SetAttributeValue("Action", "removeLine"); + break; + case WindowsInstallerConstants.MsidbIniFileActionRemoveTag: + xIniFile.SetAttributeValue("Action", "removeTag"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6])); + break; + } + + this.AddChildToParent("Component", xIniFile, row, 7); + } + } + + /// + /// Decompile the RemoveRegistry table. + /// + /// The table to decompile. + private void DecompileRemoveRegistryTable(Table table) + { + foreach (var row in table.Rows) + { + if ("-" == row.FieldAsString(3)) + { + var xRemoveRegistryKey = new XElement(Names.RemoveRegistryKeyElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + new XAttribute("Action", "removeOnInstall")); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + xRemoveRegistryKey.SetAttributeValue("Root", registryRootType); + } + + this.AddChildToParent("Component", xRemoveRegistryKey, row, 4); + } + else + { + var xRemoveRegistryValue = new XElement(Names.RemoveRegistryValueElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Key", row.FieldAsString(2)), + XAttributeIfNotNull("Name", row, 3)); + + if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType)) + { + xRemoveRegistryValue.SetAttributeValue("Root", registryRootType); + } + + this.AddChildToParent("Component", xRemoveRegistryValue, row, 4); + } + } + } + + /// + /// Decompile the ReserveCost table. + /// + /// The table to decompile. + private void DecompileReserveCostTable(Table table) + { + foreach (var row in table.Rows) + { + var xReserveCost = new XElement(Names.ReserveCostElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("Directory", row, 2), + new XAttribute("RunLocal", row.FieldAsString(3)), + new XAttribute("RunFromSource", row.FieldAsString(4))); + + this.AddChildToParent("Component", xReserveCost, row, 4); + } + } + + /// + /// Decompile the SelfReg table. + /// + /// The table to decompile. + private void DecompileSelfRegTable(Table table) + { + foreach (var row in table.Rows) + { + if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0))) + { + xFile.SetAttributeValue("SelfRegCost", row.IsColumnNull(1) ? 0 : row.FieldAsInteger(1)); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File")); + } + } + } + + /// + /// Decompile the ServiceControl table. + /// + /// The table to decompile. + private void DecompileServiceControlTable(Table table) + { + foreach (var row in table.Rows) + { + var xServiceControl = new XElement(Names.ServiceControlElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(1))); + + var eventValue = row.FieldAsInteger(2); + if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart) && + WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) + { + xServiceControl.SetAttributeValue("Start", "both"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart)) + { + xServiceControl.SetAttributeValue("Start", "install"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart)) + { + xServiceControl.SetAttributeValue("Start", "uninstall"); + } + + if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop) && + WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) + { + xServiceControl.SetAttributeValue("Stop", "both"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop)) + { + xServiceControl.SetAttributeValue("Stop", "install"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop)) + { + xServiceControl.SetAttributeValue("Stop", "uninstall"); + } + + if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete) && + WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) + { + xServiceControl.SetAttributeValue("Remove", "both"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete)) + { + xServiceControl.SetAttributeValue("Remove", "install"); + } + else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete)) + { + xServiceControl.SetAttributeValue("Remove", "uninstall"); + } + + if (!row.IsColumnNull(3)) + { + var arguments = NullSplitter.Split(row.FieldAsString(3)); + + foreach (var argument in arguments) + { + var xServiceArgument = new XElement(Names.ServiceArgumentElement, + new XAttribute("Value", argument)); + + xServiceControl.Add(xServiceArgument); + } + } + + if (!row.IsColumnNull(4)) + { + xServiceControl.SetAttributeValue("Wait", row.FieldAsInteger(4) == 0 ? "no" : "yes"); + } + + this.AddChildToParent("Component", xServiceControl, row, 5); + } + } + + /// + /// Decompile the ServiceInstall table. + /// + /// The table to decompile. + private void DecompileServiceInstallTable(Table table) + { + foreach (var row in table.Rows) + { + var xServiceInstall = new XElement(Names.ServiceInstallElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Name", row.FieldAsString(1)), + XAttributeIfNotNull("DisplayName", row, 2), + XAttributeIfNotNull("LoadOrderGroup", row, 6), + XAttributeIfNotNull("Account", row, 8), + XAttributeIfNotNull("Password", row, 9), + XAttributeIfNotNull("Arguments", row, 10), + XAttributeIfNotNull("Description", row, 12)); + + var serviceType = row.FieldAsInteger(3); + if (WindowsInstallerConstants.MsidbServiceInstallInteractive == (serviceType & WindowsInstallerConstants.MsidbServiceInstallInteractive)) + { + xServiceInstall.SetAttributeValue("Interactive", "yes"); + } + + if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess) && + WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) + { + // TODO: warn + } + else if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess)) + { + xServiceInstall.SetAttributeValue("Type", "ownProcess"); + } + else if (WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess)) + { + xServiceInstall.SetAttributeValue("Type", "shareProcess"); + } + + var startType = row.FieldAsInteger(4); + if (WindowsInstallerConstants.MsidbServiceInstallDisabled == startType) + { + xServiceInstall.SetAttributeValue("Start", "disabled"); + } + else if (WindowsInstallerConstants.MsidbServiceInstallDemandStart == startType) + { + xServiceInstall.SetAttributeValue("Start", "demand"); + } + else if (WindowsInstallerConstants.MsidbServiceInstallAutoStart == startType) + { + xServiceInstall.SetAttributeValue("Start", "auto"); + } + else + { + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4])); + } + + var errorControl = row.FieldAsInteger(5); + if (WindowsInstallerConstants.MsidbServiceInstallErrorCritical == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorCritical)) + { + xServiceInstall.SetAttributeValue("ErrorControl", "critical"); + } + else if (WindowsInstallerConstants.MsidbServiceInstallErrorNormal == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorNormal)) + { + xServiceInstall.SetAttributeValue("ErrorControl", "normal"); + } + else + { + xServiceInstall.SetAttributeValue("ErrorControl", "ignore"); + } + + if (WindowsInstallerConstants.MsidbServiceInstallErrorControlVital == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorControlVital)) + { + xServiceInstall.SetAttributeValue("Vital", "yes"); + } + + if (!row.IsColumnNull(7)) + { + var dependencies = NullSplitter.Split(row.FieldAsString(7)); + + foreach (var dependency in dependencies) + { + if (0 < dependency.Length) + { + var xServiceDependency = new XElement(Names.ServiceDependencyElement); + + if (dependency.StartsWith("+", StringComparison.Ordinal)) + { + xServiceDependency.SetAttributeValue("Group", "yes"); + xServiceDependency.SetAttributeValue("Id", dependency.Substring(1)); + } + else + { + xServiceDependency.SetAttributeValue("Id", dependency); + } + + xServiceInstall.Add(xServiceDependency); + } + } + } + + this.AddChildToParent("Component", xServiceInstall, row, 11); + this.IndexElement(row, xServiceInstall); + } + } + + /// + /// Decompile the SFPCatalog table. + /// + /// The table to decompile. + private void DecompileSFPCatalogTable(Table table) + { + foreach (var row in table.Rows) + { + var xSfpCatalog = new XElement(Names.SFPCatalogElement, + new XAttribute("Name", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1))); + + this.IndexElement(row, xSfpCatalog); + } + + // nest the SFPCatalog elements + foreach (var row in table.Rows) + { + var xSfpCatalog = this.GetIndexedElement(row); + + if (!row.IsColumnNull(2)) + { + if (this.TryGetIndexedElement("SFPCatalog", out var xParentSFPCatalog, row.FieldAsString(2))) + { + xParentSFPCatalog.Add(xSfpCatalog); + } + else + { + xSfpCatalog.SetAttributeValue("Dependency", row.FieldAsString(2)); + + this.RootElement.Add(xSfpCatalog); + } + } + else + { + this.RootElement.Add(xSfpCatalog); + } + } + } + + /// + /// Decompile the Shortcut table. + /// + /// The table to decompile. + private void DecompileShortcutTable(Table table) + { + foreach (var row in table.Rows) + { + var xShortcut = new XElement(Names.ShortcutElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Directory", row.FieldAsString(1)), + XAttributeIfNotNull("Arguments", row, 5), + XAttributeIfNotNull("Description", row, 6), + XAttributeIfNotNull("Hotkey", row, 7), + XAttributeIfNotNull("Icon", row, 8), + XAttributeIfNotNull("IconIndex", row, 9), + XAttributeIfNotNull("WorkingDirectory", row, 11)); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2)); + if (null != names[0] && null != names[1]) + { + xShortcut.SetAttributeValue("ShortName", names[0]); + xShortcut.SetAttributeValue("Name", names[1]); + } + else if (null != names[0]) + { + xShortcut.SetAttributeValue("Name", names[0]); + } + + if (!row.IsColumnNull(10)) + { + switch (row.FieldAsInteger(10)) + { + case 1: + xShortcut.SetAttributeValue("Show", "normal"); + break; + case 3: + xShortcut.SetAttributeValue("Show", "maximized"); + break; + case 7: + xShortcut.SetAttributeValue("Show", "minimized"); + break; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10])); + break; + } + } + + // Only try to read the MSI 4.0-specific columns if they actually exist + if (15 < row.Fields.Length) + { + if (!row.IsColumnNull(12)) + { + xShortcut.SetAttributeValue("DisplayResourceDll", row.FieldAsString(12)); + } + + if (null != row[13]) + { + xShortcut.SetAttributeValue("DisplayResourceId", row.FieldAsInteger(13)); + } + + if (null != row[14]) + { + xShortcut.SetAttributeValue("DescriptionResourceDll", row.FieldAsString(14)); + } + + if (null != row[15]) + { + xShortcut.SetAttributeValue("DescriptionResourceId", row.FieldAsInteger(15)); + } + } + + this.AddChildToParent("Component", xShortcut, row, 3); + this.IndexElement(row, xShortcut); + } + } + + /// + /// Decompile the Signature table. + /// + /// The table to decompile. + private void DecompileSignatureTable(Table table) + { + foreach (var row in table.Rows) + { + var fileSearch = new XElement(Names.FileSearchElement, + new XAttribute("Id", row.FieldAsString(0)), + XAttributeIfNotNull("MinVersion", row, 2), + XAttributeIfNotNull("MaxVersion", row, 3), + XAttributeIfNotNull("MinSize", row, 4), + XAttributeIfNotNull("MaxSize", row, 5), + XAttributeIfNotNull("Languages", row, 8)); + + var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1)); + if (null != names[0]) + { + // it is permissable to just have a long name + if (!this.BackendHelper.IsValidShortFilename(names[0], false) && null == names[1]) + { + fileSearch.SetAttributeValue("Name", names[0]); + } + else + { + fileSearch.SetAttributeValue("ShortName", names[0]); + } + } + + if (null != names[1]) + { + fileSearch.SetAttributeValue("Name", names[1]); + } + + if (!row.IsColumnNull(6)) + { + fileSearch.SetAttributeValue("MinDate", ConvertIntegerToDateTime(row.FieldAsInteger(6))); + } + + if (!row.IsColumnNull(7)) + { + fileSearch.SetAttributeValue("MaxDate", ConvertIntegerToDateTime(row.FieldAsInteger(7))); + } + + this.IndexElement(row, fileSearch); + } + } + + /// + /// Decompile the TargetFiles_OptionalData table. + /// + /// The table to decompile. + private void DecompileTargetFiles_OptionalDataTable(Table table) + { + foreach (var row in table.Rows) + { + if (!this.PatchTargetFiles.TryGetValue(row.FieldAsString(0), out var xPatchTargetFile)) + { + xPatchTargetFile = new XElement(Names.TargetFileElement, + new XAttribute("Id", row.FieldAsString(1))); + + if (this.TryGetIndexedElement("TargetImages", out var xTargetImage, row.FieldAsString(0))) + { + xTargetImage.Add(xPatchTargetFile); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages")); + } + + this.PatchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), xPatchTargetFile); + } + + AddSymbolPaths(row, 2, xPatchTargetFile); + + if (!row.IsColumnNull(3) && !row.IsColumnNull(4)) + { + var ignoreOffsets = row.FieldAsString(3).Split(','); + var ignoreLengths = row.FieldAsString(4).Split(','); + + if (ignoreOffsets.Length == ignoreLengths.Length) + { + for (var i = 0; i < ignoreOffsets.Length; i++) + { + var xIgnoreRange = new XElement(Names.IgnoreRangeElement); + + if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal)) + { + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16)); + } + else + { + xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture)); + } + + if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal)) + { + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16)); + } + else + { + xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture)); + } + + xPatchTargetFile.Add(xIgnoreRange); + } + } + else + { + // TODO: warn + } + } + else if (!row.IsColumnNull(3) || !row.IsColumnNull(4)) + { + // TODO: warn about mismatch between columns + } + + // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable + } + } + + /// + /// Decompile the TargetImages table. + /// + /// The table to decompile. + private void DecompileTargetImagesTable(Table table) + { + foreach (var row in table.Rows) + { + var xTargetImage = new XElement(Names.TargetImageElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1)), + new XAttribute("Order", row.FieldAsInteger(4)), + XAttributeIfNotNull("Validation", row, 5)); + + AddSymbolPaths(row, 2, xTargetImage); + + if (0 != row.FieldAsInteger(6)) + { + xTargetImage.SetAttributeValue("IgnoreMissingFiles", "yes"); + } + + this.AddChildToParent("UpgradedImages", xTargetImage, row, 3); + this.IndexElement(row, xTargetImage); + } + } + + /// + /// Decompile the TextStyle table. + /// + /// The table to decompile. + private void DecompileTextStyleTable(Table table) + { + foreach (var row in table.Rows) + { + var xTextStyle = new XElement(Names.TextStyleElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("FaceName", row.FieldAsString(1)), + new XAttribute("Size", row.FieldAsString(2))); + + if (!row.IsColumnNull(3)) + { + var color = row.FieldAsInteger(3); + + xTextStyle.SetAttributeValue("Red", color & 0xFF); + xTextStyle.SetAttributeValue("Green", (color & 0xFF00) >> 8); + xTextStyle.SetAttributeValue("Blue", (color & 0xFF0000) >> 16); + } + + if (!row.IsColumnNull(4)) + { + var styleBits = row.FieldAsInteger(4); + + if (WindowsInstallerConstants.MsidbTextStyleStyleBitsBold == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsBold)) + { + xTextStyle.SetAttributeValue("Bold", "yes"); + } + + if (WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic)) + { + xTextStyle.SetAttributeValue("Italic", "yes"); + } + + if (WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline)) + { + xTextStyle.SetAttributeValue("Underline", "yes"); + } + + if (WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike)) + { + xTextStyle.SetAttributeValue("Strike", "yes"); + } + } + + this.UIElement.Add(xTextStyle); + } + } + + /// + /// Decompile the TypeLib table. + /// + /// The table to decompile. + private void DecompileTypeLibTable(Table table) + { + foreach (var row in table.Rows) + { + var id = row.FieldAsString(0); + var xTypeLib = new XElement(Names.TypeLibElement, + new XAttribute("Advertise", "yes"), + new XAttribute("Id", id), + new XAttribute("Language", row.FieldAsInteger(1)), + XAttributeIfNotNull("Description", row, 4), + XAttributeIfNotNull("HelpDirectory", row, 5)); + + if (!row.IsColumnNull(3)) + { + var version = row.FieldAsInteger(3); + + if (65536 == version) + { + this.Messaging.Write(WarningMessages.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, id)); + } + + xTypeLib.SetAttributeValue("MajorVersion", (version & 0xFFFF00) >> 8); + xTypeLib.SetAttributeValue("MinorVersion", version & 0xFF); + } + + if (!row.IsColumnNull(7)) + { + xTypeLib.SetAttributeValue("Cost", row.FieldAsInteger(7)); + } + + // nested under the appropriate File element in FinalizeFileTable + this.IndexElement(row, xTypeLib); + } + } + + /// + /// Decompile the Upgrade table. + /// + /// The table to decompile. + private void DecompileUpgradeTable(Table table) + { + var xUpgrades = new Dictionary(); + + foreach (UpgradeRow upgradeRow in table.Rows) + { + if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty || WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty) + { + continue; // MajorUpgrade rows processed in FinalizeUpgradeTable + } + + if (!xUpgrades.TryGetValue(upgradeRow.UpgradeCode, out var xUpgrade)) + { + xUpgrade = new XElement(Names.UpgradeElement, + new XAttribute("Id", upgradeRow.UpgradeCode)); + + this.RootElement.Add(xUpgrade); + xUpgrades.Add(upgradeRow.UpgradeCode, xUpgrade); + } + + var xUpgradeVersion = new XElement(Names.UpgradeVersionElement, + new XAttribute("Id", upgradeRow.UpgradeCode), + new XAttribute("Property", upgradeRow.ActionProperty)); + + if (null != upgradeRow.VersionMin) + { + xUpgradeVersion.SetAttributeValue("Minimum", upgradeRow.VersionMin); + } + + if (null != upgradeRow.VersionMax) + { + xUpgradeVersion.SetAttributeValue("Maximum", upgradeRow.VersionMax); + } + + if (null != upgradeRow.Language) + { + xUpgradeVersion.SetAttributeValue("Language", upgradeRow.Language); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures)) + { + xUpgradeVersion.SetAttributeValue("MigrateFeatures", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect)) + { + xUpgradeVersion.SetAttributeValue("OnlyDetect", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure)) + { + xUpgradeVersion.SetAttributeValue("IgnoreRemoveFailure", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive)) + { + xUpgradeVersion.SetAttributeValue("IncludeMinimum", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive)) + { + xUpgradeVersion.SetAttributeValue("IncludeMaximum", "yes"); + } + + if (WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive)) + { + xUpgradeVersion.SetAttributeValue("ExcludeLanguages", "yes"); + } + + if (null != upgradeRow.Remove) + { + xUpgradeVersion.SetAttributeValue("RemoveFeatures", upgradeRow.Remove); + } + + xUpgrade.Add(xUpgradeVersion); + } + } + + /// + /// Decompile the UpgradedFiles_OptionalData table. + /// + /// The table to decompile. + private void DecompileUpgradedFiles_OptionalDataTable(Table table) + { + foreach (var row in table.Rows) + { + var xUpgradeFile = new XElement(Names.UpgradeFileElement, + new XAttribute("File", row.FieldAsString(1)), + new XAttribute("Ignore", "no")); + + AddSymbolPaths(row, 2, xUpgradeFile); + + if (!row.IsColumnNull(3) && 1 == row.FieldAsInteger(3)) + { + xUpgradeFile.SetAttributeValue("AllowIgnoreOnError", "yes"); + } + + if (!row.IsColumnNull(4) && 0 != row.FieldAsInteger(4)) + { + xUpgradeFile.SetAttributeValue("WholeFile", "yes"); + } + + this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); + } + } + + /// + /// Decompile the UpgradedFilesToIgnore table. + /// + /// The table to decompile. + private void DecompileUpgradedFilesToIgnoreTable(Table table) + { + foreach (var row in table.Rows) + { + if ("*" != row.FieldAsString(0)) + { + var xUpgradeFile = new XElement(Names.UpgradeFileElement, + new XAttribute("File", row.FieldAsString(1)), + new XAttribute("Ignore", "yes")); + + this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0); + } + else + { + this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0])); + } + } + } + + /// + /// Decompile the UpgradedImages table. + /// + /// The table to decompile. + private void DecompileUpgradedImagesTable(Table table) + { + foreach (var row in table.Rows) + { + var xUpgradeImage = new XElement(Names.UpgradeImageElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("SourceFile", row.FieldAsString(1)), + XAttributeIfNotNull("SourcePatch", row, 2)); + + AddSymbolPaths(row, 3, xUpgradeImage); + + this.AddChildToParent("ImageFamilies", xUpgradeImage, row, 4); + this.IndexElement(row, xUpgradeImage); + } + } + + private static void AddSymbolPaths(Row row, int column, XElement xParent) + { + if (!row.IsColumnNull(column)) + { + var symbolPaths = row.FieldAsString(column).Split(';'); + + foreach (var symbolPath in symbolPaths) + { + var xSymbolPath = new XElement(Names.SymbolPathElement, + new XAttribute("Path", symbolPath)); + + xParent.Add(xSymbolPath); + } + } + } + + /// + /// Decompile the UIText table. + /// + /// The table to decompile. + private void DecompileUITextTable(Table table) + { + foreach (var row in table.Rows) + { + var xUiText = new XElement(Names.UITextElement, + new XAttribute("Id", row.FieldAsString(0)), + new XAttribute("Value", row.FieldAsString(1))); + + this.UIElement.Add(xUiText); + } + } + + /// + /// Decompile the Verb table. + /// + /// The table to decompile. + private void DecompileVerbTable(Table table) + { + foreach (var row in table.Rows) + { + var verb = new XElement(Names.VerbElement, + new XAttribute("Id", row.FieldAsString(1)), + XAttributeIfNotNull("Sequence", row, 2), + XAttributeIfNotNull("Command", row, 3), + XAttributeIfNotNull("Argument", row, 4)); + + this.IndexElement(row, verb); + } + } + + /// + /// Gets the RegistryRootType from an integer representation of the root. + /// + /// The source line information for the root. + /// The name of the table containing the field. + /// The field containing the root value. + /// The strongly-typed representation of the root. + /// true if the value could be converted; false otherwise. + private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out string registryRootType) + { + switch (Convert.ToInt32(field.Data)) + { + case (-1): + registryRootType = "HKMU"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootClassesRoot: + registryRootType = "HKCR"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootCurrentUser: + registryRootType = "HKCU"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootLocalMachine: + registryRootType = "HKLM"; + return true; + case WindowsInstallerConstants.MsidbRegistryRootUsers: + registryRootType = "HKU"; + return true; + default: + this.Messaging.Write(WarningMessages.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data)); + registryRootType = null; // assign anything to satisfy the out parameter + return false; + } + } + + /// + /// Set the primary feature for a component. + /// + /// The row which specifies a primary feature. + /// The index of the column contaning the feature identifier. + /// The index of the column containing the component identifier. + private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex) + { + // only products contain primary features + if (OutputType.Product == this.OutputType) + { + var featureField = row.Fields[featureColumnIndex]; + var componentField = row.Fields[componentColumnIndex]; + + if (this.TryGetIndexedElement("FeatureComponents", out var xComponentRef, Convert.ToString(featureField.Data), Convert.ToString(componentField.Data))) + { + xComponentRef.SetAttributeValue("Primary", "yes"); + } + else + { + this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), featureField.Column.Name, Convert.ToString(featureField.Data), componentField.Column.Name, Convert.ToString(componentField.Data), "FeatureComponents")); + } + } + } + + /// + /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it. + /// + /// The collection of all tables. + private static string DetermineMajorUpgradeScheduling(TableIndexedCollection tables) + { + var sequenceRemoveExistingProducts = 0; + var sequenceInstallValidate = 0; + var sequenceInstallInitialize = 0; + var sequenceInstallFinalize = 0; + var sequenceInstallExecute = 0; + var sequenceInstallExecuteAgain = 0; + + var installExecuteSequenceTable = tables["InstallExecuteSequence"]; + if (null != installExecuteSequenceTable) + { + var removeExistingProductsRow = -1; + for (var i = 0; i < installExecuteSequenceTable.Rows.Count; i++) + { + var row = installExecuteSequenceTable.Rows[i]; + var action = row.FieldAsString(0); + var sequence = row.FieldAsInteger(2); + + switch (action) + { + case "RemoveExistingProducts": + sequenceRemoveExistingProducts = sequence; + removeExistingProductsRow = i; + break; + case "InstallValidate": + sequenceInstallValidate = sequence; + break; + case "InstallInitialize": + sequenceInstallInitialize = sequence; + break; + case "InstallExecute": + sequenceInstallExecute = sequence; + break; + case "InstallExecuteAgain": + sequenceInstallExecuteAgain = sequence; + break; + case "InstallFinalize": + sequenceInstallFinalize = sequence; + break; + } + } + + installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow); + } + + if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize) + { + return "afterInstallValidate"; + } + else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute) + { + return "afterInstallInitialize"; + } + else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain) + { + return "afterInstallExecute"; + } + else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize) + { + return "afterInstallExecuteAgain"; + } + else + { + return "afterInstallFinalize"; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs new file mode 100644 index 00000000..db65bbf7 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs @@ -0,0 +1,160 @@ +namespace WixToolset.Core.WindowsInstaller.Decompile +{ + using System.Xml.Linq; + + internal static class Names + { + public static readonly XNamespace WxsNamespace = "http://wixtoolset.org/schemas/v4/wxs"; + + public static readonly XName WixElement = WxsNamespace + "Wix"; + + public static readonly XName PackageElement = WxsNamespace + "Package"; + public static readonly XName ModuleElement = WxsNamespace + "Module"; + public static readonly XName PatchCreationElement = WxsNamespace + "PatchCreation"; + + public static readonly XName SummaryInformationElement = WxsNamespace + "SummaryInformation"; + + public static readonly XName CustomElement = WxsNamespace + "Custom"; + + public static readonly XName AdminExecuteSequenceElement = WxsNamespace + "AdminExecuteSequence"; + public static readonly XName AdminUISequenceElement = WxsNamespace + "AdminUISequence"; + public static readonly XName AdvertiseExecuteSequenceElement = WxsNamespace + "AdvertiseExecuteSequence"; + public static readonly XName InstallExecuteSequenceElement = WxsNamespace + "InstallExecuteSequence"; + public static readonly XName InstallUISequenceElement = WxsNamespace + "InstallUISequence"; + + public static readonly XName AppSearchElement = WxsNamespace + "AppSearch"; + + public static readonly XName PropertyElement = WxsNamespace + "Property"; + + public static readonly XName ProtectRangeElement = WxsNamespace + "ProtectRange"; + public static readonly XName ProtectFileElement = WxsNamespace + "ProtectFile"; + + public static readonly XName FileElement = WxsNamespace + "File"; + + public static readonly XName EnsureTableElement = WxsNamespace + "EnsureTable"; + public static readonly XName PatchInformationElement = WxsNamespace + "PatchInformation"; + + public static readonly XName ProgressTextElement = WxsNamespace + "ProgressText"; + public static readonly XName UIElement = WxsNamespace + "UI"; + + public static readonly XName AppIdElement = WxsNamespace + "AppId"; + + public static readonly XName ControlElement = WxsNamespace + "Control"; + + public static readonly XName BillboardElement = WxsNamespace + "Billboard"; + public static readonly XName BillboardActionElement = WxsNamespace + "BillboardAction"; + + public static readonly XName BinaryElement = WxsNamespace + "Binary"; + + public static readonly XName ClassElement = WxsNamespace + "Class"; + + public static readonly XName FileTypeMaskElement = WxsNamespace + "FileTypeMask"; + + public static readonly XName ComboBoxElement = WxsNamespace + "ComboBox"; + + public static readonly XName ListItemElement = WxsNamespace + "ListItem"; + + public static readonly XName ConditionElement = WxsNamespace + "Condition"; + public static readonly XName PublishElement = WxsNamespace + "Publish"; + public static readonly XName CustomTableElement = WxsNamespace + "CustomTable"; + public static readonly XName ColumnElement = WxsNamespace + "Column"; + public static readonly XName RowElement = WxsNamespace + "Row"; + public static readonly XName DataElement = WxsNamespace + "Data"; + public static readonly XName CreateFolderElement = WxsNamespace + "CreateFolder"; + + public static readonly XName CustomActionElement = WxsNamespace + "CustomAction"; + + public static readonly XName ComponentSearchElement = WxsNamespace + "ComponentSearch"; + public static readonly XName ComponentElement = WxsNamespace + "Component"; + + public static readonly XName LevelElement = WxsNamespace + "Level"; + public static readonly XName DialogElement = WxsNamespace + "Dialog"; + public static readonly XName StandardDirectoryElement = WxsNamespace + "StandardDirectory"; + public static readonly XName DirectoryElement = WxsNamespace + "Directory"; + public static readonly XName DirectorySearchElement = WxsNamespace + "DirectorySearch"; + public static readonly XName CopyFileElement = WxsNamespace + "CopyFile"; + public static readonly XName EnvironmentElement = WxsNamespace + "Environment"; + public static readonly XName ErrorElement = WxsNamespace + "Error"; + public static readonly XName SubscribeElement = WxsNamespace + "Subscribe"; + public static readonly XName ExtensionElement = WxsNamespace + "Extension"; + public static readonly XName ExternalFileElement = WxsNamespace + "ExternalFile"; + public static readonly XName SymbolPathElement = WxsNamespace + "SymbolPath"; + public static readonly XName IgnoreRangeElement = WxsNamespace + "IgnoreRange"; + + public static readonly XName FeatureElement = WxsNamespace + "Feature"; + public static readonly XName ComponentRefElement = WxsNamespace + "ComponentRef"; + public static readonly XName SFPFileElement = WxsNamespace + "SFPFile"; + public static readonly XName IconElement = WxsNamespace + "Icon"; + public static readonly XName FamilyElement = WxsNamespace + "Family"; + public static readonly XName IniFileElement = WxsNamespace + "IniFile"; + public static readonly XName IniFileSearchElement = WxsNamespace + "IniFileSearch"; + public static readonly XName IsolateComponentElement = WxsNamespace + "IsolateComponent"; + public static readonly XName LaunchElement = WxsNamespace + "Launch"; + public static readonly XName ListBoxElement = WxsNamespace + "ListBox"; + public static readonly XName ListViewElement = WxsNamespace + "ListView"; + public static readonly XName PermissionElement = WxsNamespace + "Permission"; + public static readonly XName MediaElement = WxsNamespace + "Media"; + public static readonly XName MIMEElement = WxsNamespace + "MIME"; + public static readonly XName ConfigurationElement = WxsNamespace + "Configuration"; + public static readonly XName DependencyElement = WxsNamespace + "Dependency"; + public static readonly XName ExclusionElement = WxsNamespace + "Exclusion"; + public static readonly XName IgnoreTableElement = WxsNamespace + "IgnoreTable"; + public static readonly XName SubstitutionElement = WxsNamespace + "Substitution"; + public static readonly XName DigitalCertificateElement = WxsNamespace + "DigitalCertificate"; + public static readonly XName DigitalSignatureElement = WxsNamespace + "DigitalSignature"; + public static readonly XName EmbeddedChainerElement = WxsNamespace + "EmbeddedChainer"; + public static readonly XName EmbeddedUIElement = WxsNamespace + "EmbeddedUI"; + public static readonly XName EmbeddedUIResourceElement = WxsNamespace + "EmbeddedUIResource"; + public static readonly XName PermissionExElement = WxsNamespace + "PermissionEx"; + public static readonly XName PackageCertificatesElement = WxsNamespace + "PackageCertificates"; + public static readonly XName PatchCertificatesElement = WxsNamespace + "PatchCertificates"; + public static readonly XName ShortcutPropertyElement = WxsNamespace + "ShortcutProperty"; + public static readonly XName ODBCDataSourceElement = WxsNamespace + "ODBCDataSource"; + public static readonly XName ODBCDriverElement = WxsNamespace + "ODBCDriver"; + public static readonly XName ODBCTranslatorElement = WxsNamespace + "ODBCTranslator"; + public static readonly XName PatchMetadataElement = WxsNamespace + "PatchMetadata"; + public static readonly XName OptimizeCustomActionsElement = WxsNamespace + "OptimizeCustomActions"; + public static readonly XName CustomPropertyElement = WxsNamespace + "CustomProperty"; + public static readonly XName PatchSequenceElement = WxsNamespace + "PatchSequence"; + public static readonly XName ProgIdElement = WxsNamespace + "ProgId"; + public static readonly XName ReplacePatchElement = WxsNamespace + "ReplacePatch"; + public static readonly XName TargetProductCodeElement = WxsNamespace + "TargetProductCode"; + public static readonly XName PatchPropertyElement = WxsNamespace + "PatchProperty"; + public static readonly XName CategoryElement = WxsNamespace + "Category"; + public static readonly XName RadioButtonElement = WxsNamespace + "RadioButton"; + public static readonly XName RadioButtonGroupElement = WxsNamespace + "RadioButtonGroup"; + public static readonly XName RegistryKeyElement = WxsNamespace + "RegistryKey"; + public static readonly XName RegistryValueElement = WxsNamespace + "RegistryValue"; + public static readonly XName MultiStringElement = WxsNamespace + "MultiString"; + public static readonly XName RegistrySearchElement = WxsNamespace + "RegistrySearch"; + public static readonly XName RemoveFolderElement = WxsNamespace + "RemoveFolder"; + public static readonly XName RemoveFileElement = WxsNamespace + "RemoveFile"; + public static readonly XName RemoveRegistryKeyElement = WxsNamespace + "RemoveRegistryKey"; + public static readonly XName RemoveRegistryValueElement = WxsNamespace + "RemoveRegistryValue"; + public static readonly XName ReserveCostElement = WxsNamespace + "ReserveCost"; + public static readonly XName ServiceControlElement = WxsNamespace + "ServiceControl"; + public static readonly XName ServiceArgumentElement = WxsNamespace + "ServiceArgument"; + public static readonly XName ServiceInstallElement = WxsNamespace + "ServiceInstall"; + public static readonly XName ServiceDependencyElement = WxsNamespace + "ServiceDependency"; + public static readonly XName SFPCatalogElement = WxsNamespace + "SFPCatalog"; + public static readonly XName ShortcutElement = WxsNamespace + "Shortcut"; + public static readonly XName FileSearchElement = WxsNamespace + "FileSearch"; + public static readonly XName TargetFileElement = WxsNamespace + "TargetFile"; + public static readonly XName TargetImageElement = WxsNamespace + "TargetImage"; + public static readonly XName TextStyleElement = WxsNamespace + "TextStyle"; + public static readonly XName TypeLibElement = WxsNamespace + "TypeLib"; + public static readonly XName UpgradeElement = WxsNamespace + "Upgrade"; + public static readonly XName UpgradeVersionElement = WxsNamespace + "UpgradeVersion"; + public static readonly XName UpgradeFileElement = WxsNamespace + "UpgradeFile"; + public static readonly XName UpgradeImageElement = WxsNamespace + "UpgradeImage"; + public static readonly XName UITextElement = WxsNamespace + "UIText"; + public static readonly XName VerbElement = WxsNamespace + "Verb"; + public static readonly XName ComplianceCheckElement = WxsNamespace + "ComplianceCheck"; + public static readonly XName FileSearchRefElement = WxsNamespace + "FileSearchRef"; + public static readonly XName ComplianceDriveElement = WxsNamespace + "ComplianceDrive"; + public static readonly XName DirectorySearchRefElement = WxsNamespace + "DirectorySearchRef"; + public static readonly XName RegistrySearchRefElement = WxsNamespace + "RegistrySearchRef"; + public static readonly XName MajorUpgradeElement = WxsNamespace + "MajorUpgrade"; + //public static readonly XName Element = WxsNamespace + ""; + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs new file mode 100644 index 00000000..304d0152 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Differ.cs @@ -0,0 +1,610 @@ +// 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. + +#if DELETE + +namespace WixToolset.Core.WindowsInstaller +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using WixToolset.Core.WindowsInstaller.Msi; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + /// + /// Creates a transform by diffing two outputs. + /// + public sealed class Differ + { + private readonly List inspectorExtensions; + private bool showPedanticMessages; + private bool suppressKeepingSpecialRows; + private bool preserveUnchangedRows; + private const char sectionDelimiter = '/'; + private readonly IMessaging messaging; + private SummaryInformationStreams transformSummaryInfo; + + /// + /// Instantiates a new Differ class. + /// + public Differ(IMessaging messaging) + { + this.inspectorExtensions = new List(); + this.messaging = messaging; + } + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + public bool ShowPedanticMessages + { + get { return this.showPedanticMessages; } + set { this.showPedanticMessages = value; } + } + + /// + /// Gets or sets the option to suppress keeping special rows. + /// + /// The option to suppress keeping special rows. + public bool SuppressKeepingSpecialRows + { + get { return this.suppressKeepingSpecialRows; } + set { this.suppressKeepingSpecialRows = value; } + } + + /// + /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. + /// + /// The option to keep all rows including unchanged rows. + public bool PreserveUnchangedRows + { + get { return this.preserveUnchangedRows; } + set { this.preserveUnchangedRows = value; } + } + + /// + /// Adds an extension. + /// + /// The extension to add. + public void AddExtension(IInspectorExtension extension) + { + this.inspectorExtensions.Add(extension); + } + + /// + /// Creates a transform by diffing two outputs. + /// + /// The target output. + /// The updated output. + /// The transform. + public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput) + { + return this.Diff(targetOutput, updatedOutput, 0); + } + + /// + /// Creates a transform by diffing two outputs. + /// + /// The target output. + /// The updated output. + /// + /// The transform. + public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, TransformFlags validationFlags) + { + WindowsInstallerData transform = new WindowsInstallerData(null); + transform.Type = OutputType.Transform; + transform.Codepage = updatedOutput.Codepage; + this.transformSummaryInfo = new SummaryInformationStreams(); + + // compare the codepages + if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) + { + this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); + if (null != updatedOutput.SourceLineNumbers) + { + this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); + } + } + + // compare the output types + if (targetOutput.Type != updatedOutput.Type) + { + throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); + } + + // compare the contents of the tables + foreach (Table targetTable in targetOutput.Tables) + { + Table updatedTable = updatedOutput.Tables[targetTable.Name]; + TableOperation operation = TableOperation.None; + + List rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); + + if (TableOperation.Drop == operation) + { + Table droppedTable = transform.EnsureTable(targetTable.Definition); + droppedTable.Operation = TableOperation.Drop; + } + else if (TableOperation.None == operation) + { + Table modified = transform.EnsureTable(updatedTable.Definition); + rows.ForEach(r => modified.Rows.Add(r)); + } + } + + // added tables + foreach (Table updatedTable in updatedOutput.Tables) + { + if (null == targetOutput.Tables[updatedTable.Name]) + { + Table addedTable = transform.EnsureTable(updatedTable.Definition); + addedTable.Operation = TableOperation.Add; + + foreach (Row updatedRow in updatedTable.Rows) + { + updatedRow.Operation = RowOperation.Add; + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + addedTable.Rows.Add(updatedRow); + } + } + } + + // set summary information properties + if (!this.suppressKeepingSpecialRows) + { + Table summaryInfoTable = transform.Tables["_SummaryInformation"]; + this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); + } + + return transform; + } + + /// + /// Add a row to the using the primary key. + /// + /// The indexed rows. + /// The row to index. + private void AddIndexedRow(IDictionary index, Row row) + { + string primaryKey = row.GetPrimaryKey('/'); + if (null != primaryKey) + { + // Overriding WixActionRows have a primary key defined and take precedence in the index. + if (row is WixActionRow) + { + WixActionRow currentRow = (WixActionRow)row; + if (index.Contains(primaryKey)) + { + // If the current row is not overridable, see if the indexed row is. + if (!currentRow.Overridable) + { + WixActionRow indexedRow = index[primaryKey] as WixActionRow; + if (null != indexedRow && indexedRow.Overridable) + { + // The indexed key is overridable and should be replaced + // (not removed and re-added which results in two Array.Copy + // operations for SortedList, or may be re-hashing in other + // implementations of IDictionary). + index[primaryKey] = currentRow; + } + } + + // If we got this far, the row does not need to be indexed. + return; + } + } + + // Nothing else should be added more than once. + if (!index.Contains(primaryKey)) + { + index.Add(primaryKey, row); + } + else if (this.showPedanticMessages) + { + this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); + } + } + else // use the string representation of the row as its primary key (it may not be unique) + { + // this is provided for compatibility with unreal tables with no primary key + // all real tables must specify at least one column as the primary key + primaryKey = row.ToString(); + index[primaryKey] = row; + } + } + + private Row CompareRows(Table targetTable, Row targetRow, Row updatedRow, out RowOperation operation, out bool keepRow) + { + Row comparedRow = null; + keepRow = false; + operation = RowOperation.None; + + if (null == targetRow ^ null == updatedRow) + { + if (null == targetRow) + { + operation = updatedRow.Operation = RowOperation.Add; + comparedRow = updatedRow; + } + else if (null == updatedRow) + { + operation = targetRow.Operation = RowOperation.Delete; + targetRow.SectionId = targetRow.SectionId + sectionDelimiter; + comparedRow = targetRow; + keepRow = true; + } + } + else // possibly modified + { + updatedRow.Operation = RowOperation.None; + if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) + { + // ignore rows that shouldn't be in a transform + if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) + { + updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + comparedRow = updatedRow; + keepRow = true; + operation = RowOperation.Modify; + } + } + else + { + if (this.preserveUnchangedRows) + { + keepRow = true; + } + + for (int i = 0; i < updatedRow.Fields.Length; i++) + { + ColumnDefinition columnDefinition = updatedRow.Fields[i].Column; + + if (!columnDefinition.PrimaryKey) + { + bool modified = false; + + if (i >= targetRow.Fields.Length) + { + columnDefinition.Added = true; + modified = true; + } + else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) + { + if (null == targetRow[i] ^ null == updatedRow[i]) + { + modified = true; + } + else if (null != targetRow[i] && null != updatedRow[i]) + { + modified = ((int)targetRow[i] != (int)updatedRow[i]); + } + } + else if (ColumnType.Preserved == columnDefinition.Type) + { + updatedRow.Fields[i].PreviousData = (string)targetRow.Fields[i].Data; + + // keep rows containing preserved fields so the historical data is available to the binder + keepRow = !this.suppressKeepingSpecialRows; + } + else if (ColumnType.Object == columnDefinition.Type) + { + ObjectField targetObjectField = (ObjectField)targetRow.Fields[i]; + ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i]; + + updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex; + updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri; + + // always keep a copy of the previous data even if they are identical + // This makes diff.wixmst clean and easier to control patch logic + updatedObjectField.PreviousData = (string)targetObjectField.Data; + + // always remember the unresolved data for target build + updatedObjectField.UnresolvedPreviousData = (string)targetObjectField.UnresolvedData; + + // keep rows containing object fields so the files can be compared in the binder + keepRow = !this.suppressKeepingSpecialRows; + } + else + { + modified = ((string)targetRow[i] != (string)updatedRow[i]); + } + + if (modified) + { + if (null != updatedRow.Fields[i].PreviousData) + { + updatedRow.Fields[i].PreviousData = targetRow.Fields[i].Data.ToString(); + } + + updatedRow.Fields[i].Modified = true; + operation = updatedRow.Operation = RowOperation.Modify; + keepRow = true; + } + } + } + + if (keepRow) + { + comparedRow = updatedRow; + comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; + } + } + } + + return comparedRow; + } + + private List CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) + { + List rows = new List(); + operation = TableOperation.None; + + // dropped tables + if (null == updatedTable ^ null == targetTable) + { + if (null == targetTable) + { + operation = TableOperation.Add; + rows.AddRange(updatedTable.Rows); + } + else if (null == updatedTable) + { + operation = TableOperation.Drop; + } + } + else // possibly modified tables + { + SortedList updatedPrimaryKeys = new SortedList(); + SortedList targetPrimaryKeys = new SortedList(); + + // compare the table definitions + if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) + { + // continue to the next table; may be more mismatches + this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); + } + else + { + this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); + + // diff the target and updated rows + foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys) + { + string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key; + bool keepRow = false; + RowOperation rowOperation = RowOperation.None; + + Row compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value as Row, updatedPrimaryKeys[targetPrimaryKey] as Row, out rowOperation, out keepRow); + + if (keepRow) + { + rows.Add(compared); + } + } + + // find the inserted rows + foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys) + { + string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key; + + if (!targetPrimaryKeys.Contains(updatedPrimaryKey)) + { + Row updatedRow = (Row)updatedPrimaryKeyEntry.Value; + + updatedRow.Operation = RowOperation.Add; + updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; + rows.Add(updatedRow); + } + } + } + } + + return rows; + } + + private void IndexPrimaryKeys(Table targetTable, SortedList targetPrimaryKeys, Table updatedTable, SortedList updatedPrimaryKeys) + { + // index the target rows + foreach (Row row in targetTable.Rows) + { + this.AddIndexedRow(targetPrimaryKeys, row); + + if ("Property" == targetTable.Name) + { + if ("ProductCode" == (string)row[0]) + { + this.transformSummaryInfo.TargetProductCode = (string)row[1]; + if ("*" == this.transformSummaryInfo.TargetProductCode) + { + this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); + } + } + else if ("ProductVersion" == (string)row[0]) + { + this.transformSummaryInfo.TargetProductVersion = (string)row[1]; + } + else if ("UpgradeCode" == (string)row[0]) + { + this.transformSummaryInfo.TargetUpgradeCode = (string)row[1]; + } + } + else if ("_SummaryInformation" == targetTable.Name) + { + if (1 == (int)row[0]) // PID_CODEPAGE + { + this.transformSummaryInfo.TargetSummaryInfoCodepage = (string)row[1]; + } + else if (7 == (int)row[0]) // PID_TEMPLATE + { + this.transformSummaryInfo.TargetPlatformAndLanguage = (string)row[1]; + } + else if (14 == (int)row[0]) // PID_PAGECOUNT + { + this.transformSummaryInfo.TargetMinimumVersion = (string)row[1]; + } + } + } + + // index the updated rows + foreach (Row row in updatedTable.Rows) + { + this.AddIndexedRow(updatedPrimaryKeys, row); + + if ("Property" == updatedTable.Name) + { + if ("ProductCode" == (string)row[0]) + { + this.transformSummaryInfo.UpdatedProductCode = (string)row[1]; + if ("*" == this.transformSummaryInfo.UpdatedProductCode) + { + this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers)); + } + } + else if ("ProductVersion" == (string)row[0]) + { + this.transformSummaryInfo.UpdatedProductVersion = (string)row[1]; + } + } + else if ("_SummaryInformation" == updatedTable.Name) + { + if (1 == (int)row[0]) // PID_CODEPAGE + { + this.transformSummaryInfo.UpdatedSummaryInfoCodepage = (string)row[1]; + } + else if (7 == (int)row[0]) // PID_TEMPLATE + { + this.transformSummaryInfo.UpdatedPlatformAndLanguage = (string)row[1]; + } + else if (14 == (int)row[0]) // PID_PAGECOUNT + { + this.transformSummaryInfo.UpdatedMinimumVersion = (string)row[1]; + } + } + } + } + + private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) + { + // calculate the minimum version of MSI required to process the transform + int targetMin; + int updatedMin; + int minimumVersion = 100; + + if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out updatedMin)) + { + minimumVersion = Math.Max(targetMin, updatedMin); + } + + Hashtable summaryRows = new Hashtable(summaryInfoTable.Rows.Count); + foreach (Row row in summaryInfoTable.Rows) + { + summaryRows[row[0]] = row; + + if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) + { + row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; + row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; + } + else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0]) + { + row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) + { + row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; + } + else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) + { + row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); + } + else if ((int)SummaryInformation.Transform.InstallerRequirement == (int)row[0]) + { + row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); + } + else if ((int)SummaryInformation.Transform.Security == (int)row[0]) + { + row[1] = "4"; + } + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) + { + Row summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; + summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) + { + Row summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; + summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) + { + Row summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; + summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.InstallerRequirement)) + { + Row summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; + summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); + } + + if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) + { + Row summaryRow = summaryInfoTable.CreateRow(null); + summaryRow[0] = (int)SummaryInformation.Transform.Security; + summaryRow[1] = "4"; + } + } + + private class SummaryInformationStreams + { + public string TargetSummaryInfoCodepage + { get; set; } + + public string TargetPlatformAndLanguage + { get; set; } + + public string TargetProductCode + { get; set; } + + public string TargetProductVersion + { get; set; } + + public string TargetUpgradeCode + { get; set; } + + public string TargetMinimumVersion + { get; set; } + + public string UpdatedSummaryInfoCodepage + { get; set; } + + public string UpdatedPlatformAndLanguage + { get; set; } + + public string UpdatedProductCode + { get; set; } + + public string UpdatedProductVersion + { get; set; } + + public string UpdatedMinimumVersion + { get; set; } + } + } +} + +#endif diff --git a/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs new file mode 100644 index 00000000..8305b5e6 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs @@ -0,0 +1,121 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class WindowsInstallerBackendHelper : IWindowsInstallerBackendHelper + { + private readonly IBackendHelper backendHelper; + + public WindowsInstallerBackendHelper(IServiceProvider serviceProvider) + { + this.backendHelper = serviceProvider.GetService(); + } + + #region IBackendHelper interfaces + + public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly); + + public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow); + + public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol); + + public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers); + + public string CreateGuid() => this.backendHelper.CreateGuid(); + + public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value); + + public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name); + + public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles); + + public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args); + + public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath); + + public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); + + public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName); + + public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable); + + public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version); + + public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id); + + public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); + + public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards); + + public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache); + + public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value); + + public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers); + + #endregion + + #region IWindowsInstallerBackendHelper interfaces + + public Row CreateRow(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinition tableDefinition) + { + var table = data.EnsureTable(tableDefinition); + + var row = table.CreateRow(symbol.SourceLineNumbers); + row.SectionId = section.Id; + + return row; + } + + public bool TryAddSymbolToMatchingTableDefinitions(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinitionCollection tableDefinitions) + { + var tableDefinition = tableDefinitions.FirstOrDefault(t => t.SymbolDefinition?.Name == symbol.Definition.Name); + if (tableDefinition == null) + { + return false; + } + + var row = this.CreateRow(section, symbol, data, tableDefinition); + var rowOffset = 0; + + if (tableDefinition.SymbolIdIsPrimaryKey) + { + row[0] = symbol.Id.Id; + rowOffset = 1; + } + + for (var i = 0; i < symbol.Fields.Length; ++i) + { + if (i < tableDefinition.Columns.Length) + { + var column = tableDefinition.Columns[i + rowOffset]; + + switch (column.Type) + { + case ColumnType.Number: + row[i + rowOffset] = column.Nullable ? symbol.AsNullableNumber(i) : symbol.AsNumber(i); + break; + + default: + row[i + rowOffset] = symbol.AsString(i); + break; + } + } + } + + return true; + } + + #endregion + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs new file mode 100644 index 00000000..57f2f753 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs @@ -0,0 +1,272 @@ +// 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.Inscribe +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Runtime.InteropServices; + using System.Security.Cryptography.X509Certificates; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class InscribeMsiPackageCommand + { + public InscribeMsiPackageCommand(IInscribeContext context) + { + this.Context = context; + this.Messaging = context.ServiceProvider.GetService(); + this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService(); + this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + } + + private IInscribeContext Context { get; } + + private IMessaging Messaging { get; } + + private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + public bool Execute() + { + // Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered + var foundUnsignedExternals = false; + var shouldCommit = false; + + var attributes = File.GetAttributes(this.Context.InputFilePath); + if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) + { + this.Messaging.Write(ErrorMessages.ReadOnlyOutputFile(this.Context.InputFilePath)); + return shouldCommit; + } + + using (var database = new Database(this.Context.InputFilePath, OpenDatabase.Transact)) + { + // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content + var codepage = 1252; + + // list of certificates for this database (hash/identifier) + var certificates = new Dictionary(); + + // Reset the in-memory tables for this new database + var digitalSignatureTable = new Table(this.TableDefinitions["MsiDigitalSignature"]); + var digitalCertificateTable = new Table(this.TableDefinitions["MsiDigitalCertificate"]); + + // If any digital signature records exist that are not of the media type, preserve them + if (database.TableExists("MsiDigitalSignature")) + { + using (var digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) + { + foreach (var digitalSignatureRecord in digitalSignatureView.Records) + { + Row digitalSignatureRow = null; + digitalSignatureRow = digitalSignatureTable.CreateRow(null); + + var table = digitalSignatureRecord.GetString(0); + var signObject = digitalSignatureRecord.GetString(1); + + digitalSignatureRow[0] = table; + digitalSignatureRow[1] = signObject; + digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); + + if (false == digitalSignatureRecord.IsNull(3)) + { + // Export to a file, because the MSI API's require us to provide a file path on disk + var hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature"); + var hashFileName = String.Concat(table, ".", signObject, ".bin"); + + Directory.CreateDirectory(hashPath); + hashPath = Path.Combine(hashPath, hashFileName); + + using (var fs = File.Create(hashPath)) + { + int bytesRead; + var buffer = new byte[1024 * 4]; + + while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) + { + fs.Write(buffer, 0, bytesRead); + } + } + + digitalSignatureRow[3] = hashFileName; + } + } + } + } + + // If any digital certificates exist, extract and preserve them + if (database.TableExists("MsiDigitalCertificate")) + { + using (var digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) + { + foreach (var digitalCertificateRecord in digitalCertificateView.Records) + { + var certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate + + // Export to a file, because the MSI API's require us to provide a file path on disk + var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); + Directory.CreateDirectory(certPath); + certPath = Path.Combine(certPath, String.Concat(certificateId, ".cer")); + + using (var fs = File.Create(certPath)) + { + int bytesRead; + var buffer = new byte[1024 * 4]; + + while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) + { + fs.Write(buffer, 0, bytesRead); + } + } + + // Add it to our "add to MsiDigitalCertificate" table dictionary + var digitalCertificateRow = digitalCertificateTable.CreateRow(null); + digitalCertificateRow[0] = certificateId; + + // Now set the file path on disk where this binary stream will be picked up at import time + digitalCertificateRow[1] = String.Concat(certificateId, ".cer"); + + // Load the cert to get it's thumbprint + var cert = X509Certificate.CreateFromCertFile(certPath); + var cert2 = new X509Certificate2(cert); + + certificates.Add(cert2.Thumbprint, certificateId); + } + } + } + + using (var mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) + { + foreach (var mediaRecord in mediaView.Records) + { + X509Certificate2 cert2 = null; + Row digitalSignatureRow = null; + + var cabName = mediaRecord.GetString(4); // get the name of the cab + // If there is no cabinet or it's an internal cab, skip it. + if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) + { + continue; + } + + var cabId = mediaRecord.GetString(1); // get the ID of the cab + var cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName); + + // If the cabs aren't there, throw an error but continue to catch the other errors + if (!File.Exists(cabPath)) + { + this.Messaging.Write(ErrorMessages.WixFileNotFound(cabPath)); + continue; + } + + try + { + // Get the certificate from the cab + var signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); + cert2 = new X509Certificate2(signedFileCert); + } + catch (System.Security.Cryptography.CryptographicException e) + { + var HResult = unchecked((uint)Marshal.GetHRForException(e)); + + // If the file has no cert, continue, but flag that we found at least one so we can later give a warning + if (0x80092009 == HResult) // CRYPT_E_NO_MATCH + { + foundUnsignedExternals = true; + continue; + } + + // todo: exactly which HRESULT corresponds to this issue? + // If it's one of these exact platforms, warn the user that it may be due to their OS. + if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 + (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP + { + this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); + } + else // otherwise, generic error + { + this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); + } + } + + // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added + if (!certificates.ContainsKey(cert2.Thumbprint)) + { + // generate a stable identifier + var certificateGeneratedId = this.WindowsInstallerBackendHelper.GenerateIdentifier("cer", cert2.Thumbprint); + + // Add it to our "add to MsiDigitalCertificate" table dictionary + var digitalCertificateRow = digitalCertificateTable.CreateRow(null); + digitalCertificateRow[0] = certificateGeneratedId; + + // Export to a file, because the MSI API's require us to provide a file path on disk + var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate"); + Directory.CreateDirectory(certPath); + certPath = Path.Combine(certPath, String.Concat(cert2.Thumbprint, ".cer")); + File.Delete(certPath); + + using (var writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) + { + writer.Write(cert2.RawData); + writer.Close(); + } + + // Now set the file path on disk where this binary stream will be picked up at import time + digitalCertificateRow[1] = String.Concat(cert2.Thumbprint, ".cer"); + + certificates.Add(cert2.Thumbprint, certificateGeneratedId); + } + + digitalSignatureRow = digitalSignatureTable.CreateRow(null); + + digitalSignatureRow[0] = "Media"; + digitalSignatureRow[1] = cabId; + digitalSignatureRow[2] = certificates[cert2.Thumbprint]; + } + } + + if (digitalCertificateTable.Rows.Count > 0) + { + var command = new CreateIdtFileCommand(this.Messaging, digitalCertificateTable, codepage, this.Context.IntermediateFolder, true); + command.Execute(); + + database.Import(command.IdtPath); + shouldCommit = true; + } + + if (digitalSignatureTable.Rows.Count > 0) + { + var command = new CreateIdtFileCommand(this.Messaging, digitalSignatureTable, codepage, this.Context.IntermediateFolder, true); + command.Execute(); + + database.Import(command.IdtPath); + shouldCommit = true; + } + + // TODO: if we created the table(s), then we should add the _Validation records for them. + + certificates = null; + + // If we did find external cabs but not all of them were signed, give a warning + if (foundUnsignedExternals) + { + this.Messaging.Write(WarningMessages.ExternalCabsAreNotSigned(this.Context.InputFilePath)); + } + + if (shouldCommit) + { + database.Commit(); + } + } + + return shouldCommit; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Melter.cs b/src/wix/WixToolset.Core.WindowsInstaller/Melter.cs new file mode 100644 index 00000000..29e19e49 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Melter.cs @@ -0,0 +1,399 @@ +// 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 +{ + using System; + using System.CodeDom.Compiler; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Globalization; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Data; + + /// + /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. + /// + public sealed class Melter + { +#if TODO_MELT + private MelterCore core; + private Decompiler decompiler; + + private Wix.ComponentGroup componentGroup; + private Wix.DirectoryRef primaryDirectoryRef; + private Wix.Fragment fragment; + + private string id; + private string moduleId; + private const string nullGuid = "{00000000-0000-0000-0000-000000000000}"; + + public string Id + { + get { return this.id; } + set { this.id = value; } + } + + public Decompiler Decompiler + { + get { return this.decompiler; } + set { this.decompiler = value; } + } + + /// + /// Creates a new melter object. + /// + /// The decompiler to use during the melting process. + /// The Id to use for the ComponentGroup, DirectoryRef, and WixVariables. If null, defaults to the Module's Id + public Melter(Decompiler decompiler, string id) + { + this.core = new MelterCore(); + + this.componentGroup = new Wix.ComponentGroup(); + this.fragment = new Wix.Fragment(); + this.primaryDirectoryRef = new Wix.DirectoryRef(); + + this.decompiler = decompiler; + this.id = id; + + if (null == this.decompiler) + { + this.core.OnMessage(WixErrors.ExpectedDecompiler("The melting process")); + } + } + + /// + /// Converts a Module wixout into a ComponentGroup. + /// + /// The output object representing the unbound merge module to melt. + /// The converted Module as a ComponentGroup. + public Wix.Wix Melt(Output wixout) + { + this.moduleId = GetModuleId(wixout); + + // Assign the default componentGroupId if none was specified + if (null == this.id) + { + this.id = this.moduleId; + } + + this.componentGroup.Id = this.id; + this.primaryDirectoryRef.Id = this.id; + + PreDecompile(wixout); + + wixout.Type = OutputType.Product; + this.decompiler.TreatProductAsModule = true; + Wix.Wix wix = this.decompiler.Decompile(wixout); + + if (null == wix) + { + return wix; + } + + ConvertModule(wix); + + return wix; + } + + /// + /// Converts a Module to a ComponentGroup and adds all of its relevant elements to the main fragment. + /// + /// The output object representing an unbound merge module. + private void ConvertModule(Wix.Wix wix) + { + Wix.Product product = Melter.GetProduct(wix); + + List customActionsRemoved = new List(); + Dictionary customsToRemove = new Dictionary(); + + foreach (Wix.ISchemaElement child in product.Children) + { + Wix.Directory childDir = child as Wix.Directory; + if (null != childDir) + { + bool isTargetDir = this.WalkDirectory(childDir); + if (isTargetDir) + { + continue; + } + } + else + { + Wix.Dependency childDep = child as Wix.Dependency; + if (null != childDep) + { + this.AddPropertyRef(childDep.RequiredId); + continue; + } + else if (child is Wix.Package) + { + continue; + } + else if (child is Wix.CustomAction) + { + Wix.CustomAction customAction = child as Wix.CustomAction; + string directoryId; + if (StartsWithStandardDirectoryId(customAction.Id, out directoryId) && customAction.Property == customAction.Id) + { + customActionsRemoved.Add(customAction.Id); + continue; + } + } + else if (child is Wix.InstallExecuteSequence) + { + Wix.InstallExecuteSequence installExecuteSequence = child as Wix.InstallExecuteSequence; + + foreach (Wix.ISchemaElement sequenceChild in installExecuteSequence.Children) + { + Wix.Custom custom = sequenceChild as Wix.Custom; + string directoryId; + if (custom != null && StartsWithStandardDirectoryId(custom.Action, out directoryId)) + { + customsToRemove.Add(custom, installExecuteSequence); + } + } + } + } + + this.fragment.AddChild(child); + } + + // For any customaction that we removed, also remove the scheduling of that action. + foreach (Wix.Custom custom in customsToRemove.Keys) + { + if (customActionsRemoved.Contains(custom.Action)) + { + ((Wix.InstallExecuteSequence)customsToRemove[custom]).RemoveChild(custom); + } + } + + AddProperty(this.moduleId, this.id); + + wix.RemoveChild(product); + wix.AddChild(this.fragment); + + this.fragment.AddChild(this.componentGroup); + this.fragment.AddChild(this.primaryDirectoryRef); + } + + /// + /// Gets the module from the Wix object. + /// + /// The Wix object. + /// The Module in the Wix object, null if no Module was found + private static Wix.Product GetProduct(Wix.Wix wix) + { + foreach (Wix.ISchemaElement element in wix.Children) + { + Wix.Product productElement = element as Wix.Product; + if (null != productElement) + { + return productElement; + } + } + return null; + } + + /// + /// Adds a PropertyRef to the main Fragment. + /// + /// Id of the PropertyRef. + private void AddPropertyRef(string propertyRefId) + { + Wix.PropertyRef propertyRef = new Wix.PropertyRef(); + propertyRef.Id = propertyRefId; + this.fragment.AddChild(propertyRef); + } + + /// + /// Adds a Property to the main Fragment. + /// + /// Id of the Property. + /// Value of the Property. + private void AddProperty(string propertyId, string value) + { + Wix.Property property = new Wix.Property(); + property.Id = propertyId; + property.Value = value; + this.fragment.AddChild(property); + } + + /// + /// Walks a directory structure obtaining Component Id's and Standard Directory Id's. + /// + /// The Directory to walk. + /// true if the directory is TARGETDIR. + private bool WalkDirectory(Wix.Directory directory) + { + bool isTargetDir = false; + if ("TARGETDIR" == directory.Id) + { + isTargetDir = true; + } + + string standardDirectoryId = null; + if (Melter.StartsWithStandardDirectoryId(directory.Id, out standardDirectoryId) && !isTargetDir) + { + this.AddSetPropertyCustomAction(directory.Id, String.Format(CultureInfo.InvariantCulture, "[{0}]", standardDirectoryId)); + } + + foreach (Wix.ISchemaElement child in directory.Children) + { + Wix.Directory childDir = child as Wix.Directory; + if (null != childDir) + { + if (isTargetDir) + { + this.primaryDirectoryRef.AddChild(child); + } + this.WalkDirectory(childDir); + } + else + { + Wix.Component childComponent = child as Wix.Component; + if (null != childComponent) + { + if (isTargetDir) + { + this.primaryDirectoryRef.AddChild(child); + } + this.AddComponentRef(childComponent); + } + } + } + + return isTargetDir; + } + + /// + /// Gets the module Id out of the Output object. + /// + /// The output object. + /// The module Id from the Output object. + private string GetModuleId(Output wixout) + { + // get the moduleId from the wixout + Table moduleSignatureTable = wixout.Tables["ModuleSignature"]; + if (null == moduleSignatureTable || 0 >= moduleSignatureTable.Rows.Count) + { + this.core.OnMessage(WixErrors.ExpectedTableInMergeModule("ModuleSignature")); + } + return moduleSignatureTable.Rows[0].Fields[0].Data.ToString(); + } + + /// + /// Determines if the directory Id starts with a standard directory id. + /// + /// The directory id. + /// The standard directory id. + /// true if the directory starts with a standard directory id. + private static bool StartsWithStandardDirectoryId(string directoryId, out string standardDirectoryId) + { + standardDirectoryId = null; + foreach (string id in WindowsInstallerStandard.GetStandardDirectories()) + { + if (directoryId.StartsWith(id, StringComparison.Ordinal)) + { + standardDirectoryId = id; + return true; + } + } + return false; + } + + /// + /// Adds a ComponentRef to the main ComponentGroup. + /// + /// The component to add. + private void AddComponentRef(Wix.Component component) + { + Wix.ComponentRef componentRef = new Wix.ComponentRef(); + componentRef.Id = component.Id; + this.componentGroup.AddChild(componentRef); + } + + /// + /// Adds a SetProperty CA for a Directory. + /// + /// The Id of the Property to set. + /// The value to set the Property to. + private void AddSetPropertyCustomAction(string propertyId, string value) + { + // Add the action + Wix.CustomAction customAction = new Wix.CustomAction(); + customAction.Id = propertyId; + customAction.Property = propertyId; + customAction.Value = value; + this.fragment.AddChild(customAction); + + // Schedule the action + Wix.InstallExecuteSequence installExecuteSequence = new Wix.InstallExecuteSequence(); + Wix.Custom custom = new Wix.Custom(); + custom.Action = customAction.Id; + custom.Before = "CostInitialize"; + installExecuteSequence.AddChild(custom); + this.fragment.AddChild(installExecuteSequence); + } + + /// + /// Does any operations to the wixout that would need to be done before decompiling. + /// + /// The output object representing the unbound merge module. + private void PreDecompile(Output wixout) + { + string wixVariable = String.Format(CultureInfo.InvariantCulture, "!(wix.{0}", this.id); + + foreach (Table table in wixout.Tables) + { + // Determine if the table has a feature foreign key + bool hasFeatureForeignKey = false; + foreach (ColumnDefinition columnDef in table.Definition.Columns) + { + if (null != columnDef.KeyTable) + { + string[] keyTables = columnDef.KeyTable.Split(';'); + foreach (string keyTable in keyTables) + { + if ("Feature" == keyTable) + { + hasFeatureForeignKey = true; + break; + } + } + } + } + + // If this table has no foreign keys to the feature table, skip it. + if (!hasFeatureForeignKey) + { + continue; + } + + // Go through all the rows and replace the null guid with the wix variable + // for columns that are foreign keys into the feature table. + foreach (Row row in table.Rows) + { + foreach (Field field in row.Fields) + { + if (null != field.Column.KeyTable) + { + string[] keyTables = field.Column.KeyTable.Split(';'); + foreach (string keyTable in keyTables) + { + if ("Feature" == keyTable) + { + field.Data = field.Data.ToString().Replace(nullGuid, wixVariable); + break; + } + } + } + } + } + } + } +#endif + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs b/src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs new file mode 100644 index 00000000..034c9465 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs @@ -0,0 +1,32 @@ +// 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 +{ + using WixToolset.Data; + + /// + /// Melts a Module Wix document into a ComponentGroup representation. + /// + public sealed class MelterCore + { +#if TODO_MELT + /// + /// Gets whether the melter core encountered an error while processing. + /// + /// Flag if core encountered an error during processing. + public bool EncounteredError + { + get { return Messaging.Instance.EncounteredError; } + } + + /// + /// Sends a message to the message delegate if there is one. + /// + /// Message event arguments. + public void OnMessage(MessageEventArgs e) + { + Messaging.Instance.OnMessage(e); + } +#endif + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs new file mode 100644 index 00000000..3bd58c25 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs @@ -0,0 +1,85 @@ +// 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 +{ + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Core.WindowsInstaller.Decompile; + using WixToolset.Core.WindowsInstaller.Inscribe; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class MsiBackend : IBackend + { + public IBindResult Bind(IBindContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + + IBindResult result = null; + var dispose = true; + try + { + var command = new BindDatabaseCommand(context, backendExtensions, "darice.cub"); + result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } + + dispose = false; + return result; + } + finally + { + if (dispose) + { + result?.Dispose(); + } + } + } + + public IDecompileResult Decompile(IDecompileContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendDecompile(context); + } + + var command = new DecompileMsiOrMsmCommand(context, backendExtensions); + var result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendDecompile(result); + } + + return result; + } + + public bool Inscribe(IInscribeContext context) + { + var command = new InscribeMsiPackageCommand(context); + return command.Execute(); + } + + public Intermediate Unbind(IUnbindContext context) + { + var command = new UnbindMsiOrMsmCommand(context); + return command.Execute(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs new file mode 100644 index 00000000..4927ee8c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs @@ -0,0 +1,76 @@ +// 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 +{ + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Core.WindowsInstaller.Decompile; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class MsmBackend : IBackend + { + public IBindResult Bind(IBindContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + + IBindResult result = null; + try + { + var command = new BindDatabaseCommand(context, backendExtensions, "mergemod.cub"); + result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } + + return result; + } + catch + { + result?.Dispose(); + throw; + } + } + + public IDecompileResult Decompile(IDecompileContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendDecompile(context); + } + + var command = new DecompileMsiOrMsmCommand(context, backendExtensions); + var result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendDecompile(result); + } + + return result; + } + + public bool Inscribe(IInscribeContext context) => false; + + public Intermediate Unbind(IUnbindContext context) + { + var command = new UnbindMsiOrMsmCommand(context); + return command.Execute(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs new file mode 100644 index 00000000..c46b6027 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs @@ -0,0 +1,162 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class MspBackend : IBackend + { + public IBindResult Bind(IBindContext context) + { + var messaging = context.ServiceProvider.GetService(); + + var backendHelper = context.ServiceProvider.GetService(); + + var extensionManager = context.ServiceProvider.GetService(); + + var backendExtensions = extensionManager.GetServices(); + + foreach (var extension in backendExtensions) + { + extension.PreBackendBind(context); + } + + // Create transforms named in patch transforms. + IEnumerable patchTransforms; + { + var command = new CreatePatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, context.IntermediateFolder); + patchTransforms = command.Execute(); + } + + // Enhance the intermediate by attaching the created patch transforms. + IEnumerable subStorages; + { + var command = new AttachPatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, patchTransforms); + subStorages = command.Execute(); + } + + // Create WindowsInstallerData with patch metdata and transforms as sub-storages + // Create MSP from WindowsInstallerData + IBindResult result = null; + try + { + var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null); + result = command.Execute(); + + foreach (var extension in backendExtensions) + { + extension.PostBackendBind(result); + } + + return result; + } + catch + { + result?.Dispose(); + throw; + } + } + + public IDecompileResult Decompile(IDecompileContext context) => throw new NotImplementedException(); + + public bool Inscribe(IInscribeContext context) => throw new NotImplementedException(); + + public Intermediate Unbind(IUnbindContext context) + { +#if TODO_PATCHING + Output patch; + + // patch files are essentially database files (use a special flag to let the API know its a patch file) + try + { + using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) + { + var unbindCommand = new UnbindDatabaseCommand(context.Messaging, database, context.InputFilePath, OutputType.Patch, context.ExportBasePath, context.IntermediateFolder, context.IsAdminImage, context.SuppressDemodularization, skipSummaryInfo: false); + patch = unbindCommand.Execute(); + } + } + catch (Win32Exception e) + { + if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED + { + throw new WixException(WixErrors.OpenDatabaseFailed(context.InputFilePath)); + } + + throw; + } + + // retrieve the transforms (they are in substorages) + using (Storage storage = Storage.Open(context.InputFilePath, StorageMode.Read | StorageMode.ShareDenyWrite)) + { + Table summaryInformationTable = patch.Tables["_SummaryInformation"]; + foreach (Row row in summaryInformationTable.Rows) + { + if (8 == (int)row[0]) // PID_LASTAUTHOR + { + string value = (string)row[1]; + + foreach (string decoratedSubStorageName in value.Split(';')) + { + string subStorageName = decoratedSubStorageName.Substring(1); + string transformFile = Path.Combine(context.IntermediateFolder, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst")); + + // ensure the parent directory exists + Directory.CreateDirectory(Path.GetDirectoryName(transformFile)); + + // copy the substorage to a new storage for the transform file + using (Storage subStorage = storage.OpenStorage(subStorageName)) + { + using (Storage transformStorage = Storage.CreateDocFile(transformFile, StorageMode.ReadWrite | StorageMode.ShareExclusive | StorageMode.Create)) + { + subStorage.CopyTo(transformStorage); + } + } + + // unbind the transform + var unbindCommand= new UnbindTransformCommand(context.Messaging, transformFile, (null == context.ExportBasePath ? null : Path.Combine(context.ExportBasePath, subStorageName)), context.IntermediateFolder); + var transform = unbindCommand.Execute(); + + patch.SubStorages.Add(new SubStorage(subStorageName, transform)); + } + + break; + } + } + } + + // extract the files from the cabinets + // TODO: use per-transform export paths for support of multi-product patches + if (null != context.ExportBasePath && !context.SuppressExtractCabinets) + { + using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) + { + foreach (SubStorage subStorage in patch.SubStorages) + { + // only patch transforms should carry files + if (subStorage.Name.StartsWith("#", StringComparison.Ordinal)) + { + var extractCommand = new ExtractCabinetsCommand(subStorage.Data, database, context.InputFilePath, context.ExportBasePath, context.IntermediateFolder); + extractCommand.Execute(); + } + } + } + } + + return patch; +#endif + throw new NotImplementedException(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs new file mode 100644 index 00000000..a6d86c10 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs @@ -0,0 +1,44 @@ +// 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 +{ + using System; + using WixToolset.Core.WindowsInstaller.Unbind; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class MstBackend : IBackend + { + public IBindResult Bind(IBindContext context) + { +#if TODO_PATCHING + var command = new BindTransformCommand(); + command.Extensions = context.Extensions; + command.TempFilesLocation = context.IntermediateFolder; + command.Transform = context.IntermediateRepresentation; + command.OutputPath = context.OutputPath; + command.Execute(); + + return new BindResult(Array.Empty(), Array.Empty()); +#endif + throw new NotImplementedException(); + } + + public IDecompileResult Decompile(IDecompileContext context) + { + throw new NotImplementedException(); + } + + public bool Inscribe(IInscribeContext context) + { + throw new NotImplementedException(); + } + + public Intermediate Unbind(IUnbindContext context) + { + var command = new UnbindMsiOrMsmCommand(context); + return command.Execute(); + } + } +} \ No newline at end of file diff --git a/src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs b/src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs new file mode 100644 index 00000000..ad7764bc --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs @@ -0,0 +1,71 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using WixToolset.Data.WindowsInstaller; + + /// + /// A dictionary of rows. Unlike the RowIndexedList this + /// will throw when multiple rows with the same key are added. + /// + internal sealed class RowDictionary : Dictionary where T : Row + { + /// + /// Creates an empty . + /// + public RowDictionary() + : base(StringComparer.InvariantCulture) + { + } + + /// + /// Creates and populates a with the rows from the given . + /// + /// The table to index. + /// + /// Rows added to the index are not automatically added to the given . + /// + public RowDictionary(Table table) + : this() + { + if (null != table) + { + foreach (T row in table.Rows) + { + this.Add(row); + } + } + } + + /// + /// Adds a row to the dictionary using the row key. + /// + /// Row to add to the dictionary. + public void Add(T row) + { + this.Add(row.GetKey(), row); + } + + /// + /// Gets the row by integer key. + /// + /// Integer key to look up. + /// Row or null if key is not found. + public T Get(int key) + { + return this.Get(key.ToString()); + } + + /// + /// Gets the row by string key. + /// + /// String key to look up. + /// Row or null if key is not found. + public T Get(string key) + { + return this.TryGetValue(key, out var result) ? result : null; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs new file mode 100644 index 00000000..8f52bed9 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs @@ -0,0 +1,147 @@ +// 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.Unbind +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Core.Native; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + + internal class ExtractCabinetsCommand + { + public ExtractCabinetsCommand(WindowsInstallerData output, Database database, string inputFilePath, string exportBasePath, string intermediateFolder, bool treatOutputAsModule = false) + { + this.Output = output; + this.Database = database; + this.InputFilePath = inputFilePath; + this.ExportBasePath = exportBasePath; + this.IntermediateFolder = intermediateFolder; + this.TreatOutputAsModule = treatOutputAsModule; + } + + public string[] ExtractedFiles { get; private set; } + + private WindowsInstallerData Output { get; } + + private Database Database { get; } + + private string InputFilePath { get; } + + private string ExportBasePath { get; } + + private string IntermediateFolder { get; } + + public bool TreatOutputAsModule { get; } + + public void Execute() + { + var databaseBasePath = Path.GetDirectoryName(this.InputFilePath); + var cabinetFiles = new List(); + var embeddedCabinets = new SortedList(); + + // index all of the cabinet files + if (OutputType.Module == this.Output.Type || this.TreatOutputAsModule) + { + embeddedCabinets.Add(0, "MergeModule.CABinet"); + } + else if (null != this.Output.Tables["Media"]) + { + foreach (MediaRow mediaRow in this.Output.Tables["Media"].Rows) + { + if (null != mediaRow.Cabinet) + { + if (OutputType.Product == this.Output.Type || + (OutputType.Transform == this.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 (var streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) + { + foreach (int diskId in embeddedCabinets.Keys) + { + using (var record = new Record(1)) + { + record.SetString(1, (string)embeddedCabinets[diskId]); + streamsView.Execute(record); + } + + using (var record = streamsView.Fetch()) + { + if (null != record) + { + // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive, + // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work + var cabinetFile = Path.Combine(this.IntermediateFolder, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); + + // ensure the parent directory exists + Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); + + using (var fs = File.Create(cabinetFile)) + { + int bytesRead; + var 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) + { + // ensure the directory exists or extraction will fail + Directory.CreateDirectory(this.ExportBasePath); + + foreach (var cabinetFile in cabinetFiles) + { + try + { + var cabinet = new Cabinet(cabinetFile); + this.ExtractedFiles = cabinet.Extract(this.ExportBasePath).ToArray(); + } + catch (FileNotFoundException) + { + throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetFile)); + } + } + } + else + { + this.ExtractedFiles = new string[0]; + } + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs new file mode 100644 index 00000000..b510690e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs @@ -0,0 +1,789 @@ +// 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.Unbind +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Text.RegularExpressions; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class UnbindDatabaseCommand + { + private List exportedFiles; + + public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, Database database, string databasePath, OutputType outputType, string exportBasePath, string intermediateFolder, bool isAdminImage, bool suppressDemodularization, bool skipSummaryInfo) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.Database = database; + this.DatabasePath = databasePath; + this.OutputType = outputType; + this.ExportBasePath = exportBasePath; + this.IntermediateFolder = intermediateFolder; + this.IsAdminImage = isAdminImage; + this.SuppressDemodularization = suppressDemodularization; + this.SkipSummaryInfo = skipSummaryInfo; + + this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + } + + public IMessaging Messaging { get; } + + public IBackendHelper BackendHelper { get; } + + public Database Database { get; } + + public string DatabasePath { get; } + + public OutputType OutputType { get; } + + public string ExportBasePath { get; } + + public string IntermediateFolder { get; } + + public bool IsAdminImage { get; } + + public bool SuppressDemodularization { get; } + + public bool SkipSummaryInfo { get; } + + public TableDefinitionCollection TableDefinitions { get; } + + public IEnumerable ExportedFiles => this.exportedFiles; + + private int SectionCount { get; set; } + + public WindowsInstallerData Execute() + { + this.exportedFiles = new List(); + + string modularizationGuid = null; + var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath)); + View validationView = null; + + // set the output type + output.Type = this.OutputType; + + Directory.CreateDirectory(this.IntermediateFolder); + + // get the codepage + this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt"); + using (var sr = File.OpenText(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt"))) + { + string line; + + while (null != (line = sr.ReadLine())) + { + var 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 (!this.SkipSummaryInfo) + { + using (var summaryInformation = new SummaryInformation(this.Database)) + { + var table = new Table(this.TableDefinitions["_SummaryInformation"]); + + for (var i = 1; 19 >= i; i++) + { + var value = summaryInformation.GetProperty(i); + + if (0 < value.Length) + { + var 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 (this.Database.TableExists("_Validation")) + { + validationView = this.Database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); + } + + // get the normal tables + using (var tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables")) + { + foreach (var tableRecord in tablesView.Records) + { + var tableName = tableRecord.GetString(1); + + using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) + { + var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView); + var table = new Table(tableDefinition); + + foreach (var rowRecord in tableView.Records) + { + var recordCount = rowRecord.GetFieldCount(); + var row = table.CreateRow(output.SourceLineNumbers); + + for (var i = 0; recordCount > i && row.Fields.Length > i; i++) + { + if (rowRecord.IsNull(i + 1)) + { + if (!row.Fields[i].Column.Nullable) + { + // 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: + var success = false; + var 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.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); + } + break; + case ColumnType.Object: + var sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; + + if (null != this.ExportBasePath) + { + var relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); + sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile); + + // ensure the parent directory exists + System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName)); + + using (var fs = System.IO.File.Create(sourceFile)) + { + int bytesRead; + var buffer = new byte[512]; + + while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) + { + fs.Write(buffer, 0, bytesRead); + } + } + + this.exportedFiles.Add(sourceFile); + } + + row[i] = sourceFile; + break; + default: + var 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) + { + var 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) + { + var 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 + // TODO: Evaluate this requirement. + //var matches = Common.WixVariableRegex.Matches(value); + //for (var 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) + { + var table = output.Tables["_SummaryInformation"]; + + foreach (var row in table.Rows) + { + if (9 == (int)row[0]) // PID_REVNUMBER + { + row[1] = modularizationGuid; + } + } + } + + if (this.IsAdminImage) + { + this.GenerateWixFileTable(this.DatabasePath, output); + this.GenerateSectionIds(output); + } + + return output; + } + + private TableDefinition GetTableDefinition(string tableName, View tableView, View validationView) + { + // Use our table definitions whenever possible since they will be used when compiling the source code anyway. + // This also allows us to take advantage of WiX concepts like localizable columns which current code assumes. + if (this.TableDefinitions.Contains(tableName)) + { + return this.TableDefinitions[tableName]; + } + + ColumnDefinition[] columns; + using (Record columnNameRecord = tableView.GetColumnNames(), + columnTypeRecord = tableView.GetColumnTypes()) + { + // index the primary keys + var tablePrimaryKeys = new HashSet(); + using (var primaryKeysRecord = this.Database.PrimaryKeys(tableName)) + { + var primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); + + for (var i = 1; i <= primaryKeysFieldCount; i++) + { + tablePrimaryKeys.Add(primaryKeysRecord.GetString(i)); + } + } + + var columnCount = columnNameRecord.GetFieldCount(); + columns = new ColumnDefinition[columnCount]; + for (var i = 1; i <= columnCount; i++) + { + var columnName = columnNameRecord.GetString(i); + var idtType = columnTypeRecord.GetString(i); + + ColumnType columnType; + int length; + bool nullable; + + var columnCategory = ColumnCategory.Unknown; + var columnModularizeType = ColumnModularizeType.None; + var primary = tablePrimaryKeys.Contains(columnName); + int? minValue = null; + int? maxValue = null; + string keyTable = null; + int? keyColumn = null; + 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 (var validationRecord = new Record(2)) + { + validationRecord.SetString(1, tableName); + validationRecord.SetString(2, columnName); + + validationView.Execute(validationRecord); + } + + using (var validationRecord = validationView.Fetch()) + { + if (null != validationRecord) + { + var validationNullable = validationRecord.GetString(3); + minValue = validationRecord.IsNull(4) ? null : (int?)validationRecord.GetInteger(4); + maxValue = validationRecord.IsNull(5) ? null : (int?)validationRecord.GetInteger(5); + keyTable = validationRecord.IsNull(6) ? null : validationRecord.GetString(6); + keyColumn = validationRecord.IsNull(7) ? null : (int?)validationRecord.GetInteger(7); + category = validationRecord.IsNull(8) ? null : validationRecord.GetString(8); + set = validationRecord.IsNull(9) ? null : validationRecord.GetString(9); + description = validationRecord.IsNull(10) ? null : validationRecord.GetString(10); + + // 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; + } + + columns[i - 1] = new ColumnDefinition(columnName, columnType, length, primary, nullable, columnCategory, minValue, maxValue, keyTable, keyColumn, set, description, columnModularizeType, (ColumnType.Localized == columnType), true); + } + } + + return new TableDefinition(tableName, null, columns, false); + } + + /// + /// Generates the WixFile table based on a path to an admin image msi and an Output. + /// + /// The path to the msi database file in an admin image. + /// The Output that represents the msi database. + private void GenerateWixFileTable(string databaseFile, WindowsInstallerData output) + { + throw new NotImplementedException(); +#if TODO_FIX_UNBINDING_FILES + var adminRootPath = Path.GetDirectoryName(databaseFile); + + var componentDirectoryIndex = new Hashtable(); + var componentTable = output.Tables["Component"]; + foreach (var row in componentTable.Rows) + { + componentDirectoryIndex.Add(row[0], row[2]); + } + + // Index full source paths for all directories + var directoryDirectoryParentIndex = new Hashtable(); + var directoryFullPathIndex = new Hashtable(); + var directorySourceNameIndex = new Hashtable(); + var directoryTable = output.Tables["Directory"]; + foreach (var row in directoryTable.Rows) + { + directoryDirectoryParentIndex.Add(row[0], row[1]); + if (null == row[1]) + { + directoryFullPathIndex.Add(row[0], adminRootPath); + } + else + { + directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2])); + } + } + + foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) + { + if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) + { + this.GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); + } + } + + var fileTable = output.Tables["File"]; + var wixFileTable = output.EnsureTable(this.TableDefinitions["WixFile"]); + foreach (var row in fileTable.Rows) + { + var wixFileRow = new WixFileRow(null, this.TableDefinitions["WixFile"]); + wixFileRow.File = (string)row[0]; + wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]]; + wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2])); + + if (!File.Exists(wixFileRow.Source)) + { + throw new WixException(ErrorMessages.WixFileNotFound(wixFileRow.Source)); + } + + wixFileTable.Rows.Add(wixFileRow); + } +#endif + } + + /// + /// Gets the full path of a directory. Populates the full path index with the directory's full path and all of its parent directorie's full paths. + /// + /// The directory identifier. + /// The Hashtable containing all the directory to directory parent mapping. + /// The Hashtable containing all the directory to source name mapping. + /// The Hashtable containing a mapping between all of the directories and their previously calculated full paths. + /// The full path to the directory. + private string GetAdminFullPath(string directory, Hashtable directoryDirectoryParentIndex, Hashtable directorySourceNameIndex, Hashtable directoryFullPathIndex) + { + var parent = (string)directoryDirectoryParentIndex[directory]; + var sourceName = (string)directorySourceNameIndex[directory]; + + string parentFullPath; + if (directoryFullPathIndex.ContainsKey(parent)) + { + parentFullPath = (string)directoryFullPathIndex[parent]; + } + else + { + parentFullPath = this.GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); + } + + if (null == sourceName) + { + sourceName = String.Empty; + } + + var fullPath = Path.Combine(parentFullPath, sourceName); + directoryFullPathIndex.Add(directory, fullPath); + + return fullPath; + } + + /// + /// Get the source name in an admin image. + /// + /// The Filename value. + /// The source name of the directory in an admin image. + private string GetAdminSourceName(string value) + { + string name = null; + string[] names; + string shortname = null; + string shortsourcename = null; + string sourcename = null; + + names = this.BackendHelper.SplitMsiFileName(value); + + if (null != names[0] && "." != names[0]) + { + if (null != names[1]) + { + shortname = names[0]; + } + else + { + name = names[0]; + } + } + + if (null != names[1]) + { + name = names[1]; + } + + if (null != names[2]) + { + if (null != names[3]) + { + shortsourcename = names[2]; + } + else + { + sourcename = names[2]; + } + } + + if (null != names[3]) + { + sourcename = names[3]; + } + + if (null != sourcename) + { + return sourcename; + } + else if (null != shortsourcename) + { + return shortsourcename; + } + else if (null != name) + { + return name; + } + else + { + return shortname; + } + } + + /// + /// Creates section ids on rows which form logical groupings of resources. + /// + /// The Output that represents the msi database. + private void GenerateSectionIds(WindowsInstallerData output) + { + // First assign and index section ids for the tables that are in their own sections. + this.AssignSectionIdsToTable(output.Tables["Binary"], 0); + var componentSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Component"], 0); + var customActionSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["CustomAction"], 0); + this.AssignSectionIdsToTable(output.Tables["Directory"], 0); + var featureSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Feature"], 0); + this.AssignSectionIdsToTable(output.Tables["Icon"], 0); + var digitalCertificateSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0); + this.AssignSectionIdsToTable(output.Tables["Property"], 0); + + // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here. + var fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0); + var appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5); + var odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0); + var odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0); + var registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0); + var serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0); + + // Now handle all the tables which only rely on previous indexes and order does not matter. + foreach (var table in output.Tables) + { + switch (table.Name) + { + case "WixFile": + case "MsiFileHash": + ConnectTableToSection(table, fileSectionIdIndex, 0); + break; + case "MsiAssembly": + case "MsiAssemblyName": + ConnectTableToSection(table, componentSectionIdIndex, 0); + break; + case "MsiPackageCertificate": + case "MsiPatchCertificate": + ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1); + break; + case "CreateFolder": + case "FeatureComponents": + case "MoveFile": + case "ReserveCost": + case "ODBCTranslator": + ConnectTableToSection(table, componentSectionIdIndex, 1); + break; + case "TypeLib": + ConnectTableToSection(table, componentSectionIdIndex, 2); + break; + case "Shortcut": + case "Environment": + ConnectTableToSection(table, componentSectionIdIndex, 3); + break; + case "RemoveRegistry": + ConnectTableToSection(table, componentSectionIdIndex, 4); + break; + case "ServiceControl": + ConnectTableToSection(table, componentSectionIdIndex, 5); + break; + case "IniFile": + case "RemoveIniFile": + ConnectTableToSection(table, componentSectionIdIndex, 7); + break; + case "AppId": + ConnectTableToSection(table, appIdSectionIdIndex, 0); + break; + case "Condition": + ConnectTableToSection(table, featureSectionIdIndex, 0); + break; + case "ODBCSourceAttribute": + ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0); + break; + case "ODBCAttribute": + ConnectTableToSection(table, odbcDriverSectionIdIndex, 0); + break; + case "AdminExecuteSequence": + case "AdminUISequence": + case "AdvtExecuteSequence": + case "AdvtUISequence": + case "InstallExecuteSequence": + case "InstallUISequence": + ConnectTableToSection(table, customActionSectionIdIndex, 0); + break; + case "LockPermissions": + case "MsiLockPermissions": + foreach (var row in table.Rows) + { + var lockObject = (string)row[0]; + var tableName = (string)row[1]; + switch (tableName) + { + case "File": + row.SectionId = (string)fileSectionIdIndex[lockObject]; + break; + case "Registry": + row.SectionId = (string)registrySectionIdIndex[lockObject]; + break; + case "ServiceInstall": + row.SectionId = (string)serviceInstallSectionIdIndex[lockObject]; + break; + } + } + break; + } + } + + // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids. + //foreach (IUnbinderExtension extension in this.unbinderExtensions) + //{ + // extension.GenerateSectionIds(output); + //} + } + + /// + /// Creates new section ids on all the rows in a table. + /// + /// The table to add sections to. + /// The index of the column which is used by other tables to reference this table. + /// A Hashtable containing the tables key for each row paired with its assigned section id. + private Hashtable AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex) + { + var hashtable = new Hashtable(); + if (null != table) + { + foreach (var row in table.Rows) + { + row.SectionId = this.GetNewSectionId(); + hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId); + } + } + return hashtable; + } + + /// + /// Connects a table's rows to an already sectioned table. + /// + /// The table containing rows that need to be connected to sections. + /// A hashtable containing keys to map table to its section. + /// The index of the column which is used as the foreign key in to the sectionIdIndex. + private static void ConnectTableToSection(Table table, Hashtable sectionIdIndex, int rowIndex) + { + if (null != table) + { + foreach (var row in table.Rows) + { + if (sectionIdIndex.ContainsKey(row[rowIndex])) + { + row.SectionId = (string)sectionIdIndex[row[rowIndex]]; + } + } + } + } + + /// + /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it. + /// + /// The table containing rows that need to be connected to sections. + /// A hashtable containing keys to map table to its section. + /// The index of the column which is used as the foreign key in to the sectionIdIndex. + /// The index of the column which is used by other tables to reference this table. + /// A Hashtable containing the tables key for each row paired with its assigned section id. + private static Hashtable ConnectTableToSectionAndIndex(Table table, Hashtable sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex) + { + var newHashTable = new Hashtable(); + if (null != table) + { + foreach (var row in table.Rows) + { + if (!sectionIdIndex.ContainsKey(row[rowIndex])) + { + continue; + } + + row.SectionId = (string)sectionIdIndex[row[rowIndex]]; + if (null != row[rowPrimaryKeyIndex]) + { + newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId); + } + } + } + return newHashTable; + } + + /// + /// Creates a new section identifier to be used when adding a section to an output. + /// + /// A string representing a new section id. + private string GetNewSectionId() + { + this.SectionCount++; + return "wix.section." + this.SectionCount.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs new file mode 100644 index 00000000..75ee6307 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs @@ -0,0 +1,55 @@ +// 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.Unbind +{ + using System; + using System.ComponentModel; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Core.Native.Msi; + + internal class UnbindMsiOrMsmCommand + { + public UnbindMsiOrMsmCommand(IUnbindContext context) + { + this.Context = context; + } + + public IUnbindContext Context { get; } + + public Intermediate Execute() + { +#if TODO_PATCHING + Output output; + + try + { + using (Database database = new Database(this.Context.InputFilePath, OpenDatabase.ReadOnly)) + { + var unbindCommand = new UnbindDatabaseCommand(this.Context.Messaging, database, this.Context.InputFilePath, OutputType.Product, this.Context.ExportBasePath, this.Context.IntermediateFolder, this.Context.IsAdminImage, this.Context.SuppressDemodularization, skipSummaryInfo: false); + output = unbindCommand.Execute(); + + // extract the files from the cabinets + if (!String.IsNullOrEmpty(this.Context.ExportBasePath) && !this.Context.SuppressExtractCabinets) + { + var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.InputFilePath, this.Context.ExportBasePath, this.Context.IntermediateFolder); + extractCommand.Execute(); + } + } + } + catch (Win32Exception e) + { + if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED + { + throw new WixException(WixErrors.OpenDatabaseFailed(this.Context.InputFilePath)); + } + + throw; + } + + return output; +#endif + throw new NotImplementedException(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs new file mode 100644 index 00000000..f40aed4e --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs @@ -0,0 +1,309 @@ +// 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.Unbind +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.ComponentModel; + using System.Globalization; + using System.IO; + using System.Linq; + using WixToolset.Core.Native.Msi; + using WixToolset.Core.WindowsInstaller.Bind; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class UnbindTransformCommand + { + public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, string transformFile, string exportBasePath, string intermediateFolder) + { + this.Messaging = messaging; + this.BackendHelper = backendHelper; + this.TransformFile = transformFile; + this.ExportBasePath = exportBasePath; + this.IntermediateFolder = intermediateFolder; + + this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private string TransformFile { get; } + + private string ExportBasePath { get; } + + private string IntermediateFolder { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private string EmptyFile { get; set; } + + public WindowsInstallerData Execute() + { + var transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile)); + transform.Type = OutputType.Transform; + + // get the summary information table + using (var summaryInformation = new SummaryInformation(this.TransformFile)) + { + var table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]); + + for (var i = 1; 19 >= i; i++) + { + var value = summaryInformation.GetProperty(i); + + if (0 < value.Length) + { + var row = table.CreateRow(transform.SourceLineNumbers); + row[0] = i; + row[1] = value; + } + } + } + + // create a schema msi which hopefully matches the table schemas in the transform + var schemaOutput = new WindowsInstallerData(null); + var msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi"); + foreach (var tableDefinition in this.TableDefinitions) + { + // skip unreal tables and the Patch table + if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name) + { + schemaOutput.EnsureTable(tableDefinition); + } + } + + var addedRows = new Dictionary(); + Table transformViewTable; + + // Bind the schema msi. + this.GenerateDatabase(schemaOutput, msiDatabaseFile); + + // apply the transform to the database and retrieve the modifications + using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) + { + // apply the transform with the ViewTransform option to collect all the modifications + msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); + + // unbind the database + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); + var transformViewOutput = unbindCommand.Execute(); + + // index the added and possibly modified rows (added rows may also appears as modified rows) + transformViewTable = transformViewOutput.Tables["_TransformView"]; + var modifiedRows = new Hashtable(); + foreach (var row in transformViewTable.Rows) + { + var tableName = (string)row[0]; + var columnName = (string)row[1]; + var primaryKeys = (string)row[2]; + + if ("INSERT" == columnName) + { + var index = String.Concat(tableName, ':', primaryKeys); + + addedRows.Add(index, null); + } + else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row + { + var index = String.Concat(tableName, ':', primaryKeys); + + modifiedRows[index] = row; + } + } + + // create placeholder rows for modified rows to make the transform insert the updated values when its applied + foreach (Row row in modifiedRows.Values) + { + var tableName = (string)row[0]; + var columnName = (string)row[1]; + var primaryKeys = (string)row[2]; + + var index = String.Concat(tableName, ':', primaryKeys); + + // ignore information for added rows + if (!addedRows.ContainsKey(index)) + { + var table = schemaOutput.Tables[tableName]; + this.CreateRow(table, primaryKeys, true); + } + } + } + + // Re-bind the schema output with the placeholder rows. + this.GenerateDatabase(schemaOutput, msiDatabaseFile); + + // apply the transform to the database and retrieve the modifications + using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) + { + try + { + // apply the transform + msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All); + + // commit the database to guard against weird errors with streams + msiDatabase.Commit(); + } + catch (Win32Exception ex) + { + if (0x65B == ex.NativeErrorCode) + { + // this commonly happens when the transform was built + // against a database schema different from the internal + // table definitions + throw new WixException(ErrorMessages.TransformSchemaMismatch()); + } + } + + // unbind the database + var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true); + var output = unbindCommand.Execute(); + + // index all the rows to easily find modified rows + var rows = new Dictionary(); + foreach (var table in output.Tables) + { + foreach (var row in table.Rows) + { + rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); + } + } + + // process the _TransformView rows into transform rows + foreach (var row in transformViewTable.Rows) + { + var tableName = (string)row[0]; + var columnName = (string)row[1]; + var primaryKeys = (string)row[2]; + + var table = transform.EnsureTable(this.TableDefinitions[tableName]); + + if ("CREATE" == columnName) // added table + { + table.Operation = TableOperation.Add; + } + else if ("DELETE" == columnName) // deleted row + { + var deletedRow = this.CreateRow(table, primaryKeys, false); + deletedRow.Operation = RowOperation.Delete; + } + else if ("DROP" == columnName) // dropped table + { + table.Operation = TableOperation.Drop; + } + else if ("INSERT" == columnName) // added row + { + var index = String.Concat(tableName, ':', primaryKeys); + var addedRow = rows[index]; + addedRow.Operation = RowOperation.Add; + table.Rows.Add(addedRow); + } + else if (null != primaryKeys) // modified row + { + var index = String.Concat(tableName, ':', primaryKeys); + + // the _TransformView table includes information for added rows + // that looks like modified rows so it sometimes needs to be ignored + if (!addedRows.ContainsKey(index)) + { + var modifiedRow = rows[index]; + + // mark the field as modified + var indexOfModifiedValue = -1; + for (var i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++i) + { + if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal)) + { + indexOfModifiedValue = i; + break; + } + } + modifiedRow.Fields[indexOfModifiedValue].Modified = true; + + // move the modified row into the transform the first time its encountered + if (RowOperation.None == modifiedRow.Operation) + { + modifiedRow.Operation = RowOperation.Modify; + table.Rows.Add(modifiedRow); + } + } + } + else // added column + { + var column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal)); + column.Added = true; + } + } + } + + return transform; + } + + /// + /// Create a deleted or modified row. + /// + /// The table containing the row. + /// The primary keys of the row. + /// Option to set all required fields with placeholder values. + /// The new row. + private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields) + { + var row = table.CreateRow(null); + + var primaryKeyParts = primaryKeys.Split('\t'); + var primaryKeyPartIndex = 0; + + for (var i = 0; i < table.Definition.Columns.Length; i++) + { + var columnDefinition = table.Definition.Columns[i]; + + if (columnDefinition.PrimaryKey) + { + if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) + { + row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture); + } + else + { + row[i] = primaryKeyParts[primaryKeyPartIndex++]; + } + } + else if (setRequiredFields) + { + if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) + { + row[i] = 1; + } + else if (ColumnType.Object == columnDefinition.Type) + { + if (null == this.EmptyFile) + { + this.EmptyFile = Path.Combine(this.IntermediateFolder, ".empty"); + using (var fileStream = File.Create(this.EmptyFile)) + { + } + } + + row[i] = this.EmptyFile; + } + else + { + row[i] = "1"; + } + } + } + + return row; + } + + private void GenerateDatabase(WindowsInstallerData output, string databaseFile) + { + var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false); + command.Execute(); + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs new file mode 100644 index 00000000..0c15ad05 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs @@ -0,0 +1,24 @@ +// 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 +{ + using WixToolset.Data; + + internal static class WindowsInstallerBackendErrors + { + //public static Message ReplaceThisWithTheFirstError(SourceLineNumber sourceLineNumbers) + //{ + // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstError, "format string", arg1, arg2); + //} + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + // ReplaceThisWithTheFirstError = 7500, + } // last available is 7999. 8000 is BurnBackendErrors. + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs new file mode 100644 index 00000000..f72acb21 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs @@ -0,0 +1,51 @@ +// 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 +{ + using System; + using System.IO; + using WixToolset.Extensibility; + + internal class WindowsInstallerBackendFactory : IBackendFactory + { + public bool TryCreateBackend(string outputType, string outputFile, out IBackend backend) + { + if (String.IsNullOrEmpty(outputType)) + { + outputType = Path.GetExtension(outputFile); + } + + switch (outputType?.ToLowerInvariant()) + { + case "module": + case ".msm": + backend = new MsmBackend(); + return true; + + case "msipackage": + case "package": + case "product": + case ".msi": + backend = new MsiBackend(); + return true; + + case "patch": + case ".msp": + backend = new MspBackend(); + return true; + + //case "patchcreation": + //case ".pcp": + // return new PatchCreationBackend(); + + case "transform": + case ".mst": + backend = new MstBackend(); + return true; + } + + backend = null; + return false; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs new file mode 100644 index 00000000..d0986a4d --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs @@ -0,0 +1,24 @@ +// 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 +{ + using WixToolset.Data; + + internal static class WindowsInstallerBackendWarnings + { + //public static Message ReplaceThisWithTheFirstWarning(SourceLineNumber sourceLineNumbers) + //{ + // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstWarning, "format string", arg1, arg2); + //} + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); + } + + public enum Ids + { + // ReplaceThisWithTheFirstWarning = 7100, + } // last available is 7499. 7500 is WindowsInstallerBackendErrors. + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs new file mode 100644 index 00000000..7b12fc8c --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs @@ -0,0 +1,22 @@ +// 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 +{ + using System; + using WixToolset.Extensibility; + + internal class WindowsInstallerExtensionFactory : IExtensionFactory + { + public bool TryCreateExtension(Type extensionType, out object extension) + { + extension = null; + + if (extensionType == typeof(IBackendFactory)) + { + extension = new WindowsInstallerBackendFactory(); + } + + return extension != null; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj b/src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj new file mode 100644 index 00000000..b08f337f --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj @@ -0,0 +1,30 @@ + + + + + + netstandard2.0 + $(TargetFrameworks);net461;net472 + Core Windows Installer + WiX Toolset Core Windows Installer + embedded + true + true + + + + + + + + + + + + + + + + + + diff --git a/src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs new file mode 100644 index 00000000..e686fa49 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs @@ -0,0 +1,42 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using WixToolset.Core.WindowsInstaller.ExtensibilityServices; + using WixToolset.Extensibility.Services; + + /// + /// Extensions methods for adding WindowsInstaller services. + /// + public static class WixToolsetCoreServiceProviderExtensions + { + /// + /// Adds WindowsInstaller services. + /// + /// + /// + public static IWixToolsetCoreServiceProvider AddWindowsInstallerBackend(this IWixToolsetCoreServiceProvider coreProvider) + { + AddServices(coreProvider); + + var extensionManager = coreProvider.GetService(); + extensionManager.Add(typeof(WindowsInstallerExtensionFactory).Assembly); + + return coreProvider; + } + + private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) + { + // Singletons. + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new WindowsInstallerBackendHelper(provider))); + } + + private static T AddSingleton(Dictionary singletons, T service) where T : class + { + singletons.Add(typeof(T), service); + return service; + } + } +} diff --git a/src/wix/WixToolset.Core.sln b/src/wix/WixToolset.Core.sln new file mode 100644 index 00000000..523c960e --- /dev/null +++ b/src/wix/WixToolset.Core.sln @@ -0,0 +1,156 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2009 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core", "src\WixToolset.Core\WixToolset.Core.csproj", "{0B524850-5B9A-472B-85CC-D25920A1DFE1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.WindowsInstaller", "src\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj", "{5617F2A7-46A0-4D07-B9E0-E982D15641E4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.Burn", "src\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj", "{BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.ExtensionCache", "src\WixToolset.Core.ExtensionCache\WixToolset.Core.ExtensionCache.csproj", "{A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{1284331E-BC6C-426D-AAAF-140C0174F875}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.Extension", "src\test\Example.Extension\Example.Extension.csproj", "{C66C2503-C671-4230-8B48-1D93A8532A28}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.CoreIntegration", "src\test\WixToolsetTest.CoreIntegration\WixToolsetTest.CoreIntegration.csproj", "{E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.Core.Burn", "src\test\WixToolsetTest.Core.Burn\WixToolsetTest.Core.Burn.csproj", "{DF63F589-028E-45A1-A212-948FACF1FDCD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.TestPackage", "src\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj", "{853716DB-C02C-41BD-91BC-79CDC0C17D10}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompileCoreTestExtensionWixlib", "src\test\CompileCoreTestExtensionWixlib\CompileCoreTestExtensionWixlib.csproj", "{23FC60D7-B101-42F8-9786-DB7A9CD964A2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x64.ActiveCfg = Debug|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x64.Build.0 = Debug|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x86.ActiveCfg = Debug|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x86.Build.0 = Debug|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.Build.0 = Release|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x64.ActiveCfg = Release|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x64.Build.0 = Release|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x86.ActiveCfg = Release|Any CPU + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x86.Build.0 = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x64.ActiveCfg = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x64.Build.0 = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x86.ActiveCfg = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x86.Build.0 = Debug|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|Any CPU.Build.0 = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x64.ActiveCfg = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x64.Build.0 = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x86.ActiveCfg = Release|Any CPU + {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x86.Build.0 = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x64.ActiveCfg = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x64.Build.0 = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x86.ActiveCfg = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x86.Build.0 = Debug|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|Any CPU.Build.0 = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x64.ActiveCfg = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x64.Build.0 = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x86.ActiveCfg = Release|Any CPU + {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x86.Build.0 = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.Build.0 = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.Build.0 = Debug|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.Build.0 = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.ActiveCfg = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.Build.0 = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.ActiveCfg = Release|Any CPU + {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.Build.0 = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.ActiveCfg = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.Build.0 = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.ActiveCfg = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.Build.0 = Debug|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.Build.0 = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.ActiveCfg = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.Build.0 = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.ActiveCfg = Release|Any CPU + {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.Build.0 = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.ActiveCfg = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.Build.0 = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.ActiveCfg = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.Build.0 = Debug|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.Build.0 = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.ActiveCfg = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.Build.0 = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.ActiveCfg = Release|Any CPU + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.Build.0 = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.Build.0 = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.Build.0 = Debug|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.Build.0 = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.ActiveCfg = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.Build.0 = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.ActiveCfg = Release|Any CPU + {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.Build.0 = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.ActiveCfg = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.Build.0 = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.ActiveCfg = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.Build.0 = Debug|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.Build.0 = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.ActiveCfg = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.Build.0 = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.ActiveCfg = Release|Any CPU + {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.Build.0 = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.ActiveCfg = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.Build.0 = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.ActiveCfg = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.Build.0 = Debug|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.Build.0 = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.ActiveCfg = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.Build.0 = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.ActiveCfg = Release|Any CPU + {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C66C2503-C671-4230-8B48-1D93A8532A28} = {1284331E-BC6C-426D-AAAF-140C0174F875} + {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B} = {1284331E-BC6C-426D-AAAF-140C0174F875} + {DF63F589-028E-45A1-A212-948FACF1FDCD} = {1284331E-BC6C-426D-AAAF-140C0174F875} + {23FC60D7-B101-42F8-9786-DB7A9CD964A2} = {1284331E-BC6C-426D-AAAF-140C0174F875} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BB8820D5-723D-426D-B4A0-4D221603C5FA} + EndGlobalSection +EndGlobal diff --git a/src/wix/WixToolset.Core.v3.ncrunchsolution b/src/wix/WixToolset.Core.v3.ncrunchsolution new file mode 100644 index 00000000..10420ac9 --- /dev/null +++ b/src/wix/WixToolset.Core.v3.ncrunchsolution @@ -0,0 +1,6 @@ + + + True + True + + \ No newline at end of file diff --git a/src/wix/WixToolset.Core/Bind/DelayedField.cs b/src/wix/WixToolset.Core/Bind/DelayedField.cs new file mode 100644 index 00000000..25641516 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/DelayedField.cs @@ -0,0 +1,35 @@ +// 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.Bind +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + /// + /// Holds a symbol and field that contain binder variables, which need to be resolved + /// later, once the files have been resolved. + /// + internal class DelayedField : IDelayedField + { + /// + /// Creates a delayed field. + /// + /// Symbol for the field. + /// Field needing further resolution. + public DelayedField(IntermediateSymbol symbol, IntermediateField field) + { + this.Symbol = symbol; + this.Field = field; + } + + /// + /// The row containing the field. + /// + public IntermediateSymbol Symbol { get; } + + /// + /// The field needing further resolving. + /// + public IntermediateField Field { get; } + } +} diff --git a/src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs b/src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs new file mode 100644 index 00000000..b27cdfee --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs @@ -0,0 +1,16 @@ +// 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.Bind +{ + using System; + using WixToolset.Extensibility.Data; + + internal class ExpectedExtractFile : IExpectedExtractFile + { + public Uri Uri { get; set; } + + public string EmbeddedFileId { get; set; } + + public string OutputPath { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs new file mode 100644 index 00000000..a0798e62 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs @@ -0,0 +1,92 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + + /// + /// Internal helper class used to extract embedded files. + /// + internal class ExtractEmbeddedFiles + { + private readonly Dictionary> filesWithEmbeddedFiles = new Dictionary>(); + + public IEnumerable Uris => this.filesWithEmbeddedFiles.Keys; + + /// + /// Adds an embedded file index to track and returns the path where the embedded file will be extracted. Duplicates will return the same extract path. + /// + /// Uri to file containing the embedded files. + /// Id of the embedded file to extract. + /// Folder where extracted files should be placed. + /// The extract path for the embedded file. + public string AddEmbeddedFileToExtract(Uri uri, string embeddedFileId, string extractFolder) + { + // If the uri to the file that contains the embedded file does not already have embedded files + // being extracted, create the dictionary to track that. + if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) + { + extracts = new SortedList(StringComparer.OrdinalIgnoreCase); + this.filesWithEmbeddedFiles.Add(uri, extracts); + } + + // If the embedded file is not already tracked in the dictionary of extracts, add it. + if (!extracts.TryGetValue(embeddedFileId, out var extractPath)) + { + var localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath); + var unique = this.HashUri(uri.AbsoluteUri); + var extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileId); + + extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName)); + extracts.Add(embeddedFileId, extractPath); + } + + return extractPath; + } + + public IReadOnlyList GetExpectedEmbeddedFiles() + { + var files = new List(); + + foreach (var uriWithExtracts in this.filesWithEmbeddedFiles) + { + foreach (var extracts in uriWithExtracts.Value) + { + files.Add(new ExpectedExtractFile + { + Uri = uriWithExtracts.Key, + EmbeddedFileId = extracts.Key, + OutputPath = extracts.Value, + }); + } + } + + return files; + } + + public IEnumerable GetExtractFilesForUri(Uri uri) + { + if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts)) + { + extracts = new SortedList(StringComparer.OrdinalIgnoreCase); + } + + return extracts.Select(e => new ExpectedExtractFile { Uri = uri, EmbeddedFileId = e.Key, OutputPath = e.Value }); + } + + private string HashUri(string uri) + { + using (SHA1 sha1 = new SHA1CryptoServiceProvider()) + { + var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(uri)); + return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs new file mode 100644 index 00000000..ec2d8896 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs @@ -0,0 +1,53 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ExtractEmbeddedFilesCommand + { + public ExtractEmbeddedFilesCommand(IBackendHelper backendHelper, IEnumerable embeddedFiles) + { + this.BackendHelper = backendHelper; + this.FilesWithEmbeddedFiles = embeddedFiles; + } + + public IReadOnlyList TrackedFiles { get; private set; } + + private IBackendHelper BackendHelper { get; } + + private IEnumerable FilesWithEmbeddedFiles { get; } + + public void Execute() + { + var trackedFiles = new List(); + var group = this.FilesWithEmbeddedFiles.GroupBy(e => e.Uri); + + foreach (var expectedEmbeddedFileByUri in group) + { + var baseUri = expectedEmbeddedFileByUri.Key; + + using (var wixout = WixOutput.Read(baseUri)) + { + var uniqueIds = new SortedSet(StringComparer.OrdinalIgnoreCase); + + foreach (var embeddedFile in expectedEmbeddedFileByUri) + { + if (uniqueIds.Add(embeddedFile.EmbeddedFileId)) + { + wixout.ExtractEmbeddedFile(embeddedFile.EmbeddedFileId, embeddedFile.OutputPath); + trackedFiles.Add(this.BackendHelper.TrackFile(embeddedFile.OutputPath, TrackedFileType.Temporary)); + } + } + } + } + + this.TrackedFiles = trackedFiles; + } + } +} diff --git a/src/wix/WixToolset.Core/Bind/FileResolver.cs b/src/wix/WixToolset.Core/Bind/FileResolver.cs new file mode 100644 index 00000000..eb878239 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/FileResolver.cs @@ -0,0 +1,207 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class FileResolver + { + private const string BindPathOpenString = "!(bindpath."; + + private FileResolver(IEnumerable bindPaths) + { + this.BindPaths = (bindPaths ?? Array.Empty()).ToLookup(b => b.Stage); + this.RebaseTarget = this.BindPaths[BindStage.Target].Any(); + this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any(); + } + + public FileResolver(IEnumerable bindPaths, IEnumerable extensions) : this(bindPaths) + { + this.ResolverExtensions = extensions ?? Array.Empty(); + } + + public FileResolver(IEnumerable bindPaths, IEnumerable extensions) : this(bindPaths) + { + this.LibrarianExtensions = extensions ?? Array.Empty(); + } + + private ILookup BindPaths { get; } + + public bool RebaseTarget { get; } + + public bool RebaseUpdated { get; } + + private IEnumerable ResolverExtensions { get; } + + private IEnumerable LibrarianExtensions { get; } + + public string Resolve(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string source) + { + var checkedPaths = new List(); + + foreach (var extension in this.LibrarianExtensions) + { + var resolved = extension.ResolveFile(sourceLineNumbers, symbolDefinition, source); + + if (resolved?.CheckedPaths != null) + { + checkedPaths.AddRange(resolved.CheckedPaths); + } + + if (!String.IsNullOrEmpty(resolved?.Path)) + { + return resolved?.Path; + } + } + + return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, BindStage.Normal, checkedPaths); + } + + /// + /// Resolves the source path of a file using binder extensions. + /// + /// Original source value. + /// Optional type of source file being resolved. + /// Optional source line of source file being resolved. + /// The binding stage used to determine what collection of bind paths will be used + /// Optional collection of paths already checked. + /// Should return a valid path for the stream to be imported. + public string ResolveFile(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, IEnumerable alreadyCheckedPaths = null) + { + var checkedPaths = new List(); + + if (alreadyCheckedPaths != null) + { + checkedPaths.AddRange(alreadyCheckedPaths); + } + + foreach (var extension in this.ResolverExtensions) + { + var resolved = extension.ResolveFile(source, symbolDefinition, sourceLineNumbers, bindStage); + + if (resolved?.CheckedPaths != null) + { + checkedPaths.AddRange(resolved.CheckedPaths); + } + + if (!String.IsNullOrEmpty(resolved?.Path)) + { + return resolved?.Path; + } + } + + return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindStage, checkedPaths); + } + + private string MustResolveUsingBindPaths(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, List checkedPaths) + { + string resolved = null; + + // If the file exists, we're good to go. + checkedPaths.Add(source); + if (CheckFileExists(source)) + { + resolved = source; + } + else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist. + { + resolved = null; + } + else // not a rooted path so let's try applying all the different source resolution options. + { + var bindName = String.Empty; + var path = source; + var pathWithoutSourceDir = String.Empty; + + if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal)) + { + var closeParen = source.IndexOf(')', BindPathOpenString.Length); + + if (-1 != closeParen) + { + bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length); + path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace. + path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted. + } + } + else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal)) + { + pathWithoutSourceDir = path.Substring(10); + } + + var bindPaths = this.BindPaths[bindStage]; + + foreach (var bindPath in bindPaths) + { + if (String.IsNullOrEmpty(bindName)) + { + if (String.IsNullOrEmpty(bindPath.Name)) + { + if (!String.IsNullOrEmpty(pathWithoutSourceDir)) + { + var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir); + + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } + } + + if (String.IsNullOrEmpty(resolved)) + { + var filePath = Path.Combine(bindPath.Path, path); + + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } + } + } + } + else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase)) + { + var filePath = Path.Combine(bindPath.Path, path); + + checkedPaths.Add(filePath); + if (CheckFileExists(filePath)) + { + resolved = filePath; + } + } + + if (!String.IsNullOrEmpty(resolved)) + { + break; + } + } + } + + if (null == resolved) + { + throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, source, symbolDefinition.Name, checkedPaths)); + } + + return resolved; + } + + private static bool CheckFileExists(string path) + { + try + { + return File.Exists(path); + } + catch (ArgumentException) + { + throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs new file mode 100644 index 00000000..4ad8f764 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs @@ -0,0 +1,164 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Text; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Resolves the fields which had variables that needed to be resolved after the file information + /// was loaded. + /// + internal class ResolveDelayedFieldsCommand + { + /// + /// Resolve delayed fields. + /// + /// + /// The fields which had resolution delayed. + /// The cached variable values used when resolving delayed fields. + public ResolveDelayedFieldsCommand(IMessaging messaging, IEnumerable delayedFields, Dictionary variableCache) + { + this.Messaging = messaging; + this.DelayedFields = delayedFields; + this.VariableCache = variableCache; + } + + private IMessaging Messaging { get; } + + private IEnumerable DelayedFields { get;} + + private IDictionary VariableCache { get; } + + public void Execute() + { + var deferredFields = new List(); + + foreach (var delayedField in this.DelayedFields) + { + try + { + var propertySymbol = delayedField.Symbol; + + // process properties first in case they refer to other binder variables + if (delayedField.Symbol.Definition.Type == SymbolDefinitionType.Property) + { + var value = this.ResolveDelayedVariables(propertySymbol.SourceLineNumbers, delayedField.Field.AsString()); + + // update the variable cache with the new value + var key = String.Concat("property.", propertySymbol.Id.Id); + this.VariableCache[key] = value; + + // update the field data + delayedField.Field.Set(value); + } + else + { + deferredFields.Add(delayedField); + } + } + catch (WixException we) + { + this.Messaging.Write(we.Error); + continue; + } + } + + // add specialization for ProductVersion fields + var keyProductVersion = "property.ProductVersion"; + if (this.VariableCache.TryGetValue(keyProductVersion, out var versionValue) && Version.TryParse(versionValue, out var productVersion)) + { + // Don't add the variable if it already exists (developer defined a property with the same name). + var fieldKey = String.Concat(keyProductVersion, ".Major"); + if (!this.VariableCache.ContainsKey(fieldKey)) + { + this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture); + } + + fieldKey = String.Concat(keyProductVersion, ".Minor"); + if (!this.VariableCache.ContainsKey(fieldKey)) + { + this.VariableCache[fieldKey] = productVersion.Minor.ToString(CultureInfo.InvariantCulture); + } + + fieldKey = String.Concat(keyProductVersion, ".Build"); + if (!this.VariableCache.ContainsKey(fieldKey)) + { + this.VariableCache[fieldKey] = productVersion.Build.ToString(CultureInfo.InvariantCulture); + } + + fieldKey = String.Concat(keyProductVersion, ".Revision"); + if (!this.VariableCache.ContainsKey(fieldKey)) + { + this.VariableCache[fieldKey] = productVersion.Revision.ToString(CultureInfo.InvariantCulture); + } + } + + // process the remaining fields in case they refer to property binder variables + foreach (var delayedField in deferredFields) + { + try + { + var value = this.ResolveDelayedVariables(delayedField.Symbol.SourceLineNumbers, delayedField.Field.AsString()); + delayedField.Field.Set(value); + } + catch (WixException we) + { + this.Messaging.Write(we.Error); + } + } + } + + private string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value) + { + var start = 0; + + while (Common.TryParseWixVariable(value, start, out var parsed)) + { + if (parsed.Namespace == "bind") + { + var key = String.Concat(parsed.Name, ".", parsed.Scope); + + if (!this.VariableCache.TryGetValue(key, out var resolvedValue)) + { + resolvedValue = parsed.DefaultValue; + } + + // insert the resolved value if it was found or display an error + if (null != resolvedValue) + { + if (parsed.Index == 0 && parsed.Length == value.Length) + { + value = resolvedValue; + } + else + { + var sb = new StringBuilder(value); + sb.Remove(parsed.Index, parsed.Length); + sb.Insert(parsed.Index, resolvedValue); + value = sb.ToString(); + } + + start = parsed.Index; + } + else + { + this.Messaging.Write(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value)); + break; + } + } + else + { + start = parsed.Index + parsed.Length; + } + } + + return value; + } + } +} diff --git a/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs new file mode 100644 index 00000000..794208e5 --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs @@ -0,0 +1,276 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Resolve source fields in the tables included in the output + /// + internal class ResolveFieldsCommand + { + public IMessaging Messaging { private get; set; } + + public bool BuildingPatch { private get; set; } + + public IVariableResolver VariableResolver { private get; set; } + + public IEnumerable BindPaths { private get; set; } + + public IEnumerable Extensions { private get; set; } + + public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } + + public string IntermediateFolder { private get; set; } + + public Intermediate Intermediate { private get; set; } + + public bool SupportDelayedResolution { private get; set; } + + public bool AllowUnresolvedVariables { private get; set; } + + public IReadOnlyCollection DelayedFields { get; private set; } + + public void Execute() + { + var delayedFields = this.SupportDelayedResolution ? new List() : null; + + var fileResolver = new FileResolver(this.BindPaths, this.Extensions); + + // Build the column lookup only when needed. + Dictionary customColumnsById = null; + + foreach (var sections in this.Intermediate.Sections) + { + foreach (var symbol in sections.Symbols) + { + foreach (var field in symbol.Fields) + { + if (field.IsNull()) + { + continue; + } + + var fieldType = field.Type; + + // Custom table cells require an extra look up to the column definition as the + // cell's data type is always a string (because strings can store anything) but + // the column definition may be more specific. + if (symbol.Definition.Type == SymbolDefinitionType.WixCustomTableCell) + { + // We only care about the Data in a CustomTable cell. + if (field.Name != nameof(WixCustomTableCellSymbolFields.Data)) + { + continue; + } + + if (customColumnsById == null) + { + customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Symbols.OfType()).ToDictionary(t => t.Id.Id); + } + + if (customColumnsById.TryGetValue(symbol.Fields[(int)WixCustomTableCellSymbolFields.TableRef].AsString() + "/" + symbol.Fields[(int)WixCustomTableCellSymbolFields.ColumnRef].AsString(), out var customColumn)) + { + fieldType = customColumn.Type; + } + } + + // Check to make sure we're in a scenario where we can handle variable resolution. + if (null != delayedFields) + { + // resolve localization and wix variables + if (fieldType == IntermediateFieldType.String) + { + var original = field.AsString(); + if (!String.IsNullOrEmpty(original)) + { + var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables); + if (resolution.UpdatedValue) + { + field.Set(resolution.Value); + } + + if (resolution.DelayedResolve) + { + delayedFields.Add(new DelayedField(symbol, field)); + } + } + } + } + + // Move to next symbol if we've hit an error resolving variables. + if (this.Messaging.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. + { + continue; + } + + // Resolve file paths + if (fieldType == IntermediateFieldType.Path) + { + this.ResolvePathField(fileResolver, symbol, field); + +#if TODO_PATCHING + if (null != objectField.PreviousData) + { + objectField.PreviousData = this.BindVariableResolver.ResolveVariables(symbol.SourceLineNumbers, objectField.PreviousData, false, out isDefault); + + if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. + { + // file is compressed in a cabinet (and not modified above) + if (objectField.PreviousEmbeddedFileIndex.HasValue && isDefault) + { + // when loading transforms from disk, PreviousBaseUri may not have been set + if (null == objectField.PreviousBaseUri) + { + objectField.PreviousBaseUri = objectField.BaseUri; + } + + string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.IntermediateFolder); + + // set the path to the file once its extracted from the cabinet + objectField.PreviousData = extractPath; + } + else if (null != objectField.PreviousData) // non-compressed file (or localized value) + { + try + { + if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) + { + // resolve the path to the file + objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Normal); + } + else + { + if (fileResolver.RebaseTarget) + { + // if -bt is used, it come here + // Try to use the original unresolved source from either target build or update build + // If both target and updated are of old wixpdb, it behaves the same as today, no re-base logic here + // If target is old version and updated is new version, it uses unresolved path from updated build + // If both target and updated are of new versions, it uses unresolved path from target build + if (null != objectField.UnresolvedPreviousData || null != objectField.UnresolvedData) + { + objectField.PreviousData = objectField.UnresolvedPreviousData ?? objectField.UnresolvedData; + } + } + + // resolve the path to the file + objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Target); + + } + } + catch (WixFileNotFoundException) + { + // display the error with source line information + Messaging.Instance.Write(WixErrors.FileNotFound(symbol.SourceLineNumbers, (string)objectField.PreviousData)); + } + } + } + } +#endif + } + } + } + } + + this.DelayedFields = delayedFields; + } + + private void ResolvePathField(FileResolver fileResolver, IntermediateSymbol symbol, IntermediateField field) + { + var fieldValue = field.AsPath(); + var originalFieldPath = fieldValue.Path; + +#if TODO_PATCHING + // Skip file resolution if the file is to be deleted. + if (RowOperation.Delete == symbol.Operation) + { + continue; + } +#endif + + // If the file is embedded and if the previous value has a bind variable in the path + // which gets modified by resolving the previous value again then switch to that newly + // resolved path instead of using the embedded file. + if (fieldValue.Embed && field.PreviousValue != null) + { + var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, field.PreviousValue.AsString(), errorOnUnknown: false); + + if (resolution.UpdatedValue && !resolution.IsDefault) + { + fieldValue = new IntermediateFieldPathValue { Path = resolution.Value }; + } + } + + // If we're still using the embedded file. + if (fieldValue.Embed) + { + // Set the path to the embedded file once where it will be extracted. + var extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileToExtract(fieldValue.BaseUri, fieldValue.Path, this.IntermediateFolder); + + field.Set(extractPath); + } + else if (fieldValue.Path != null) + { + try + { + var resolvedPath = fieldValue.Path; + + if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe) + { +#if TODO_PATCHING + // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file + if (null == objectField.UnresolvedData) + { + objectField.UnresolvedData = (string)objectField.Data; + } +#endif + resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); + } + else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic) + { + resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal); + } +#if TODO_PATCHING + else // Re-base binding path scenario caused by pyro.exe -bt -bu + { + // by default, use the resolved Data for file lookup + string filePathToResolve = (string)objectField.Data; + + // if -bu is used in pyro command, this condition holds true and the tool + // will use pre-resolved source for new wixpdb file + if (fileResolver.RebaseUpdated) + { + // try to use the unResolved Source if it exists. + // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll + // Old version of winpdb file does not contain this attribute and the value is null. + if (null != objectField.UnresolvedData) + { + filePathToResolve = objectField.UnresolvedData; + } + } + + objectField.Data = fileResolver.ResolveFile(filePathToResolve, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Updated); + } +#endif + + if (!String.Equals(originalFieldPath, resolvedPath, StringComparison.OrdinalIgnoreCase)) + { + field.Set(resolvedPath); + } + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs new file mode 100644 index 00000000..b3b74fbc --- /dev/null +++ b/src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs @@ -0,0 +1,196 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class TransferFilesCommand + { + public TransferFilesCommand(IMessaging messaging, IEnumerable extensions, IEnumerable fileTransfers, bool resetAcls) + { + this.Extensions = extensions; + this.Messaging = messaging; + this.FileTransfers = fileTransfers; + this.ResetAcls = resetAcls; + } + + private IMessaging Messaging { get; } + + private IEnumerable Extensions { get; } + + private IEnumerable FileTransfers { get; } + + private bool ResetAcls { get; } + + public void Execute() + { + var destinationFiles = new List(); + + foreach (var fileTransfer in this.FileTransfers) + { + // If the source and destination are identical, then there's nothing to do here + if (0 == String.Compare(fileTransfer.Source, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase)) + { + fileTransfer.Redundant = true; + continue; + } + + var retry = false; + do + { + try + { + if (fileTransfer.Move) + { + this.Messaging.Write(VerboseMessages.MoveFile(fileTransfer.Source, fileTransfer.Destination)); + this.MoveFile(fileTransfer.Source, fileTransfer.Destination); + } + else + { + this.Messaging.Write(VerboseMessages.CopyFile(fileTransfer.Source, fileTransfer.Destination)); + this.CopyFile(fileTransfer.Source, fileTransfer.Destination); + } + + retry = false; + destinationFiles.Add(fileTransfer.Destination); + } + catch (FileNotFoundException e) + { + throw new WixException(ErrorMessages.FileNotFound(fileTransfer.SourceLineNumbers, e.FileName)); + } + catch (DirectoryNotFoundException) + { + // if we already retried, give up + if (retry) + { + throw; + } + + var directory = Path.GetDirectoryName(fileTransfer.Destination); + this.Messaging.Write(VerboseMessages.CreateDirectory(directory)); + Directory.CreateDirectory(directory); + retry = true; + } + catch (UnauthorizedAccessException) + { + // if we already retried, give up + if (retry) + { + throw; + } + + if (File.Exists(fileTransfer.Destination)) + { + this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination)); + + // try to ensure the file is not read-only + var attributes = File.GetAttributes(fileTransfer.Destination); + try + { + File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly); + } + catch (ArgumentException) // thrown for unauthorized access errors + { + throw new WixException(ErrorMessages.UnauthorizedAccess(fileTransfer.Destination)); + } + + // try to delete the file + try + { + File.Delete(fileTransfer.Destination); + } + catch (IOException) + { + throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination)); + } + + retry = true; + } + else // no idea what just happened, bail + { + throw; + } + } + catch (IOException) + { + // if we already retried, give up + if (retry) + { + throw; + } + + if (File.Exists(fileTransfer.Destination)) + { + this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination)); + + // ensure the file is not read-only, then delete it + var attributes = File.GetAttributes(fileTransfer.Destination); + File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly); + try + { + File.Delete(fileTransfer.Destination); + } + catch (IOException) + { + throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination)); + } + + retry = true; + } + else // no idea what just happened, bail + { + throw; + } + } + } while (retry); + } + + // Finally, if directed then reset remove ACLs that may may have been picked up + // during the file transfer process. + if (this.ResetAcls && 0 < destinationFiles.Count) + { + try + { + FileSystem.ResetAcls(destinationFiles); + } + catch (Exception e) + { + this.Messaging.Write(WarningMessages.UnableToResetAcls(e.Message)); + } + } + } + + private void CopyFile(string source, string destination) + { + foreach (var extension in this.Extensions) + { + if (extension.CopyFile(source, destination)) + { + return; + } + } + + FileSystem.CopyFile(source, destination, allowHardlink: true); + } + + private void MoveFile(string source, string destination) + { + foreach (var extension in this.Extensions) + { + if (extension.MoveFile(source, destination)) + { + return; + } + } + + FileSystem.MoveFile(source, destination); + } + } +} diff --git a/src/wix/WixToolset.Core/BindContext.cs b/src/wix/WixToolset.Core/BindContext.cs new file mode 100644 index 00000000..052382f1 --- /dev/null +++ b/src/wix/WixToolset.Core/BindContext.cs @@ -0,0 +1,65 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class BindContext : IBindContext + { + internal BindContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IReadOnlyCollection BindPaths { get; set; } + + public string BurnStubPath { get; set; } + + public int CabbingThreadCount { get; set; } + + public string CabCachePath { get; set; } + + public CompressionLevel? DefaultCompressionLevel { get; set; } + + public IReadOnlyCollection DelayedFields { get; set; } + + public IReadOnlyCollection ExpectedEmbeddedFiles { get; set; } + + public IReadOnlyCollection Extensions { get; set; } + + public IReadOnlyCollection FileSystemExtensions { get; set; } + + public IReadOnlyCollection Ices { get; set; } + + public string IntermediateFolder { get; set; } + + public Intermediate IntermediateRepresentation { get; set; } + + public string OutputPath { get; set; } + + public PdbType PdbType { get; set; } + + public string PdbPath { get; set; } + + public int? ResolvedCodepage { get; set; } + + public int? ResolvedSummaryInformationCodepage { get; set; } + + public int? ResolvedLcid { get; set; } + + public IReadOnlyCollection SuppressIces { get; set; } + + public bool SuppressValidation { get; set; } + + public bool SuppressLayout { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/BindFileWithPath.cs b/src/wix/WixToolset.Core/BindFileWithPath.cs new file mode 100644 index 00000000..539600d3 --- /dev/null +++ b/src/wix/WixToolset.Core/BindFileWithPath.cs @@ -0,0 +1,22 @@ +// 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 +{ + using WixToolset.Extensibility.Data; + + /// + /// Bind file with its path. + /// + internal class BindFileWithPath : IBindFileWithPath + { + /// + /// Gets or sets the identifier of the file with this path. + /// + public string Id { get; set; } + + /// + /// Gets or sets the file path. + /// + public string Path { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/BindPath.cs b/src/wix/WixToolset.Core/BindPath.cs new file mode 100644 index 00000000..f70d5e36 --- /dev/null +++ b/src/wix/WixToolset.Core/BindPath.cs @@ -0,0 +1,20 @@ +// 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 +{ + using System.Diagnostics; + using WixToolset.Extensibility.Data; + + /// + /// Bind path representation. + /// + [DebuggerDisplay("Name={Name,nq} Path={Path,nq}")] + internal class BindPath : IBindPath + { + public string Name { get; set; } + + public string Path { get; set; } + + public BindStage Stage { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/BindResult.cs b/src/wix/WixToolset.Core/BindResult.cs new file mode 100644 index 00000000..9785484c --- /dev/null +++ b/src/wix/WixToolset.Core/BindResult.cs @@ -0,0 +1,48 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class BindResult : IBindResult + { + private bool disposed; + + public IReadOnlyCollection FileTransfers { get; set; } + + public IReadOnlyCollection TrackedFiles { get; set; } + + public WixOutput Wixout { get; set; } + + #region IDisposable Support + /// + /// Disposes of the internal state of the file structure. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of the internsl state of the file structure. + /// + /// True if disposing. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + this.Wixout?.Dispose(); + } + } + + this.disposed = true; + } + #endregion + } +} diff --git a/src/wix/WixToolset.Core/Binder.cs b/src/wix/WixToolset.Core/Binder.cs new file mode 100644 index 00000000..204ab6ee --- /dev/null +++ b/src/wix/WixToolset.Core/Binder.cs @@ -0,0 +1,96 @@ +// 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 +{ + using System; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Binder of the WiX toolset. + /// + internal class Binder : IBinder + { + internal Binder(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IBindResult Bind(IBindContext context) + { + // Prebind. + // + foreach (var extension in context.Extensions) + { + extension.PreBind(context); + } + + // Bind. + // + this.WriteBuildInfoSymbol(context.IntermediateRepresentation, context.OutputPath, context.PdbPath); + + var bindResult = this.BackendBind(context); + + if (bindResult != null) + { + // Postbind. + // + foreach (var extension in context.Extensions) + { + extension.PostBind(bindResult); + } + } + + return bindResult; + } + + private IBindResult BackendBind(IBindContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendFactories = extensionManager.GetServices(); + + var entrySection = context.IntermediateRepresentation.Sections.First(); + + foreach (var factory in backendFactories) + { + if (factory.TryCreateBackend(entrySection.Type.ToString(), context.OutputPath, out var backend)) + { + var result = backend.Bind(context); + return result; + } + } + + // TODO: messaging that a backend could not be found to bind the output type? + + return null; + } + + private void WriteBuildInfoSymbol(Intermediate output, string outputFile, string outputPdbPath) + { + var entrySection = output.Sections.First(s => s.Type != SectionType.Fragment); + + var executingAssembly = Assembly.GetExecutingAssembly(); + var fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location); + + var buildInfoSymbol = entrySection.AddSymbol(new WixBuildInfoSymbol() + { + WixVersion = fileVersion.FileVersion, + WixOutputFile = outputFile, + }); + + if (!String.IsNullOrEmpty(outputPdbPath)) + { + buildInfoSymbol.WixPdbFile = outputPdbPath; + } + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs new file mode 100644 index 00000000..5f618b81 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs @@ -0,0 +1,912 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class BuildCommand : ICommandLineCommand + { + private readonly CommandLine commandLine; + + public BuildCommand(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + this.ExtensionManager = serviceProvider.GetService(); + this.commandLine = new CommandLine(this.ServiceProvider, this.Messaging); + } + + public bool ShowLogo => this.commandLine.ShowLogo; + + public bool StopParsing => this.commandLine.ShowHelp; + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private IExtensionManager ExtensionManager { get; } + + private string IntermediateFolder { get; set; } + + private OutputType OutputType { get; set; } + + private List IncludeSearchPaths { get; set; } + + public string PdbFile { get; set; } + + public PdbType PdbType { get; set; } + + private Platform Platform { get; set; } + + private string OutputFile { get; set; } + + private CompressionLevel? DefaultCompressionLevel { get; set; } + + private string ContentsFile { get; set; } + + private string OutputsFile { get; set; } + + private string BuiltOutputsFile { get; set; } + + public Task ExecuteAsync(CancellationToken cancellationToken) + { + if (this.commandLine.ShowHelp) + { + Console.WriteLine("TODO: Show build command help"); + return Task.FromResult(-1); + } + + this.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); + + this.OutputType = this.commandLine.CalculateOutputType(); + + this.IncludeSearchPaths = this.commandLine.IncludeSearchPaths; + + this.PdbFile = this.commandLine.PdbFile; + + this.PdbType = this.commandLine.PdbType; + + this.Platform = this.commandLine.Platform; + + this.ContentsFile = this.commandLine.ContentsFile; + + this.OutputsFile = this.commandLine.OutputsFile; + + this.BuiltOutputsFile = this.commandLine.BuiltOutputsFile; + + this.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel; + + var preprocessorVariables = this.commandLine.GatherPreprocessorVariables(); + + var sourceFiles = this.commandLine.GatherSourceFiles(this.IntermediateFolder); + + var filterCultures = this.commandLine.CalculateFilterCultures(); + + var creator = this.ServiceProvider.GetService(); + + this.EvaluateSourceFiles(sourceFiles, creator, out var codeFiles, out var wixipl); + + this.OutputFile = this.commandLine.OutputFile; + + if (String.IsNullOrEmpty(this.OutputFile)) + { + if (codeFiles.Count == 1) + { + // If output type is unknown, the extension will be replaced with the right default based on output type. + this.OutputFile = Path.ChangeExtension(codeFiles[0].OutputPath, DefaultExtensionForOutputType(this.OutputType)); + } + else + { + this.Messaging.Write(ErrorMessages.MustSpecifyOutputWithMoreThanOneInput()); + } + } + + if (this.Messaging.EncounteredError) + { + return Task.FromResult(this.Messaging.LastErrorNumber); + } + + var wixobjs = this.CompilePhase(preprocessorVariables, codeFiles, cancellationToken); + + var wxls = this.LoadLocalizationFiles(this.commandLine.LocalizationFilePaths, preprocessorVariables, cancellationToken); + + if (this.Messaging.EncounteredError) + { + return Task.FromResult(this.Messaging.LastErrorNumber); + } + + if (this.OutputType == OutputType.Library) + { + using (new IntermediateFieldContext("wix.lib")) + { + var wixlib = this.LibraryPhase(wixobjs, wxls, this.commandLine.BindFiles, this.commandLine.BindPaths, cancellationToken); + + if (!this.Messaging.EncounteredError) + { + wixlib.Save(this.OutputFile); + } + } + } + else + { + using (new IntermediateFieldContext("wix.link")) + { + if (wixipl == null) + { + wixipl = this.LinkPhase(wixobjs, this.commandLine.LibraryFilePaths, creator, cancellationToken); + } + + if (!this.Messaging.EncounteredError) + { + var outputExtension = Path.GetExtension(this.OutputFile); + if (String.IsNullOrEmpty(outputExtension) || ".wix" == outputExtension) + { + var entrySectionType = wixipl.Sections.Single().Type; + this.OutputFile = Path.ChangeExtension(this.OutputFile, DefaultExtensionForSectionType(entrySectionType)); + } + + if (this.OutputType == OutputType.IntermediatePostLink) + { + wixipl.Save(this.OutputFile); + } + else + { + using (new IntermediateFieldContext("wix.bind")) + { + this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths, cancellationToken); + } + } + } + } + } + + return Task.FromResult(this.Messaging.LastErrorNumber); + } + + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + return this.commandLine.TryParseArgument(argument, parser); + } + + private void EvaluateSourceFiles(IEnumerable sourceFiles, ISymbolDefinitionCreator creator, out List codeFiles, out Intermediate wixipl) + { + codeFiles = new List(); + + wixipl = null; + + foreach (var sourceFile in sourceFiles) + { + var extension = Path.GetExtension(sourceFile.SourcePath); + + if (wixipl != null || ".wxs".Equals(extension, StringComparison.OrdinalIgnoreCase)) + { + codeFiles.Add(sourceFile); + } + else + { + try + { + wixipl = Intermediate.Load(sourceFile.SourcePath, creator); + } + catch (WixException) + { + // We'll assume anything that isn't a valid intermediate is source code to compile. + codeFiles.Add(sourceFile); + } + } + } + + if (wixipl == null && codeFiles.Count == 0) + { + this.Messaging.Write(ErrorMessages.NoSourceFiles()); + } + else if (wixipl != null && codeFiles.Count != 0) + { + this.Messaging.Write(ErrorMessages.WixiplSourceFileIsExclusive()); + } + } + + private IReadOnlyList CompilePhase(IDictionary preprocessorVariables, IEnumerable sourceFiles, CancellationToken cancellationToken) + { + var intermediates = new List(); + + foreach (var sourceFile in sourceFiles) + { + var document = this.Preprocess(preprocessorVariables, sourceFile.SourcePath, cancellationToken); + + if (this.Messaging.EncounteredError) + { + continue; + } + + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ExtensionManager.GetServices(); + context.Platform = this.Platform; + context.Source = document; + context.CancellationToken = cancellationToken; + + Intermediate intermediate = null; + try + { + var compiler = this.ServiceProvider.GetService(); + intermediate = compiler.Compile(context); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + if (this.Messaging.EncounteredError) + { + continue; + } + + intermediates.Add(intermediate); + } + + return intermediates; + } + + private Intermediate LibraryPhase(IReadOnlyCollection intermediates, IReadOnlyCollection localizations, bool bindFiles, IReadOnlyCollection bindPaths, CancellationToken cancellationToken) + { + var context = this.ServiceProvider.GetService(); + context.BindFiles = bindFiles; + context.BindPaths = bindPaths; + context.Extensions = this.ExtensionManager.GetServices(); + context.Localizations = localizations; + context.Intermediates = intermediates; + context.CancellationToken = cancellationToken; + + Intermediate library = null; + try + { + var librarian = this.ServiceProvider.GetService(); + library = librarian.Combine(context); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + return library; + } + + private Intermediate LinkPhase(IEnumerable intermediates, IEnumerable libraryFiles, ISymbolDefinitionCreator creator, CancellationToken cancellationToken) + { + var libraries = this.LoadLibraries(libraryFiles, creator); + + if (this.Messaging.EncounteredError) + { + return null; + } + + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ExtensionManager.GetServices(); + context.ExtensionData = this.ExtensionManager.GetServices(); + context.ExpectedOutputType = this.OutputType; + context.Intermediates = intermediates.Concat(libraries).ToList(); + context.SymbolDefinitionCreator = creator; + context.CancellationToken = cancellationToken; + + var linker = this.ServiceProvider.GetService(); + return linker.Link(context); + } + + private void BindPhase(Intermediate output, IReadOnlyCollection localizations, IReadOnlyCollection filterCultures, string cabCachePath, IReadOnlyCollection bindPaths, CancellationToken cancellationToken) + { + var intermediateFolder = this.IntermediateFolder; + if (String.IsNullOrEmpty(intermediateFolder)) + { + intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + } + + IResolveResult resolveResult; + { + var context = this.ServiceProvider.GetService(); + context.BindPaths = bindPaths; + context.Extensions = this.ExtensionManager.GetServices(); + context.ExtensionData = this.ExtensionManager.GetServices(); + context.FilterCultures = filterCultures; + context.IntermediateFolder = intermediateFolder; + context.IntermediateRepresentation = output; + context.Localizations = localizations; + context.CancellationToken = cancellationToken; + + var resolver = this.ServiceProvider.GetService(); + resolveResult = resolver.Resolve(context); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + IBindResult bindResult = null; + try + { + { + var context = this.ServiceProvider.GetService(); + //context.CabbingThreadCount = this.CabbingThreadCount; + context.CabCachePath = cabCachePath; + context.ResolvedCodepage = resolveResult.Codepage; + context.ResolvedSummaryInformationCodepage = resolveResult.SummaryInformationCodepage; + context.ResolvedLcid = resolveResult.PackageLcid; + context.DefaultCompressionLevel = this.DefaultCompressionLevel; + context.DelayedFields = resolveResult.DelayedFields; + context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; + context.Extensions = this.ExtensionManager.GetServices(); + context.FileSystemExtensions = this.ExtensionManager.GetServices(); + context.Ices = this.commandLine.Ices; + context.IntermediateFolder = intermediateFolder; + context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; + context.OutputPath = this.OutputFile; + context.PdbType = this.PdbType; + context.PdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(this.OutputFile, ".wixpdb"); + context.SuppressIces = this.commandLine.SuppressIces; + context.SuppressValidation = this.commandLine.SuppressValidation; + context.CancellationToken = cancellationToken; + + var binder = this.ServiceProvider.GetService(); + bindResult = binder.Bind(context); + } + + if (this.Messaging.EncounteredError) + { + return; + } + + { + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ExtensionManager.GetServices(); + context.TrackedFiles = bindResult.TrackedFiles; + context.FileTransfers = bindResult.FileTransfers; + context.IntermediateFolder = intermediateFolder; + context.ContentsFile = this.ContentsFile; + context.OutputsFile = this.OutputsFile; + context.BuiltOutputsFile = this.BuiltOutputsFile; + context.ResetAcls = this.commandLine.ResetAcls; + context.CancellationToken = cancellationToken; + + var layout = this.ServiceProvider.GetService(); + layout.Layout(context); + } + } + finally + { + bindResult?.Dispose(); + } + } + + private IEnumerable LoadLibraries(IEnumerable libraryFiles, ISymbolDefinitionCreator creator) + { + try + { + return Intermediate.Load(libraryFiles, creator); + } + catch (WixCorruptFileException e) + { + this.Messaging.Write(e.Error); + } + catch (WixUnexpectedFileFormatException e) + { + this.Messaging.Write(e.Error); + } + + return Array.Empty(); + } + + private IReadOnlyList LoadLocalizationFiles(IEnumerable locFiles, IDictionary preprocessorVariables, CancellationToken cancellationToken) + { + var localizations = new List(); + var parser = this.ServiceProvider.GetService(); + + foreach (var loc in locFiles) + { + var document = this.Preprocess(preprocessorVariables, loc, cancellationToken); + + if (this.Messaging.EncounteredError) + { + continue; + } + + var localization = parser.ParseLocalization(document); + localizations.Add(localization); + } + + return localizations; + } + + private XDocument Preprocess(IDictionary preprocessorVariables, string sourcePath, CancellationToken cancellationToken) + { + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ExtensionManager.GetServices(); + context.Platform = this.Platform; + context.IncludeSearchPaths = this.IncludeSearchPaths; + context.SourcePath = sourcePath; + context.Variables = preprocessorVariables; + context.CancellationToken = cancellationToken; + + IPreprocessResult result = null; + try + { + var preprocessor = this.ServiceProvider.GetService(); + result = preprocessor.Preprocess(context); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + return result?.Document; + } + + private static string DefaultExtensionForSectionType(SectionType sectionType) + { + switch (sectionType) + { + case SectionType.Bundle: + return ".exe"; + case SectionType.Module: + return ".msm"; + case SectionType.Product: + return ".msi"; + case SectionType.PatchCreation: + return ".pcp"; + case SectionType.Patch: + return ".msp"; + case SectionType.Fragment: + case SectionType.Unknown: + default: + return ".wix"; + } + } + + private static string DefaultExtensionForOutputType(OutputType outputType) + { + switch (outputType) + { + case OutputType.Bundle: + return ".exe"; + case OutputType.Library: + return ".wixlib"; + case OutputType.Module: + return ".msm"; + case OutputType.Patch: + return ".msp"; + case OutputType.PatchCreation: + return ".pcp"; + case OutputType.Product: + return ".msi"; + case OutputType.Transform: + return ".mst"; + case OutputType.IntermediatePostLink: + return ".wixipl"; + case OutputType.Unknown: + default: + return ".wix"; + } + } + + private class CommandLine + { + private static readonly char[] BindPathSplit = { '=' }; + + public bool BindFiles { get; private set; } + + public List BindPaths { get; } = new List(); + + public string CabCachePath { get; private set; } + + public List Cultures { get; } = new List(); + + public List Defines { get; } = new List(); + + public List IncludeSearchPaths { get; } = new List(); + + public List LocalizationFilePaths { get; } = new List(); + + public List LibraryFilePaths { get; } = new List(); + + public List SourceFilePaths { get; } = new List(); + + public Platform Platform { get; private set; } + + public string PdbFile { get; private set; } + + public PdbType PdbType { get; private set; } + + public bool ShowLogo { get; private set; } + + public bool ShowHelp { get; private set; } + + public string IntermediateFolder { get; private set; } + + public string OutputFile { get; private set; } + + public string OutputType { get; private set; } + + public CompressionLevel? DefaultCompressionLevel { get; private set; } + + public string ContentsFile { get; private set; } + + public string OutputsFile { get; private set; } + + public string BuiltOutputsFile { get; private set; } + + public List Ices { get; } = new List(); + + public List SuppressIces { get; } = new List(); + + public bool SuppressValidation { get; set; } + + public bool ResetAcls { get; set; } + + public CommandLine(IServiceProvider serviceProvider, IMessaging messaging) + { + this.ServiceProvider = serviceProvider; + this.Messaging = messaging; + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + public bool TryParseArgument(string arg, ICommandLineParser parser) + { + if (parser.IsSwitch(arg)) + { + var parameter = arg.Substring(1).ToLowerInvariant(); + switch (parameter) + { + case "?": + case "h": + case "help": + this.ShowHelp = true; + return true; + + case "arch": + case "platform": + { + var value = parser.GetNextArgumentOrError(arg); + if (Enum.TryParse(value, true, out Platform platform)) + { + this.Platform = platform; + return true; + } + break; + } + + case "bf": + case "bindfiles": + this.BindFiles = true; + return true; + + case "bindpath": + { + var value = parser.GetNextArgumentOrError(arg); + if (value != null && this.TryParseBindPath(value, out var bindPath)) + { + this.BindPaths.Add(bindPath); + return true; + } + return false; + } + + case "cc": + this.CabCachePath = parser.GetNextArgumentOrError(arg); + return true; + + case "culture": + parser.GetNextArgumentOrError(arg, this.Cultures); + return true; + + case "contentsfile": + this.ContentsFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "outputsfile": + this.OutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "builtoutputsfile": + this.BuiltOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "d": + case "define": + parser.GetNextArgumentOrError(arg, this.Defines); + return true; + + case "dcl": + case "defaultcompressionlevel": + { + var value = parser.GetNextArgumentOrError(arg); + if (Enum.TryParse(value, true, out CompressionLevel compressionLevel)) + { + this.DefaultCompressionLevel = compressionLevel; + return true; + } + return false; + } + + case "i": + case "includepath": + parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths); + return true; + + case "ice": + { + var value = parser.GetNextArgumentOrError(arg); + this.Ices.Add(value); + return true; + } + + case "intermediatefolder": + this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); + return true; + + case "loc": + parser.GetNextArgumentAsFilePathOrError(arg, "localization files", this.LocalizationFilePaths); + return true; + + case "lib": + parser.GetNextArgumentAsFilePathOrError(arg, "library files", this.LibraryFilePaths); + return true; + + case "o": + case "out": + this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "outputtype": + this.OutputType = parser.GetNextArgumentOrError(arg); + return true; + + case "pdb": + this.PdbFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "pdbtype": + { + var value = parser.GetNextArgumentOrError(arg); + if (Enum.TryParse(value, true, out PdbType pdbType)) + { + this.PdbType = pdbType; + return true; + } + return false; + } + + case "sice": + { + var value = parser.GetNextArgumentOrError(arg); + this.SuppressIces.Add(value); + return true; + } + + case "nologo": + this.ShowLogo = false; + return true; + + case "v": + case "verbose": + this.Messaging.ShowVerboseMessages = true; + return true; + + case "sval": + this.SuppressValidation = true; + return true; + + case "resetacls": + this.ResetAcls = true; + return true; + } + + if (parameter.StartsWith("sw")) + { + this.ParseSuppressWarning(parameter, "sw".Length, parser); + return true; + } + else if (parameter.StartsWith("suppresswarning")) + { + this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser); + return true; + } + else if (parameter.StartsWith("wx")) + { + this.ParseWarningAsError(parameter, "wx".Length, parser); + return true; + } + + return false; + } + else + { + parser.GetArgumentAsFilePathOrError(arg, "source code", this.SourceFilePaths); + return true; + } + } + + public string CalculateIntermedateFolder() + { + return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; + } + + public OutputType CalculateOutputType() + { + if (String.IsNullOrEmpty(this.OutputType)) + { + this.OutputType = Path.GetExtension(this.OutputFile); + } + + switch (this.OutputType?.ToLowerInvariant()) + { + case "bundle": + case ".exe": + return Data.OutputType.Bundle; + + case "library": + case ".wixlib": + return Data.OutputType.Library; + + case "module": + case ".msm": + return Data.OutputType.Module; + + case "patch": + case ".msp": + return Data.OutputType.Patch; + + case ".pcp": + return Data.OutputType.PatchCreation; + + case "product": + case "package": + case ".msi": + return Data.OutputType.Product; + + case "transform": + case ".mst": + return Data.OutputType.Transform; + + case "intermediatepostlink": + case ".wixipl": + return Data.OutputType.IntermediatePostLink; + } + + return Data.OutputType.Unknown; + } + + public IReadOnlyList CalculateFilterCultures() + { + var result = new List(); + + if (this.Cultures == null) + { + } + else if (this.Cultures.Count == 1 && this.Cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase)) + { + // When null is used treat it as if cultures wasn't specified. This is + // needed for batching in the MSBuild task since MSBuild doesn't support + // empty items. + } + else + { + foreach (var culture in this.Cultures) + { + // Neutral is different from null. For neutral we still want to do culture filtering. + // Set the culture to the empty string = identifier for the invariant culture. + var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture; + result.Add(filter); + } + } + + return result; + } + + public IDictionary GatherPreprocessorVariables() + { + var variables = new Dictionary(); + + foreach (var pair in this.Defines) + { + var value = pair.Split(new[] { '=' }, 2); + + if (variables.ContainsKey(value[0])) + { + this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]])); + continue; + } + + variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]); + } + + return variables; + } + + public IEnumerable GatherSourceFiles(string intermediateDirectory) + { + var files = new List(); + + foreach (var item in this.SourceFilePaths) + { + var sourcePath = item; + var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir"); + + files.Add(new SourceFile(sourcePath, outputPath)); + } + + return files; + } + + private bool TryParseBindPath(string bindPath, out IBindPath bp) + { + var namedPath = bindPath.Split(BindPathSplit, 2); + + bp = this.ServiceProvider.GetService(); + + if (1 == namedPath.Length) + { + bp.Path = namedPath[0]; + } + else + { + bp.Name = namedPath[0]; + bp.Path = namedPath[1]; + } + + if (File.Exists(bp.Path)) + { + this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path)); + return false; + } + + return true; + } + + private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser) + { + var paramArg = parameter.Substring(offset); + if (paramArg.Length == 0) + { + this.Messaging.SuppressAllWarnings = true; + } + else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0) + { + this.Messaging.SuppressWarningMessage(suppressWarning); + } + else + { + parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg)); + } + } + + private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser) + { + var paramArg = parameter.Substring(offset); + if (paramArg.Length == 0) + { + this.Messaging.WarningsAsError = true; + } + else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0) + { + this.Messaging.ElevateWarningMessage(elevateWarning); + } + else + { + parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg)); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLine.cs b/src/wix/WixToolset.Core/CommandLine/CommandLine.cs new file mode 100644 index 00000000..b87b6a5d --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLine.cs @@ -0,0 +1,199 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal enum CommandTypes + { + Unknown, + Build, + Preprocess, + Compile, + Link, + Bind, + Decompile, + } + + internal class CommandLine : ICommandLine + { + public CommandLine(IServiceProvider serviceProvider) => this.ServiceProvider = serviceProvider; + + private IServiceProvider ServiceProvider { get; } + + public ICommandLineCommand CreateCommand(string[] args) + { + var arguments = this.ServiceProvider.GetService(); + arguments.Populate(args); + + this.LoadExtensions(arguments.Extensions); + + return this.ParseStandardCommandLine(arguments); + } + + public ICommandLineCommand CreateCommand(string commandLine) + { + var arguments = this.ServiceProvider.GetService(); + arguments.Populate(commandLine); + + this.LoadExtensions(arguments.Extensions); + + return this.ParseStandardCommandLine(arguments); + } + + public ICommandLineCommand ParseStandardCommandLine(ICommandLineArguments arguments) + { + var context = this.ServiceProvider.GetService(); + context.ExtensionManager = this.ServiceProvider.GetService(); + context.Arguments = arguments; + + var command = this.Parse(context); + + if (command.ShowLogo) + { + var branding = this.ServiceProvider.GetService(); + Console.WriteLine(branding.ReplacePlaceholders("[AssemblyProduct] [AssemblyDescription] version [FileVersion]")); + Console.WriteLine(branding.ReplacePlaceholders("[AssemblyCopyright]")); + } + + return command; + } + + private void LoadExtensions(string[] extensions) + { + var extensionManager = this.ServiceProvider.GetService(); + + foreach (var extension in extensions) + { + extensionManager.Load(extension); + } + } + + private ICommandLineCommand Parse(ICommandLineContext context) + { + var branding = context.ServiceProvider.GetService(); + var extensions = context.ExtensionManager.GetServices(); + + foreach (var extension in extensions) + { + extension.PreParse(context); + } + + ICommandLineCommand command = null; + var parser = context.Arguments.Parse(); + + while (command?.StopParsing != true && + String.IsNullOrEmpty(parser.ErrorArgument) && + parser.TryGetNextSwitchOrArgument(out var arg)) + { + if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments. + { + continue; + } + + // First argument must be the command or global switch (that creates a command). + if (command == null) + { + if (!this.TryParseCommand(arg, parser, extensions, out command)) + { + parser.ReportErrorArgument(arg); + } + } + else if (parser.IsSwitch(arg)) + { + if (!command.TryParseArgument(parser, arg) && !TryParseCommandLineArgumentWithExtension(arg, parser, extensions)) + { + parser.ReportErrorArgument(arg); + } + } + else if (!TryParseCommandLineArgumentWithExtension(arg, parser, extensions) && !command.TryParseArgument(parser, arg)) + { + parser.ReportErrorArgument(arg); + } + } + + foreach (var extension in extensions) + { + extension.PostParse(); + } + + return command ?? new HelpCommand(extensions, branding); + } + + private bool TryParseCommand(string arg, ICommandLineParser parser, IEnumerable extensions, out ICommandLineCommand command) + { + command = null; + + if (parser.IsSwitch(arg)) + { + var parameter = arg.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "?": + case "h": + case "help": + case "-help": + var branding = this.ServiceProvider.GetService(); + command = new HelpCommand(extensions, branding); + break; + + case "version": + case "-version": + command = new VersionCommand(); + break; + } + } + else + { + if (Enum.TryParse(arg, true, out CommandTypes commandType)) + { + switch (commandType) + { + case CommandTypes.Build: + command = new BuildCommand(this.ServiceProvider); + break; + + case CommandTypes.Compile: + command = new CompileCommand(this.ServiceProvider); + break; + + case CommandTypes.Decompile: + command = new DecompileCommand(this.ServiceProvider); + break; + } + } + else + { + foreach (var extension in extensions) + { + if (extension.TryParseCommand(parser, arg, out command)) + { + break; + } + + command = null; + } + } + } + + return command != null; + } + + private static bool TryParseCommandLineArgumentWithExtension(string arg, ICommandLineParser parse, IEnumerable extensions) + { + foreach (var extension in extensions) + { + if (extension.TryParseArgument(parse, arg)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs b/src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs new file mode 100644 index 00000000..40b8b320 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs @@ -0,0 +1,207 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CommandLineArguments : ICommandLineArguments + { + public CommandLineArguments(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + public string[] OriginalArguments { get; set; } + + public string[] Arguments { get; set; } + + public string[] Extensions { get; set; } + + public string ErrorArgument { get; set; } + + private IMessaging Messaging { get; } + + public void Populate(string commandLine) + { + var args = CommandLineArguments.ParseArgumentsToArray(commandLine); + + this.Populate(args.ToArray()); + } + + public void Populate(string[] args) + { + this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(args); + + this.ProcessArgumentsAndParseExtensions(this.OriginalArguments); + } + + public ICommandLineParser Parse() => new CommandLineParser(this.Messaging, this.Arguments, this.ErrorArgument); + + private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments) + { + var args = new List(); + + foreach (var arg in commandLineArguments) + { + if (arg != null) + { + if ('@' == arg[0]) + { + var responseFileArguments = CommandLineArguments.ParseResponseFile(arg.Substring(1)); + args.AddRange(responseFileArguments); + } + else + { + args.Add(arg); + } + } + } + + this.OriginalArguments = args.ToArray(); + } + + private void ProcessArgumentsAndParseExtensions(string[] args) + { + var arguments = new List(); + var extensions = new List(); + + for (var i = 0; i < args.Length; ++i) + { + var arg = args[i]; + + if ("-ext" == arg || "/ext" == arg) + { + if (!CommandLineArguments.IsSwitchAt(args, ++i)) + { + extensions.Add(args[i]); + } + else + { + this.ErrorArgument = arg; + break; + } + } + else + { + arguments.Add(arg); + } + } + + this.Arguments = arguments.ToArray(); + this.Extensions = extensions.ToArray(); + } + + private static List ParseResponseFile(string responseFile) + { + string arguments; + + using (var reader = new StreamReader(responseFile)) + { + arguments = reader.ReadToEnd(); + } + + return CommandLineArguments.ParseArgumentsToArray(arguments); + } + + private static List ParseArgumentsToArray(string arguments) + { + // Scan and parse the arguments string, dividing up the arguments based on whitespace. + // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed. + // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments. + // Escaped quotes and escaped backslashes also need to be unescaped by this process. + + // Collects the final list of arguments to be returned. + var argsList = new List(); + + // True if we are inside an unescaped quote, meaning whitespace should be ignored. + var insideQuote = false; + + // Index of the start of the current argument substring; either the start of the argument + // or the start of a quoted or unquoted sequence within it. + var partStart = 0; + + // The current argument string being built; when completed it will be added to the list. + var arg = new StringBuilder(); + + for (var i = 0; i <= arguments.Length; i++) + { + if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote)) + { + // Reached a whitespace separator or the end of the string. + + // Finish building the current argument. + arg.Append(arguments.Substring(partStart, i - partStart)); + + // Skip over the whitespace character. + partStart = i + 1; + + // Add the argument to the list if it's not empty. + if (arg.Length > 0) + { + argsList.Add(CommandLineArguments.ExpandEnvironmentVariables(arg.ToString())); + arg.Length = 0; + } + } + else if (i > partStart && arguments[i - 1] == '\\') + { + // Check the character following an unprocessed backslash. + // Unescape quotes, and backslashes followed by a quote. + if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"')) + { + // Unescape the quote or backslash by skipping the preceeding backslash. + arg.Append(arguments.Substring(partStart, i - 1 - partStart)); + arg.Append(arguments[i]); + partStart = i + 1; + } + } + else if (arguments[i] == '"') + { + // Add the quoted or unquoted section to the argument string. + arg.Append(arguments.Substring(partStart, i - partStart)); + + // And skip over the quote character. + partStart = i + 1; + + insideQuote = !insideQuote; + } + } + + return argsList; + } + + private static string ExpandEnvironmentVariables(string arguments) + { + var id = Environment.GetEnvironmentVariables(); + + var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)"); + var matches = regex.Matches(arguments); + + var value = String.Empty; + for (var i = 0; i <= (matches.Count - 1); i++) + { + try + { + var key = matches[i].Value; + regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)")); + value = id[key].ToString(); + arguments = regex.Replace(arguments, value); + } + catch (NullReferenceException) + { + // Collapse unresolved environment variables. + arguments = regex.Replace(arguments, value); + } + } + + return arguments; + } + + private static bool IsSwitchAt(string[] args, int index) => args.Length > index && !String.IsNullOrEmpty(args[index]) && ('/' == args[index][0] || '-' == args[index][0]); + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs b/src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs new file mode 100644 index 00000000..8d5cf120 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs @@ -0,0 +1,22 @@ +// 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.CommandLine +{ + using System; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CommandLineContext : ICommandLineContext + { + public CommandLineContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IExtensionManager ExtensionManager { get; set; } + + public ICommandLineArguments Arguments { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs b/src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs new file mode 100644 index 00000000..015d3e62 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs @@ -0,0 +1,270 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + internal class CommandLineParser : ICommandLineParser + { + private const string ExpectedArgument = "expected argument"; + + public string ErrorArgument { get; private set; } + + private Queue RemainingArguments { get; } + + private IMessaging Messaging { get; } + + public CommandLineParser(IMessaging messaging, string[] arguments, string errorArgument) + { + this.Messaging = messaging; + this.RemainingArguments = new Queue(arguments); + this.ErrorArgument = errorArgument; + } + + public bool IsSwitch(string arg) + { + return !String.IsNullOrEmpty(arg) && '-' == arg[0]; + } + + public string GetArgumentAsFilePathOrError(string argument, string fileType) + { + if (!File.Exists(argument)) + { + this.Messaging.Write(ErrorMessages.FileNotFound(null, argument, fileType)); + return null; + } + + return argument; + } + + public void GetArgumentAsFilePathOrError(string argument, string fileType, IList paths) + { + foreach (var path in this.GetFiles(argument, fileType)) + { + paths.Add(path); + } + } + + public string GetNextArgumentOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var argument)) + { + return argument; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } + + public bool GetNextArgumentOrError(string commandLineSwitch, IList args) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) + { + args.Add(arg); + return true; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; + } + + public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory)) + { + return directory; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } + + public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList directories) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory)) + { + directories.Add(directory); + return true; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; + } + + public string GetNextArgumentAsFilePathOrError(string commandLineSwitch) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path)) + { + return path; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return null; + } + + public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList paths) + { + if (this.TryGetNextNonSwitchArgumentOrError(out var arg)) + { + foreach (var path in this.GetFiles(arg, fileType)) + { + paths.Add(path); + } + + return true; + } + + this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch)); + return false; + } + + public void ReportErrorArgument(string argument, Message message = null) + { + this.Messaging.Write(message ?? ErrorMessages.AdditionalArgumentUnexpected(argument)); + this.ErrorArgument = argument; + } + + public bool TryGetNextSwitchOrArgument(out string arg) + { + if (this.RemainingArguments.Count > 0) + { + arg = this.RemainingArguments.Dequeue(); + return true; + } + + arg = null; + return false; + } + + private bool TryGetNextNonSwitchArgumentOrError(out string arg) + { + var result = this.TryGetNextSwitchOrArgument(out arg); + + if (!result || this.IsSwitch(arg)) + { + this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument; + return false; + } + + return result; + } + + private bool TryGetDirectory(string commandlineSwitch, string arg, out string directory) + { + directory = null; + + if (File.Exists(arg)) + { + this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg)); + return false; + } + + directory = this.VerifyPath(arg); + return directory != null; + } + + private bool TryGetFile(string commandlineSwitch, string arg, out string path) + { + path = null; + + if (String.IsNullOrEmpty(arg) || '-' == arg[0]) + { + this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch)); + } + else if (Directory.Exists(arg)) + { + this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg)); + } + else + { + path = this.VerifyPath(arg); + } + + return path != null; + } + + /// + /// Get a set of files that possibly have a search pattern in the path (such as '*'). + /// + /// Search path to find files in. + /// Type of file; typically "Source". + /// An array of files matching the search path. + /// + /// This method is written in this verbose way because it needs to support ".." in the path. + /// It needs the directory path isolated from the file name in order to use Directory.GetFiles + /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since + /// Path.GetDirectoryName does not support ".." in the path. + /// + private string[] GetFiles(string searchPath, string fileType) + { + if (null == searchPath) + { + throw new ArgumentNullException(nameof(searchPath)); + } + + // Convert alternate directory separators to the standard one. + var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar); + var files = new string[0]; + + try + { + if (0 > lastSeparator) + { + files = Directory.GetFiles(".", filePath); + } + else // found directory separator + { + files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1)); + } + } + catch (DirectoryNotFoundException) + { + // Don't let this function throw the DirectoryNotFoundException. This exception + // occurs for non-existant directories and invalid characters in the searchPattern. + } + catch (ArgumentException) + { + // Don't let this function throw the ArgumentException. This exception + // occurs in certain situations such as when passing a malformed UNC path. + } + catch (IOException) + { + } + + if (0 == files.Length) + { + this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType)); + } + + return files; + } + + private string VerifyPath(string path) + { + string fullPath; + + if (0 <= path.IndexOf('\"')) + { + this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path)); + return null; + } + + try + { + fullPath = Path.GetFullPath(path); + } + catch (Exception e) + { + this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message)); + return null; + } + + return fullPath; + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/CompileCommand.cs b/src/wix/WixToolset.Core/CommandLine/CompileCommand.cs new file mode 100644 index 00000000..6e31b241 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/CompileCommand.cs @@ -0,0 +1,94 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class CompileCommand : ICommandLineCommand + { + public CompileCommand(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + this.ExtensionManager = serviceProvider.GetService(); + } + + public CompileCommand(IServiceProvider serviceProvider, IEnumerable sources, IDictionary preprocessorVariables, Platform platform) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + this.ExtensionManager = serviceProvider.GetService(); + this.SourceFiles = sources; + this.PreprocessorVariables = preprocessorVariables; + this.Platform = platform; + } + + private IServiceProvider ServiceProvider { get; } + + public IMessaging Messaging { get; } + + public IExtensionManager ExtensionManager { get; } + + private IEnumerable SourceFiles { get; } + + private IDictionary PreprocessorVariables { get; } + + private Platform Platform { get; } + + public IReadOnlyCollection IncludeSearchPaths { get; } + + public bool ShowLogo => throw new NotImplementedException(); + + public bool StopParsing => throw new NotImplementedException(); + + public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => throw new NotImplementedException(); + + public Task ExecuteAsync(CancellationToken _) + { + foreach (var sourceFile in this.SourceFiles) + { + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ExtensionManager.GetServices(); + context.Platform = this.Platform; + context.IncludeSearchPaths = this.IncludeSearchPaths; + context.SourcePath = sourceFile.SourcePath; + context.Variables = this.PreprocessorVariables; + + IPreprocessResult result = null; + try + { + var preprocessor = this.ServiceProvider.GetService(); + result = preprocessor.Preprocess(context); + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + if (this.Messaging.EncounteredError) + { + continue; + } + + var compileContext = this.ServiceProvider.GetService(); + compileContext.Extensions = this.ExtensionManager.GetServices(); + compileContext.Platform = this.Platform; + compileContext.Source = result?.Document; + + var compiler = this.ServiceProvider.GetService(); + var intermediate = compiler.Compile(compileContext); + + intermediate.Save(sourceFile.OutputPath); + } + + return Task.FromResult(0); + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs b/src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs new file mode 100644 index 00000000..fc0ab0c9 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs @@ -0,0 +1,256 @@ +// 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.CommandLine +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class DecompileCommand : ICommandLineCommand + { + private readonly CommandLine commandLine; + + public DecompileCommand(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + this.commandLine = new CommandLine(this.Messaging); + } + + public bool ShowLogo => this.commandLine.ShowLogo; + + public bool StopParsing => this.commandLine.ShowHelp; + + private IServiceProvider ServiceProvider { get; } + + public IMessaging Messaging { get; } + + public Task ExecuteAsync(CancellationToken _) + { + if (this.commandLine.ShowHelp || String.IsNullOrEmpty(this.commandLine.DecompileFilePath)) + { + Console.WriteLine("TODO: Show decompile command help"); + return Task.FromResult(-1); + } + + var context = this.ServiceProvider.GetService(); + context.Extensions = this.ServiceProvider.GetService().GetServices(); + context.DecompilePath = this.commandLine.DecompileFilePath; + context.DecompileType = this.commandLine.CalculateDecompileType(); + context.IntermediateFolder = this.commandLine.CalculateIntermedateFolder(); + context.OutputPath = this.commandLine.CalculateOutputPath(); + + try + { + var decompiler = this.ServiceProvider.GetService(); + var result = decompiler.Decompile(context); + + if (!this.Messaging.EncounteredError) + { + Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(context.OutputPath))); + result.Document.Save(context.OutputPath, SaveOptions.OmitDuplicateNamespaces); + } + } + catch (WixException e) + { + this.Messaging.Write(e.Error); + } + + if (this.Messaging.EncounteredError) + { + return Task.FromResult(1); + } + + return Task.FromResult(0); + } + + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + return this.commandLine.TryParseArgument(argument, parser); + } + + private class CommandLine + { + public CommandLine(IMessaging messaging) + { + this.Messaging = messaging; + } + + private IMessaging Messaging { get; } + + public string DecompileFilePath { get; private set; } + + public string DecompileType { get; private set; } + + public Platform Platform { get; private set; } + + public bool ShowLogo { get; private set; } + + public bool ShowHelp { get; private set; } + + public string IntermediateFolder { get; private set; } + + public string OutputFile { get; private set; } + + public bool TryParseArgument(string arg, ICommandLineParser parser) + { + if (parser.IsSwitch(arg)) + { + var parameter = arg.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "?": + case "h": + case "help": + this.ShowHelp = true; + return true; + + case "intermediatefolder": + this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); + return true; + + case "o": + case "out": + this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg); + return true; + + case "nologo": + this.ShowLogo = false; + return true; + + case "v": + case "verbose": + this.Messaging.ShowVerboseMessages = true; + return true; + } + + if (parameter.StartsWith("sw")) + { + this.ParseSuppressWarning(parameter, "sw".Length, parser); + return true; + } + else if (parameter.StartsWith("suppresswarning")) + { + this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser); + return true; + } + else if (parameter.StartsWith("wx")) + { + this.ParseWarningAsError(parameter, "wx".Length, parser); + return true; + } + } + else + { + if (String.IsNullOrEmpty(this.DecompileFilePath)) + { + this.DecompileFilePath = parser.GetArgumentAsFilePathOrError(arg, "decompile file"); + return true; + } + else if (String.IsNullOrEmpty(this.OutputFile)) + { + this.OutputFile = parser.GetArgumentAsFilePathOrError(arg, "output file"); + return true; + } + } + + return false; + } + + public OutputType CalculateDecompileType() + { + if (String.IsNullOrEmpty(this.DecompileType)) + { + this.DecompileType = Path.GetExtension(this.DecompileFilePath); + } + + switch (this.DecompileType.ToLowerInvariant()) + { + case "bundle": + case ".exe": + return OutputType.Bundle; + + case "library": + case ".wixlib": + return OutputType.Library; + + case "module": + case ".msm": + return OutputType.Module; + + case "patch": + case ".msp": + return OutputType.Patch; + + case ".pcp": + return OutputType.PatchCreation; + + case "product": + case "package": + case ".msi": + return OutputType.Product; + + case "transform": + case ".mst": + return OutputType.Transform; + + case "intermediatepostlink": + case ".wixipl": + return OutputType.IntermediatePostLink; + } + + return OutputType.Unknown; + } + + public string CalculateIntermedateFolder() + { + return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder; + } + + public string CalculateOutputPath() + { + return String.IsNullOrEmpty(this.OutputFile) ? Path.ChangeExtension(this.DecompileFilePath, ".wxs") : this.OutputFile; + } + + private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser) + { + var paramArg = parameter.Substring(offset); + if (paramArg.Length == 0) + { + this.Messaging.SuppressAllWarnings = true; + } + else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0) + { + this.Messaging.SuppressWarningMessage(suppressWarning); + } + else + { + parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg)); + } + } + + private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser) + { + var paramArg = parameter.Substring(offset); + if (paramArg.Length == 0) + { + this.Messaging.WarningsAsError = true; + } + else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0) + { + this.Messaging.ElevateWarningMessage(elevateWarning); + } + else + { + parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg)); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/HelpCommand.cs b/src/wix/WixToolset.Core/CommandLine/HelpCommand.cs new file mode 100644 index 00000000..6a5ac183 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/HelpCommand.cs @@ -0,0 +1,66 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class HelpCommand : ICommandLineCommand + { + private static readonly ExtensionCommandLineSwitch[] BuiltInSwitches = new ExtensionCommandLineSwitch[] + { + new ExtensionCommandLineSwitch { Switch = "build", Description = "Build a wixlib, package or bundle." }, + new ExtensionCommandLineSwitch { Switch = "decompile", Description = "Decompile a package or bundle into source code." }, + }; + + public HelpCommand(IEnumerable extensions, IWixBranding branding) + { + this.Extensions = extensions; + this.Branding = branding; + } + + public bool ShowLogo => true; + + public bool StopParsing => true; + + private IEnumerable Extensions { get; } + + private IWixBranding Branding { get; } + + public Task ExecuteAsync(CancellationToken _) + { + var commandLineSwitches = new List(BuiltInSwitches); + commandLineSwitches.AddRange(this.Extensions.SelectMany(e => e.CommandLineSwitches).OrderBy(s => s.Switch, StringComparer.Ordinal)); + + Console.WriteLine(); + Console.WriteLine("Usage: wix [option]"); + Console.WriteLine("Usage: wix [command]"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" -h|--help Show command line help."); + Console.WriteLine(" --version Display WiX Toolset version in use."); + Console.WriteLine(); + + Console.WriteLine("Commands:"); + foreach (var commandLineSwitch in commandLineSwitches) + { + Console.WriteLine(" {0,-17} {1}", commandLineSwitch.Switch, commandLineSwitch.Description); + } + + Console.WriteLine(); + Console.WriteLine("Run 'wix [command] --help' for more information on a command."); + Console.WriteLine(); + Console.WriteLine(this.Branding.ReplacePlaceholders("For more information see: [SupportUrl]")); + + return Task.FromResult(-1); + } + + public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments + } +} diff --git a/src/wix/WixToolset.Core/CommandLine/VersionCommand.cs b/src/wix/WixToolset.Core/CommandLine/VersionCommand.cs new file mode 100644 index 00000000..01a7d0e6 --- /dev/null +++ b/src/wix/WixToolset.Core/CommandLine/VersionCommand.cs @@ -0,0 +1,26 @@ +// 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.CommandLine +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class VersionCommand : ICommandLineCommand + { + public bool ShowLogo => true; + + public bool StopParsing => true; + + public Task ExecuteAsync(CancellationToken cancellationToken) + { + Console.WriteLine(ThisAssembly.AssemblyInformationalVersion); + + return Task.FromResult(0); + } + + public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments + } +} diff --git a/src/wix/WixToolset.Core/Common.cs b/src/wix/WixToolset.Core/Common.cs new file mode 100644 index 00000000..848f009a --- /dev/null +++ b/src/wix/WixToolset.Core/Common.cs @@ -0,0 +1,832 @@ +// 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 +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using System.Xml; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + /// + /// Common Wix utility methods and types. + /// + internal static class Common + { + private static readonly char[] IllegalShortFilenameCharacters = new[] { '\\', '?', '|', '>', '<', ':', '/', '*', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; + private static readonly char[] IllegalWildcardShortFilenameCharacters = new[] { '\\', '|', '>', '<', ':', '/', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' }; + + internal static readonly char[] IllegalLongFilenameCharacters = new[] { '\\', '/', '?', '*', '|', '>', '<', ':', '\"' }; // illegal: \ / ? | > < : / * " + internal static readonly char[] IllegalRelativeLongFilenameCharacters = new[] { '?', '*', '|', '>', '<', ':', '\"' }; // like illegal, but we allow '\' and '/' + internal static readonly char[] IllegalWildcardLongFilenameCharacters = new[] { '\\', '/', '|', '>', '<', ':', '\"' }; // like illegal: but we allow '*' and '?' + + public static string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath, IMessaging messageHandler) + { + const string root = @"C:\"; + if (!Path.IsPathRooted(relativePath)) + { + var normalizedPath = Path.GetFullPath(root + relativePath); + if (normalizedPath.StartsWith(root)) + { + var canonicalizedPath = normalizedPath.Substring(root.Length); + if (canonicalizedPath != relativePath) + { + messageHandler.Write(WarningMessages.PathCanonicalized(sourceLineNumbers, elementName, attributeName, relativePath, canonicalizedPath)); + } + return canonicalizedPath; + } + } + + messageHandler.Write(ErrorMessages.PayloadMustBeRelativeToCache(sourceLineNumbers, elementName, attributeName, relativePath)); + return relativePath; + } + + /// + /// Gets a valid code page from the given web name or integer value. + /// + /// A code page web name or integer value as a string. + /// Whether to allow -1 which does not change the database code pages. This may be the case with wxl files. + /// Whether to allow Unicode (UCS) or UTF code pages. + /// Source line information for the current authoring. + /// A valid code page number. + /// The value is an integer less than 0 or greater than 65535. + /// is null. + /// The value doesn't not represent a valid code page name or integer value. + /// The code page is invalid for summary information. + public static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + try + { + Encoding encoding; + + // Check if a integer as a string was passed. + if (Int32.TryParse(value, out var codePage)) + { + if (0 == codePage) + { + // 0 represents a neutral database + return 0; + } + else if (allowNoChange && -1 == codePage) + { + // -1 means no change to the database code page + return -1; + } + + encoding = Encoding.GetEncoding(codePage); + } + else + { + encoding = Encoding.GetEncoding(value); + } + + // Windows Installer parses some code page references + // as unsigned shorts which fail to open the database. + if (onlyAnsi) + { + codePage = encoding.CodePage; + if (0 > codePage || Int16.MaxValue < codePage) + { + throw new WixException(ErrorMessages.InvalidSummaryInfoCodePage(sourceLineNumbers, codePage)); + } + } + + if (encoding == null) + { + throw new WixException(ErrorMessages.IllegalCodepage(sourceLineNumbers, codePage)); + } + + return encoding.CodePage; + } + catch (ArgumentException ex) + { + // Rethrow as NotSupportedException since either can be thrown + // if the system does not support the specified code page. + throw new NotSupportedException(ex.Message, ex); + } + } + + /// + /// Verifies if an identifier is a valid binder variable name. + /// + /// Binder variable name to verify. + /// True if the identifier is a valid binder variable name. + public static bool IsValidBinderVariable(string variable) + { + return TryParseWixVariable(variable, 0, out var parsed) && parsed.Index == 0 && parsed.Length == variable.Length && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); + } + + /// + /// Verifies if a string contains a valid binder variable name. + /// + /// String to verify. + /// True if the string contains a valid binder variable name. + public static bool ContainsValidBinderVariable(string verify) + { + return TryParseWixVariable(verify, 0, out var parsed) && (parsed.Namespace == "bind" || parsed.Namespace == "wix"); + } + + /// + /// Verifies the given string is a valid 4-part version module or bundle version. + /// + /// The version to verify. + /// True if version is a valid module or bundle version. + public static bool IsValidFourPartVersion(string version) + { + if (!Common.IsValidBinderVariable(version)) + { + if (!Version.TryParse(version, out var ver) || 65535 < ver.Major || 65535 < ver.Minor || 65535 < ver.Build || 65535 < ver.Revision) + { + return false; + } + } + + return true; + } + + public static bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) + { + if (String.IsNullOrEmpty(filename)) + { + return false; + } + else if (filename.Length > 259) + { + return false; + } + + // Check for a non-period character (all periods is not legal) + var allPeriods = true; + foreach (var character in filename) + { + if ('.' != character) + { + allPeriods = false; + break; + } + } + + if (allPeriods) + { + return false; + } + + if (allowWildcards) + { + return filename.IndexOfAny(Common.IllegalWildcardLongFilenameCharacters) == -1; + } + else if (allowRelative) + { + return filename.IndexOfAny(Common.IllegalRelativeLongFilenameCharacters) == -1; + } + else + { + return filename.IndexOfAny(Common.IllegalLongFilenameCharacters) == -1; + } + } + + public static bool IsValidShortFilename(string filename, bool allowWildcards) + { + if (String.IsNullOrEmpty(filename)) + { + return false; + } + + if (allowWildcards) + { + var expectedDot = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters); + if (expectedDot == -1) + { + } + else if (filename[expectedDot] != '.') + { + return false; + } + else if (expectedDot < filename.Length) + { + var extensionInvalids = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters, expectedDot + 1); + if (extensionInvalids != -1) + { + return false; + } + } + + var foundPeriod = false; + var beforePeriod = 0; + var afterPeriod = 0; + + // count the number of characters before and after the period + // '*' is not counted because it may represent zero characters + foreach (var character in filename) + { + if ('.' == character) + { + foundPeriod = true; + } + else if ('*' != character) + { + if (foundPeriod) + { + afterPeriod++; + } + else + { + beforePeriod++; + } + } + } + + if (8 >= beforePeriod && 3 >= afterPeriod) + { + return true; + } + + return false; + } + else + { + if (filename.Length > 12) + { + return false; + } + + var expectedDot = filename.IndexOfAny(IllegalShortFilenameCharacters); + if (expectedDot == -1) + { + return filename.Length < 9; + } + else if (expectedDot > 8 || filename[expectedDot] != '.' || expectedDot + 4 < filename.Length) + { + return false; + } + + var validExtension = filename.IndexOfAny(IllegalShortFilenameCharacters, expectedDot + 1); + return validExtension == -1; + } + } + + /// + /// Generate a new Windows Installer-friendly guid. + /// + /// A new guid. + public static string GenerateGuid() + { + return Guid.NewGuid().ToString("B").ToUpperInvariant(); + } + + /// + /// Generate an identifier by hashing data from the row. + /// + /// Three letter or less prefix for generated row identifier. + /// Information to hash. + /// The generated identifier. + public static string GenerateIdentifier(string prefix, params string[] args) + { + string base64; + + using (var sha1 = new SHA1CryptoServiceProvider()) + { + var combined = String.Join("|", args); + var data = Encoding.UTF8.GetBytes(combined); + var hash = sha1.ComputeHash(data); + base64 = Convert.ToBase64String(hash); + } + + var identifier = new StringBuilder(32); + identifier.Append(prefix); + identifier.Append(base64); + identifier.Length -= 1; // removes the trailing '=' from base64 + identifier.Replace('+', '.'); + identifier.Replace('/', '_'); + + return identifier.ToString(); + } + + /// + /// Return an identifier based on provided file or directory name + /// + /// File/directory name to generate identifer from + /// A version of the name that is a legal identifier. + internal static string GetIdentifierFromName(string name) + { + StringBuilder sb = null; + var offset = 0; + + // MSI identifiers must begin with an alphabetic character or an + // underscore. Prefix all other values with an underscore. + if (!ValidIdentifierChar(name[0], true)) + { + sb = new StringBuilder("_" + name); + offset = 1; + } + + for (var i = 0; i < name.Length; ++i) + { + if (!ValidIdentifierChar(name[i], false)) + { + if (sb == null) + { + sb = new StringBuilder(name); + } + + sb[i + offset] = '_'; + } + } + + return sb?.ToString() ?? name; + } + + /// + /// Checks if the string contains a property (i.e. "foo[Property]bar") + /// + /// String to evaluate for properties. + /// True if a property is found in the string. + internal static bool ContainsProperty(string possibleProperty) + { + var start = possibleProperty.IndexOf('['); + if (start != -1 && start < possibleProperty.Length - 2) + { + var end = possibleProperty.IndexOf(']', start + 1); + if (end > start + 1) + { + // Skip supported property modifiers. + if (possibleProperty[start + 1] == '#' || possibleProperty[start + 1] == '$' || possibleProperty[start + 1] == '!') + { + ++start; + } + + var id = possibleProperty.Substring(start + 1, end - 1); + + if (Common.IsIdentifier(id)) + { + return true; + } + } + } + + return false; + } + + /// + /// Recursively loops through a directory, changing an attribute on all of the underlying files. + /// An example is to add/remove the ReadOnly flag from each file. + /// + /// The directory path to start deleting from. + /// The FileAttribute to change on each file. + /// The message handler. + /// If true, add the attribute to each file. If false, remove it. + private static void RecursiveFileAttributes(string path, FileAttributes fileAttribute, bool markAttribute, IMessaging messageHandler) + { + foreach (var subDirectory in Directory.GetDirectories(path)) + { + RecursiveFileAttributes(subDirectory, fileAttribute, markAttribute, messageHandler); + } + + foreach (var filePath in Directory.GetFiles(path)) + { + var attributes = File.GetAttributes(filePath); + if (markAttribute) + { + attributes = attributes | fileAttribute; // add to list of attributes + } + else if (fileAttribute == (attributes & fileAttribute)) // if attribute set + { + attributes = attributes ^ fileAttribute; // remove from list of attributes + } + + try + { + File.SetAttributes(filePath, attributes); + } + catch (UnauthorizedAccessException) + { + messageHandler.Write(WarningMessages.AccessDeniedForSettingAttributes(null, filePath)); + } + } + } + + /// + /// Takes an id, and demodularizes it (if possible). + /// + /// + /// If the output type is a module, returns a demodularized version of an id. Otherwise, returns the id. + /// + /// The type of the output to bind. + /// The modularization GUID. + /// The id to demodularize. + /// The demodularized id. + public static string Demodularize(OutputType outputType, string modularizationGuid, string id) + { + if (OutputType.Module == outputType && id.EndsWith(String.Concat(".", modularizationGuid), StringComparison.Ordinal)) + { + id = id.Substring(0, id.Length - 37); + } + + return id; + } + + /// + /// Get the source/target and short/long file names from an MSI Filename column. + /// + /// The Filename value. + /// An array of strings of length 4. The contents are: short target, long target, short source, and long source. + /// + /// If any particular file name part is not parsed, its set to null in the appropriate location of the returned array of strings. + /// Thus the returned array will always be of length 4. + /// + public static string[] GetNames(string value) + { + var targetSeparator = value.IndexOf(':'); + + // split source and target + string sourceName = null; + var targetName = value; + if (0 <= targetSeparator) + { + sourceName = value.Substring(targetSeparator + 1); + targetName = value.Substring(0, targetSeparator); + } + + // split the source short and long names + string sourceLongName = null; + if (null != sourceName) + { + var sourceLongNameSeparator = sourceName.IndexOf('|'); + if (0 <= sourceLongNameSeparator) + { + sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); + sourceName = sourceName.Substring(0, sourceLongNameSeparator); + } + } + + // split the target short and long names + var targetLongNameSeparator = targetName.IndexOf('|'); + string targetLongName = null; + if (0 <= targetLongNameSeparator) + { + targetLongName = targetName.Substring(targetLongNameSeparator + 1); + targetName = targetName.Substring(0, targetLongNameSeparator); + } + + // Remove the long source name when its identical to the short source name. + if (null != sourceName && sourceName == sourceLongName) + { + sourceLongName = null; + } + + // Remove the long target name when its identical to the long target name. + if (null != targetName && targetName == targetLongName) + { + targetLongName = null; + } + + // Remove the source names when they are identical to the target names. + if (sourceName == targetName && sourceLongName == targetLongName) + { + sourceName = null; + sourceLongName = null; + } + + // target name(s) + if ("." == targetName) + { + targetName = null; + } + + if ("." == targetLongName) + { + targetLongName = null; + } + + // source name(s) + if ("." == sourceName) + { + sourceName = null; + } + + if ("." == sourceLongName) + { + sourceLongName = null; + } + + return new[] { targetName, targetLongName, sourceName, sourceLongName }; + } + + /// + /// Get a source/target and short/long file name from an MSI Filename column. + /// + /// The Filename value. + /// true to get a source name; false to get a target name + /// true to get a long name; false to get a short name + /// The name. + public static string GetName(string value, bool source, bool longName) + { + var names = GetNames(value); + + if (source) + { + if (longName && null != names[3]) + { + return names[3]; + } + else if (null != names[2]) + { + return names[2]; + } + } + + if (longName && null != names[1]) + { + return names[1]; + } + else + { + return names[0]; + } + } + + /// + /// Get an attribute value. + /// + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// A rule for the contents of the value. If the contents do not follow the rule, an error is thrown. + /// The attribute's value. + internal static string GetAttributeValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule) + { + var value = attribute.Value; + + if ((emptyRule == EmptyRule.MustHaveNonWhitespaceCharacters && String.IsNullOrEmpty(value.Trim())) || + (emptyRule == EmptyRule.CanBeWhitespaceOnly && String.IsNullOrEmpty(value))) + { + messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); + return String.Empty; + } + + return value; + } + + /// + /// Verifies that a value is a legal identifier. + /// + /// The value to verify. + /// true if the value is an identifier; false otherwise. + public static bool IsIdentifier(string value) + { + if (String.IsNullOrEmpty(value)) + { + return false; + } + + for (var i = 0; i < value.Length; ++i) + { + if (!ValidIdentifierChar(value[i], i == 0)) + { + return false; + } + } + + return true; + } + + /// + /// Get an identifier attribute value and displays an error for an illegal identifier value. + /// + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's identifier value or a special value if an error occurred. + internal static string GetAttributeIdentifierValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); + + if (Common.IsIdentifier(value)) + { + if (72 < value.Length) + { + messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + + return value; + } + else + { + if (value.StartsWith("[", StringComparison.Ordinal) && value.EndsWith("]", StringComparison.Ordinal)) + { + messaging.Write(ErrorMessages.IllegalIdentifierLooksLikeFormatted(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + else + { + messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + + return String.Empty; + } + } + + /// + /// Get an integer attribute value and displays an error for an illegal integer value. + /// + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The minimum legal value. + /// The maximum legal value. + /// The attribute's integer value or a special value if an error occurred during conversion. + public static int GetAttributeIntegerValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) + { + Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); + + var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); + var integer = CompilerConstants.IllegalInteger; + + if (0 < value.Length) + { + if (Int32.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out integer)) + { + if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) + { + messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); + } + else if (minimum > integer || maximum < integer) + { + messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); + integer = CompilerConstants.IllegalInteger; + } + } + else + { + messaging.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return integer; + } + + /// + /// Gets a yes/no value and displays an error for an illegal yes/no value. + /// + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's YesNoType value. + internal static YesNoType GetAttributeYesNoValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly); + var yesNo = YesNoType.IllegalValue; + + if ("yes".Equals(value) || "true".Equals(value)) + { + yesNo = YesNoType.Yes; + } + else if ("no".Equals(value) || "false".Equals(value)) + { + yesNo = YesNoType.No; + } + else + { + messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + + return yesNo; + } + + /// + /// Gets the text of an XElement. + /// + /// Element to get text. + /// The element's text. + internal static string GetInnerText(XElement node) + { + var text = node.Nodes().Where(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType).Cast().FirstOrDefault(); + return text?.Value; + } + + internal static bool TryParseWixVariable(string value, int start, out ParsedWixVariable parsedVariable) + { + parsedVariable = null; + + if (String.IsNullOrEmpty(value) || start >= value.Length) + { + return false; + } + + var startWixVariable = value.IndexOf("!(", start, StringComparison.Ordinal); + if (startWixVariable == -1) + { + return false; + } + + var firstDot = value.IndexOf('.', startWixVariable + 1); + if (firstDot == -1) + { + return false; + } + + var ns = value.Substring(startWixVariable + 2, firstDot - startWixVariable - 2); + if (ns != "loc" && ns != "bind" && ns != "wix") + { + return false; + } + + var closeParen = value.IndexOf(')', firstDot); + if (closeParen == -1) + { + return false; + } + + string name; + string scope = null; + string defaultValue = null; + + var equalsDefaultValue = value.IndexOf('=', firstDot + 1, closeParen - firstDot); + var end = equalsDefaultValue == -1 ? closeParen : equalsDefaultValue; + var secondDot = value.IndexOf('.', firstDot + 1, end - firstDot); + + if (secondDot == -1) + { + name = value.Substring(firstDot + 1, end - firstDot - 1); + } + else + { + name = value.Substring(firstDot + 1, secondDot - firstDot - 1); + scope = value.Substring(secondDot + 1, end - secondDot - 1); + + if (!Common.IsIdentifier(scope)) + { + return false; + } + } + + if (!Common.IsIdentifier(name)) + { + return false; + } + + if (equalsDefaultValue != -1 && equalsDefaultValue < closeParen) + { + defaultValue = value.Substring(equalsDefaultValue + 1, closeParen - equalsDefaultValue - 1); + } + + parsedVariable = new ParsedWixVariable + { + Index = startWixVariable, + Length = closeParen - startWixVariable + 1, + Namespace = ns, + Name = name, + Scope = scope, + DefaultValue = defaultValue + }; + + return true; + } + + /// + /// Display an unexpected attribute error. + /// + /// + /// Source line information about the owner element. + /// The attribute. + public static void UnexpectedAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + // Ignore elements defined by the W3C because we'll assume they are always right. + if (!((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || + attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))) + { + messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); + } + } + + /// + /// Display an unsupported extension attribute error. + /// + /// + /// Source line information about the owner element. + /// The extension attribute. + internal static void UnsupportedExtensionAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute extensionAttribute) + { + // Ignore elements defined by the W3C because we'll assume they are always right. + if (!((String.IsNullOrEmpty(extensionAttribute.Name.NamespaceName) && extensionAttribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || + extensionAttribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))) + { + messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, extensionAttribute.Parent.Name.LocalName, extensionAttribute.Name.LocalName)); + } + } + + private static bool ValidIdentifierChar(char c, bool firstChar) + { + return ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || '_' == c || + (!firstChar && (Char.IsDigit(c) || '.' == c)); + } + } +} diff --git a/src/wix/WixToolset.Core/Compile/CompilerPayload.cs b/src/wix/WixToolset.Core/Compile/CompilerPayload.cs new file mode 100644 index 00000000..3f423034 --- /dev/null +++ b/src/wix/WixToolset.Core/Compile/CompilerPayload.cs @@ -0,0 +1,291 @@ +// 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 +{ + using System; + using System.IO; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + + internal class CompilerPayload + { + public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default; + + public string Description { get; set; } + + public string DownloadUrl { get; set; } + + public string Hash { get; set; } + + public Identifier Id { get; set; } + + public bool IsRemoteAllowed { get; set; } + + public bool IsRequired { get; set; } = true; + + public string Name { get; set; } + + public string ProductName { get; set; } + + public long? Size { get; set; } + + public string SourceFile { get; set; } + + public string Version { get; set; } + + public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element) + { + this.Core = core; + this.Element = element; + this.SourceLineNumbers = sourceLineNumbers; + } + + private CompilerCore Core { get; } + + private XElement Element { get; } + + private SourceLineNumber SourceLineNumbers { get; } + + private void CalculateAndVerifyFields() + { + var isRemote = this.IsRemoteAllowed && !String.IsNullOrEmpty(this.Hash); + + if (String.IsNullOrEmpty(this.SourceFile)) + { + if (!String.IsNullOrEmpty(this.Name) && !isRemote) + { + this.SourceFile = Path.Combine("SourceDir", this.Name); + } + } + else if (this.SourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + if (String.IsNullOrEmpty(this.Name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile", this.SourceFile)); + } + else + { + this.SourceFile = Path.Combine(this.SourceFile, Path.GetFileName(this.Name)); + } + } + + if (String.IsNullOrEmpty(this.SourceFile) && !isRemote) + { + if (this.IsRequired) + { + if (!this.IsRemoteAllowed) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile")); + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "Hash")); + } + } + } + else if (this.IsRemoteAllowed) + { + var isLocal = !String.IsNullOrEmpty(this.SourceFile); + + if (isLocal) + { + if (isRemote) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile")); + } + + if (!String.IsNullOrEmpty(this.Description)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Description", "SourceFile")); + } + + if (!String.IsNullOrEmpty(this.ProductName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "ProductName", "SourceFile")); + } + + if (this.Size.HasValue) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "SourceFile")); + } + + if (!String.IsNullOrEmpty(this.Version)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Version", "SourceFile")); + } + } + else + { + if (String.IsNullOrEmpty(this.DownloadUrl)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "Hash")); + } + + if (String.IsNullOrEmpty(this.Name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "Hash")); + } + + if (!this.Size.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "Hash")); + } + + if (YesNoDefaultType.Yes == this.Compressed) + { + this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName)); + } + + this.Compressed = YesNoDefaultType.No; + } + } + } + + public WixBundlePayloadSymbol CreatePayloadSymbol(ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown, string previousId = null) + { + WixBundlePayloadSymbol symbol = null; + + if (parentType == ComplexReferenceParentType.Container && parentId == BurnConstants.BurnUXContainerName) + { + if (this.Compressed == YesNoDefaultType.No) + { + this.Core.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(this.SourceLineNumbers, this.SourceFile)); + } + + if (!String.IsNullOrEmpty(this.DownloadUrl)) + { + this.Core.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(this.SourceLineNumbers, this.Id.Id)); + } + + this.Compressed = YesNoDefaultType.Yes; + this.DownloadUrl = null; + } + + if (!this.Core.EncounteredError) + { + symbol = this.Core.AddSymbol(new WixBundlePayloadSymbol(this.SourceLineNumbers, this.Id) + { + Name = String.IsNullOrEmpty(this.Name) ? Path.GetFileName(this.SourceFile) : this.Name, + SourceFile = new IntermediateFieldPathValue { Path = this.SourceFile }, + DownloadUrl = this.DownloadUrl, + Compressed = (this.Compressed == YesNoDefaultType.Yes) ? true : (this.Compressed == YesNoDefaultType.No) ? (bool?)false : null, + UnresolvedSourceFile = this.SourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. + DisplayName = this.ProductName, + Description = this.Description, + Hash = this.Hash, + FileSize = this.Size, + Version = this.Version, + }); + + this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, previousType, previousId); + } + + return symbol; + } + + public void FinishCompilingPackage() + { + this.CalculateAndVerifyFields(); + this.GenerateIdFromFilename(); + + if (this.Id == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Id")); + this.Id = Identifier.Invalid; + } + } + + public void FinishCompilingPackagePayload() + { + this.CalculateAndVerifyFields(); + this.GenerateIdFromFilename(); + this.GenerateIdFromPrefix("ppy"); + } + + public void FinishCompilingPayload() + { + this.CalculateAndVerifyFields(); + this.GenerateIdFromPrefix("pay"); + } + + private void GenerateIdFromFilename() + { + if (this.Id == null) + { + if (!String.IsNullOrEmpty(this.Name)) + { + this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.Name)); + } + else if (!String.IsNullOrEmpty(this.SourceFile)) + { + this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.SourceFile)); + } + } + } + + private void GenerateIdFromPrefix(string prefix) + { + if (this.Id == null) + { + this.Id = this.Core.CreateIdentifier(prefix, this.SourceFile?.ToUpperInvariant() ?? String.Empty); + } + } + + public void ParseCompressed(XAttribute attrib) + { + this.Compressed = this.Core.GetAttributeYesNoDefaultValue(this.SourceLineNumbers, attrib); + } + + public void ParseDescription(XAttribute attrib) + { + this.Description = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseDownloadUrl(XAttribute attrib) + { + this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseHash(XAttribute attrib) + { + this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseId(XAttribute attrib) + { + this.Id = this.Core.GetAttributeIdentifier(this.SourceLineNumbers, attrib); + } + + public void ParseName(XAttribute attrib) + { + this.Name = this.Core.GetAttributeLongFilename(this.SourceLineNumbers, attrib, false, true); + if (!this.Core.IsValidLongFilename(this.Name, false, true)) + { + this.Core.Write(ErrorMessages.IllegalLongFilename(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", this.Name)); + } + } + + public void ParseProductName(XAttribute attrib) + { + this.ProductName = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseSize(XAttribute attrib) + { + this.Size = this.Core.GetAttributeLongValue(this.SourceLineNumbers, attrib, 1, Int64.MaxValue); + } + + public void ParseSourceFile(XAttribute attrib) + { + this.SourceFile = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseVersion(XAttribute attrib) + { + this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + } +} diff --git a/src/wix/WixToolset.Core/CompileContext.cs b/src/wix/WixToolset.Core/CompileContext.cs new file mode 100644 index 00000000..d84d7aac --- /dev/null +++ b/src/wix/WixToolset.Core/CompileContext.cs @@ -0,0 +1,34 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class CompileContext : ICompileContext + { + internal CompileContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public string CompilationId { get; set; } + + public IReadOnlyCollection Extensions { get; set; } + + public Platform Platform { get; set; } + + public bool IsCurrentPlatform64Bit => this.Platform == Platform.ARM64 || this.Platform == Platform.X64; + + public XDocument Source { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Compiler.cs b/src/wix/WixToolset.Core/Compiler.cs new file mode 100644 index 00000000..c39bec70 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler.cs @@ -0,0 +1,8514 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB + private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) + + private const char ComponentIdPlaceholderStart = (char)167; + private const char ComponentIdPlaceholderEnd = (char)167; + private Dictionary componentIdPlaceholders; + + // If these are true you know you are building a module or product + // but if they are false you cannot not be sure they will not end + // up a product or module. Use these flags carefully. + private bool compilingModule; + private bool compilingProduct; + + private string activeName; + private string activeLanguage; + + /// + /// Type of RadioButton element in a group. + /// + private enum RadioButtonType + { + /// Not set, yet. + NotSet, + + /// Text + Text, + + /// Bitmap + Bitmap, + + /// Icon + Icon, + } + + internal Compiler(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + public IMessaging Messaging { get; } + + private ICompileContext Context { get; set; } + + private CompilerCore Core { get; set; } + + /// + /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements. + /// + /// The platform which the compiler will use when defaulting 64-bit attributes and elements. + public Platform CurrentPlatform => this.Context.Platform; + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + public bool ShowPedanticMessages { get; set; } + + /// + /// Compiles the provided Xml document into an intermediate object + /// + /// Intermediate object representing compiled source document. + /// This method is not thread-safe. + public Intermediate Compile(ICompileContext context) + { + var target = new Intermediate(); + + if (String.IsNullOrEmpty(context.CompilationId)) + { + context.CompilationId = target.Id; + } + + this.Context = context; + + var extensionsByNamespace = new Dictionary(); + + foreach (var extension in this.Context.Extensions) + { + if (!extensionsByNamespace.TryGetValue(extension.Namespace, out var collidingExtension)) + { + extensionsByNamespace.Add(extension.Namespace, extension); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateExtensionXmlSchemaNamespace(extension.GetType().ToString(), extension.Namespace.NamespaceName, collidingExtension.GetType().ToString())); + } + + extension.PreCompile(this.Context); + } + + // Try to compile it. + try + { + var parseHelper = this.Context.ServiceProvider.GetService(); + + this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace); + this.Core.ShowPedanticMessages = this.ShowPedanticMessages; + this.componentIdPlaceholders = new Dictionary(); + + // parse the document + var source = this.Context.Source; + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(source.Root); + if ("Wix" == source.Root.Name.LocalName) + { + if (CompilerCore.WixNamespace == source.Root.Name.Namespace) + { + this.ParseWixElement(source.Root); + } + else // invalid or missing namespace + { + if (String.IsNullOrEmpty(source.Root.Name.NamespaceName)) + { + this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", CompilerCore.WixNamespace.ToString())); + } + else + { + this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", source.Root.Name.NamespaceName, CompilerCore.WixNamespace.ToString())); + } + } + } + else + { + this.Core.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, source.Root.Name.LocalName, "source", "Wix")); + } + + // Resolve any Component Id placeholders compiled into the intermediate. + this.ResolveComponentIdPlaceholders(target); + } + finally + { + foreach (var extension in this.Context.Extensions) + { + extension.PostCompile(target); + } + + this.Core = null; + } + + target.UpdateLevel(Data.IntermediateLevels.Compiled); + + return this.Messaging.EncounteredError ? null : target; + } + + /// + /// Parses a Wix element. + /// + /// Element to parse. + private void ParseWixElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string requiredVersion = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "RequiredVersion": + requiredVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null != requiredVersion) + { + this.Core.VerifyRequiredVersion(sourceLineNumbers, requiredVersion); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Bundle": + this.ParseBundleElement(child); + break; + case "Fragment": + this.ParseFragmentElement(child); + break; + case "Module": + this.ParseModuleElement(child); + break; + case "PatchCreation": + this.ParsePatchCreationElement(child); + break; + case "Package": + this.ParsePackageElement(child); + break; + case "Patch": + this.ParsePatchElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + private void ResolveComponentIdPlaceholders(Intermediate target) + { + if (0 < this.componentIdPlaceholders.Count) + { + foreach (var section in target.Sections) + { + foreach (var symbol in section.Symbols) + { + foreach (var field in symbol.Fields) + { + if (field != null && field.Type == IntermediateFieldType.String) + { + var data = field.AsString(); + if (!String.IsNullOrEmpty(data)) + { + var changed = false; + var start = data.IndexOf(ComponentIdPlaceholderStart); + while (start != -1) + { + var end = data.IndexOf(ComponentIdPlaceholderEnd, start + 1); + if (end == -1) + { + break; + } + + var placeholderId = data.Substring(start, end - start + 1); + if (this.componentIdPlaceholders.TryGetValue(placeholderId, out var value)) + { + var sb = new StringBuilder(data); + sb.Remove(start, end - start + 1); + sb.Insert(start, value); + + data = sb.ToString(); + changed = true; + + end = start + value.Length; + } + + start = data.IndexOf(ComponentIdPlaceholderStart, end); + } + + if (changed) + { + field.Overwrite(data); + } + } + } + } + } + } + } + } + + /// + /// Uppercases the first character of a string. + /// + /// String to uppercase first character of. + /// String with first character uppercased. + private static string UppercaseFirstChar(string s) + { + if (0 == s.Length) + { + return s; + } + + return String.Concat(s.Substring(0, 1).ToUpperInvariant(), s.Substring(1)); + } + + /// + /// Lowercases the string if present. + /// + /// String to lowercase. + /// Null if the string is null, otherwise returns the lowercase. + private static string LowercaseOrNull(string s) + { + return s?.ToLowerInvariant(); + } + + /// + /// Adds a search property to the active section. + /// + /// Current source/line number of processing. + /// Property to add to search. + /// Signature for search. + private void AddAppSearch(SourceLineNumber sourceLineNumbers, Identifier propertyId, string signature) + { + if (!this.Core.EncounteredError) + { + if (propertyId.Id != propertyId.Id.ToUpperInvariant()) + { + this.Core.Write(ErrorMessages.SearchPropertyNotUppercase(sourceLineNumbers, "Property", "Id", propertyId.Id)); + } + + this.Core.AddSymbol(new AppSearchSymbol(sourceLineNumbers, new Identifier(propertyId.Access, propertyId.Id, signature)) + { + PropertyRef = propertyId.Id, + SignatureRef = signature + }); + } + } + + /// + /// Adds a property to the active section. + /// + /// Current source/line number of processing. + /// Identifier of property to add. + /// Value of property. + /// Flag if property is an admin property. + /// Flag if property is a secure property. + /// Flag if property is to be hidden. + /// Adds the property to a new section. + private void AddProperty(SourceLineNumber sourceLineNumbers, Identifier propertyId, string value, bool admin, bool secure, bool hidden, bool fragment) + { + // properties without a valid identifier should not be processed any further + if (null == propertyId || String.IsNullOrEmpty(propertyId.Id)) + { + return; + } + + if (!String.IsNullOrEmpty(value)) + { + var start = value.IndexOf('['); + while (start != -1 && start < value.Length) + { + var end = value.IndexOf(']', start + 1); + if (end == -1) + { + break; + } + + var id = value.Substring(start + 1, end - 1); + if (Common.IsIdentifier(id)) + { + this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, id)); + } + + start = (end < value.Length) ? value.IndexOf('[', end + 1) : -1; + } + } + + if (!this.Core.EncounteredError) + { + var section = this.Core.ActiveSection; + + // Add the symbol to a separate section if requested. + if (fragment) + { + var id = String.Concat(this.Core.ActiveSection.Id, ".", propertyId.Id); + + section = this.Core.CreateSection(id, SectionType.Fragment, this.Context.CompilationId); + + // Reference the property in the active section. + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, propertyId.Id); + } + + // Allow symbol to exist with no value so that PropertyRefs can be made for *Search elements + // the linker will remove these symbols before the final output is created. + section.AddSymbol(new PropertySymbol(sourceLineNumbers, propertyId) + { + Value = value, + }); + + if (admin || hidden || secure) + { + this.AddWixPropertySymbol(sourceLineNumbers, propertyId, admin, secure, hidden, section); + } + } + } + + private void AddWixPropertySymbol(SourceLineNumber sourceLineNumbers, Identifier property, bool admin, bool secure, bool hidden, IntermediateSection section = null) + { + if (secure && property.Id != property.Id.ToUpperInvariant()) + { + this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, "Property", "Id", property.Id)); + } + + if (null == section) + { + section = this.Core.ActiveSection; + + this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Property); // Property table is always required when using WixProperty table. + } + + section.AddSymbol(new WixPropertySymbol(sourceLineNumbers) + { + PropertyRef = property.Id, + Admin = admin, + Hidden = hidden, + Secure = secure + }); + } + + /// + /// Adds a "implemented category" registry key to active section. + /// + /// Current source/line number of processing. + /// GUID for category. + /// ClassId for to mark "implemented". + /// Identifier of parent component. + private void RegisterImplementedCategories(SourceLineNumber sourceLineNumbers, string categoryId, string classId, string componentId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Implemented Categories\\", categoryId), "*", null, componentId); + } + + /// + /// Parses an application identifer element. + /// + /// Element to parse. + /// Identifier of parent component. + /// The required advertise state (set depending upon the parent). + /// Optional file identifier for CLSID when not advertised. + /// Optional TypeLib GUID for CLSID. + /// Optional TypeLib Version for CLSID Interfaces (if any). + private void ParseAppIdElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string appId = null; + string remoteServerName = null; + string localService = null; + string serviceParameters = null; + string dllSurrogate = null; + bool? activateAtStorage = null; + var appIdAdvertise = YesNoType.NotSet; + bool? runAsInteractiveUser = null; + string description = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "ActivateAtStorage": + activateAtStorage = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Advertise": + appIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DllSurrogate": + dllSurrogate = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "LocalService": + localService = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "RemoteServerName": + remoteServerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "RunAsInteractiveUser": + runAsInteractiveUser = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ServiceParameters": + serviceParameters = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == appId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if ((YesNoType.No == advertise && YesNoType.Yes == appIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == appIdAdvertise)) + { + this.Core.Write(ErrorMessages.AppIdIncompatibleAdvertiseState(sourceLineNumbers, node.Name.LocalName, "Advertise", appIdAdvertise.ToString(), advertise.ToString())); + } + else + { + advertise = appIdAdvertise; + } + + // if the advertise state has not been set, default to non-advertised + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Class": + this.ParseClassElement(child, componentId, advertise, fileServer, typeLibId, typeLibVersion, appId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (YesNoType.Yes == advertise) + { + if (null != description) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "Description")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new AppIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, appId)) + { + AppId = appId, + RemoteServerName = remoteServerName, + LocalService = localService, + ServiceParameters = serviceParameters, + DllSurrogate = dllSurrogate, + ActivateAtStorage = activateAtStorage, + RunAsInteractiveUser = runAsInteractiveUser, + }); + } + } + else if (YesNoType.No == advertise) + { + if (null != description) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), null, description, componentId); + } + else + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "+", null, componentId); + } + + if (null != remoteServerName) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RemoteServerName", remoteServerName, componentId); + } + + if (null != localService) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "LocalService", localService, componentId); + } + + if (null != serviceParameters) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ServiceParameters", serviceParameters, componentId); + } + + if (null != dllSurrogate) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "DllSurrogate", dllSurrogate, componentId); + } + + if (true == activateAtStorage) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ActivateAtStorage", "Y", componentId); + } + + if (true == runAsInteractiveUser) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RunAs", "Interactive User", componentId); + } + } + } + + /// + /// Parses an AssemblyName element. + /// + /// File element to parse. + /// Parent's component id. + private void ParseAssemblyName(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiAssemblyNameSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, componentId, id)) + { + ComponentRef = componentId, + Name = id, + Value = value, + }); + } + } + + /// + /// Parses a binary element. + /// + /// Element to parse. + /// Identifier for the new row. + private Identifier ParseBinaryElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string sourceFile = null; + var suppressModularization = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SuppressModularization": + suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (!String.IsNullOrEmpty(id.Id)) // only check legal values + { + if (55 < id.Id.Length) + { + this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 55)); + } + else if (!this.compilingProduct) // if we're not doing a product then we can't be sure that a binary identifier will fit when modularized + { + if (18 < id.Id.Length) + { + this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 18)); + } + } + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new BinarySymbol(sourceLineNumbers, id) + { + Data = new IntermediateFieldPathValue { Path = sourceFile } + }); + + if (YesNoType.Yes == suppressModularization) + { + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) + { + SuppressIdentifier = id.Id + }); + } + } + + return id; + } + + /// + /// Parses an icon element. + /// + /// Element to parse. + /// Identifier for the new row. + private string ParseIconElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (!String.IsNullOrEmpty(id.Id)) // only check legal values + { + if (57 < id.Id.Length) + { + this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 57)); + } + else if (!this.compilingProduct) // if we're not doing a product then we can't be sure that a binary identifier will fit when modularized + { + if (20 < id.Id.Length) + { + this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 20)); + } + } + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new IconSymbol(sourceLineNumbers, id) + { + Data = new IntermediateFieldPathValue { Path = sourceFile }, + }); + } + + return id.Id; + } + + /// + /// Parses an InstanceTransforms element. + /// + /// Element to parse. + private void ParseInstanceTransformsElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string property = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Property": + property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == property) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + + // find unexpected child elements + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Instance": + this.ParseInstanceElement(child, property); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses an instance element. + /// + /// Element to parse. + /// Identifier of instance property. + private void ParseInstanceElement(XElement node, string propertyId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string productCode = null; + string productName = null; + string upgradeCode = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ProductCode": + productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); + break; + case "ProductName": + productName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpgradeCode": + upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == productCode) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductCode")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixInstanceTransformsSymbol(sourceLineNumbers, id) + { + PropertyId = propertyId, + ProductCode = productCode, + ProductName = productName, + UpgradeCode = upgradeCode + }); + } + } + + /// + /// Parses a category element. + /// + /// Element to parse. + /// Identifier of parent component. + private void ParseCategoryElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string appData = null; + string feature = null; + string qualifier = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "AppData": + appData = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Feature": + feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); + break; + case "Qualifier": + qualifier = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == qualifier) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Qualifier")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new PublishComponentSymbol(sourceLineNumbers) + { + ComponentId = id, + Qualifier = qualifier, + ComponentRef = componentId, + AppData = appData, + FeatureRef = feature ?? Guid.Empty.ToString("B"), + }); + } + } + + /// + /// Parses a class element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional Advertise State for the parent AppId element (if any). + /// Optional file identifier for CLSID when not advertised. + /// Optional TypeLib GUID for CLSID. + /// Optional TypeLib Version for CLSID Interfaces (if any). + /// Optional parent AppId. + private void ParseClassElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion, string parentAppId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + string appId = null; + string argument = null; + var class16bit = false; + var class32bit = false; + string classId = null; + var classAdvertise = YesNoType.NotSet; + var contexts = new string[0]; + string formattedContextString = null; + var control = false; + string defaultInprocHandler = null; + string defaultProgId = null; + string description = null; + string fileTypeMask = null; + string foreignServer = null; + string icon = null; + var iconIndex = CompilerConstants.IntegerNotSet; + string insertable = null; + string localFileServer = null; + var programmable = false; + var relativePath = YesNoType.NotSet; + var safeForInit = false; + var safeForScripting = false; + var shortServerPath = false; + string threadingModel = null; + string version = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Advertise": + classAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "AppId": + appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Argument": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Context": + contexts = this.Core.GetAttributeValue(sourceLineNumbers, attrib).Split("\r\n\t ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + break; + case "Control": + control = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Handler": + defaultInprocHandler = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Icon": + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "IconIndex": + iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); + break; + case "RelativePath": + relativePath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + + // The following attributes result in rows always added to the Registry table rather than the Class table + case "Insertable": + insertable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? "Insertable" : "NotInsertable"; + break; + case "Programmable": + programmable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SafeForInitializing": + safeForInit = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SafeForScripting": + safeForScripting = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ForeignServer": + foreignServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Server": + localFileServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ShortPath": + shortServerPath = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ThreadingModel": + threadingModel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Version": + version = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == classId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + var uniqueContexts = new HashSet(); + foreach (var context in contexts) + { + if (uniqueContexts.Contains(context)) + { + this.Core.Write(ErrorMessages.DuplicateContextValue(sourceLineNumbers, context)); + } + else + { + uniqueContexts.Add(context); + } + + if (context.EndsWith("32", StringComparison.Ordinal)) + { + class32bit = true; + } + else + { + class16bit = true; + } + } + + if ((YesNoType.No == advertise && YesNoType.Yes == classAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == classAdvertise)) + { + this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, classAdvertise.ToString(), advertise.ToString())); + } + else + { + advertise = classAdvertise; + } + + // If the advertise state has not been set, default to non-advertised. + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + if (YesNoType.Yes == advertise && 0 == contexts.Length) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Context", "Advertise", "yes")); + } + + if (!String.IsNullOrEmpty(parentAppId) && !String.IsNullOrEmpty(appId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AppId", node.Parent.Name.LocalName)); + } + + if (!String.IsNullOrEmpty(localFileServer)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, localFileServer); + } + + // Local variables used strictly for child node processing. + var fileTypeMaskIndex = 0; + var firstProgIdForClass = YesNoType.Yes; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "FileTypeMask": + if (YesNoType.Yes == advertise) + { + fileTypeMask = String.Concat(fileTypeMask, null == fileTypeMask ? String.Empty : ";", this.ParseFileTypeMaskElement(child)); + } + else if (YesNoType.No == advertise) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.CreateRegistryRow(childSourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("FileType\\", classId, "\\", fileTypeMaskIndex.ToString()), String.Empty, this.ParseFileTypeMaskElement(child), componentId); + fileTypeMaskIndex++; + } + break; + case "Interface": + this.ParseInterfaceElement(child, componentId, class16bit ? classId : null, class32bit ? classId : null, typeLibId, typeLibVersion); + break; + case "ProgId": + { + var foundExtension = false; + var progId = this.ParseProgIdElement(child, componentId, advertise, classId, description, null, ref foundExtension, firstProgIdForClass); + if (null == defaultProgId) + { + defaultProgId = progId; + } + firstProgIdForClass = YesNoType.No; + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + // If this Class is being advertised. + if (YesNoType.Yes == advertise) + { + if (null != fileServer || null != localFileServer) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Server", "Advertise", "yes")); + } + + if (null != foreignServer) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Advertise", "yes")); + } + + if (null == appId && null != parentAppId) + { + appId = parentAppId; + } + + // add a Class row for each context + if (!this.Core.EncounteredError) + { + foreach (var context in contexts) + { + var symbol = this.Core.AddSymbol(new ClassSymbol(sourceLineNumbers) + { + CLSID = classId, + Context = context, + ComponentRef = componentId, + DefaultProgIdRef = defaultProgId, + Description = description, + FileTypeMask = fileTypeMask, + DefInprocHandler = defaultInprocHandler, + Argument = argument, + FeatureRef = Guid.Empty.ToString("B"), + RelativePath = YesNoType.Yes == relativePath, + }); + + if (null != appId) + { + symbol.AppIdRef = appId; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.AppId, appId); + } + + if (null != icon) + { + symbol.IconRef = icon; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); + } + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + symbol.IconIndex = iconIndex; + } + } + } + } + else if (YesNoType.No == advertise) + { + if (null == fileServer && null == localFileServer && null == foreignServer) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server")); + } + + if (null != fileServer && null != foreignServer) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "File")); + } + else if (null != localFileServer && null != foreignServer) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server")); + } + else if (null == fileServer) + { + fileServer = localFileServer; + } + + if (null != appId) // need to use nesting (not a reference) for the unadvertised Class elements + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AppId", "Advertise", "no")); + } + + // add the core registry keys for each context in the class + foreach (var context in contexts) + { + if (context.StartsWith("InprocServer", StringComparison.Ordinal)) // dll server + { + if (null != argument) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Arguments", "Context", context)); + } + + if (null != fileServer) + { + formattedContextString = String.Concat("[", shortServerPath ? "!" : "#", fileServer, "]"); + } + else if (null != foreignServer) + { + formattedContextString = foreignServer; + } + } + else if (context.StartsWith("LocalServer", StringComparison.Ordinal)) // exe server (quote the long path) + { + if (null != fileServer) + { + if (shortServerPath) + { + formattedContextString = String.Concat("[!", fileServer, "]"); + } + else + { + formattedContextString = String.Concat("\"[#", fileServer, "]\""); + } + } + else if (null != foreignServer) + { + formattedContextString = foreignServer; + } + + if (null != argument) + { + formattedContextString = String.Concat(formattedContextString, " ", argument); + } + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Context", context, "InprocServer", "InprocServer32", "LocalServer", "LocalServer32")); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), String.Empty, formattedContextString, componentId); // ClassId context + + if (null != icon) // ClassId default icon + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); + + icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + icon = String.Concat(icon, ",", iconIndex); + } + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\DefaultIcon"), String.Empty, icon, componentId); + } + } + + if (null != parentAppId) // ClassId AppId (must be specified via nesting, not with the AppId attribute) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), "AppID", parentAppId, componentId); + } + + if (null != description) // ClassId description + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), String.Empty, description, componentId); + } + + if (null != defaultInprocHandler) + { + switch (defaultInprocHandler) // ClassId Default Inproc Handler + { + case "1": + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId); + break; + case "2": + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId); + break; + case "3": + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId); + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId); + break; + default: + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, defaultInprocHandler, componentId); + break; + } + } + + if (YesNoType.NotSet != relativePath) // ClassId's RelativePath + { + this.Core.Write(ErrorMessages.RelativePathForRegistryElement(sourceLineNumbers)); + } + } + + if (null != threadingModel) + { + threadingModel = Compiler.UppercaseFirstChar(threadingModel); + + // add a threading model for each context in the class + foreach (var context in contexts) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), "ThreadingModel", threadingModel, componentId); + } + } + + if (null != typeLibId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\TypeLib"), null, typeLibId, componentId); + } + + if (null != version) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Version"), null, version, componentId); + } + + if (null != insertable) + { + // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", insertable), "*", null, componentId); + } + + if (control) + { + // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Control"), "*", null, componentId); + } + + if (programmable) + { + // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall. + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Programmable"), "*", null, componentId); + } + + if (safeForInit) + { + this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95802-9882-11CF-9FA9-00AA006C42C4}", classId, componentId); + } + + if (safeForScripting) + { + this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95801-9882-11CF-9FA9-00AA006C42C4}", classId, componentId); + } + } + + /// + /// Parses an Interface element. + /// + /// Element to parse. + /// Identifier of parent component. + /// 16-bit proxy for interface. + /// 32-bit proxy for interface. + /// Optional TypeLib GUID for CLSID. + /// Version of the TypeLib to which this interface belongs. Required if typeLibId is specified + private void ParseInterfaceElement(XElement node, string componentId, string proxyId, string proxyId32, string typeLibId, string typelibVersion) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string baseInterface = null; + string interfaceId = null; + string name = null; + var numMethods = CompilerConstants.IntegerNotSet; + var versioned = true; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + interfaceId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "BaseInterface": + baseInterface = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "NumMethods": + numMethods = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "ProxyStubClassId": + proxyId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib); + break; + case "ProxyStubClassId32": + proxyId32 = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Versioned": + versioned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == interfaceId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId), null, name, componentId); + if (null != typeLibId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), null, typeLibId, componentId); + if (versioned) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), "Version", typelibVersion, componentId); + } + } + + if (null != baseInterface) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\BaseInterface"), null, baseInterface, componentId); + } + + if (CompilerConstants.IntegerNotSet != numMethods) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\NumMethods"), null, numMethods.ToString(), componentId); + } + + if (null != proxyId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid"), null, proxyId, componentId); + } + + if (null != proxyId32) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid32"), null, proxyId32, componentId); + } + } + + /// + /// Parses a CLSID's file type mask element. + /// + /// Element to parse. + /// String representing the file type mask elements. + private string ParseFileTypeMaskElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var cb = 0; + var offset = CompilerConstants.IntegerNotSet; + string mask = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Mask": + mask = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Offset": + offset = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + + if (null == mask) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Mask")); + } + + if (CompilerConstants.IntegerNotSet == offset) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + if (mask.Length != value.Length) + { + this.Core.Write(ErrorMessages.ValueAndMaskMustBeSameLength(sourceLineNumbers)); + } + cb = mask.Length / 2; + } + + return String.Concat(offset.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", cb.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", mask, ",", value); + } + + /// + /// Parses a product search element. + /// + /// Element to parse. + /// + /// Signature for search element. + private void ParseProductSearchElement(XElement node, string propertyId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + string upgradeCode = null; + string language = null; + string maximum = null; + string minimum = null; + var excludeLanguages = false; + var maxInclusive = false; + var minInclusive = true; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ExcludeLanguages": + excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMaximum": + maxInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMinimum": + minInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Language": + language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Minimum": + minimum = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Maximum": + maximum = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpgradeCode": + upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == minimum && null == maximum) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeCode, + VersionMin = minimum, + VersionMax = maximum, + Language = language, + ActionProperty = propertyId, + OnlyDetect = true, + ExcludeLanguages = excludeLanguages, + VersionMaxInclusive = maxInclusive, + VersionMinInclusive = minInclusive, + }); + } + } + + /// + /// Parses a registry search element. + /// + /// Element to parse. + /// Signature for search element. + private string ParseRegistrySearchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string name = null; + RegistryRootType? root = null; + RegLocatorType? type = null; + var search64bit = this.Context.IsCurrentPlatform64Bit; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + search64bit = false; + break; + case "always64": + search64bit = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, false); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "directory": + type = RegLocatorType.Directory; + break; + case "file": + type = RegLocatorType.FileName; + break; + case "raw": + type = RegLocatorType.Raw; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "raw")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("reg", root.ToString(), key, name, type.ToString(), search64bit.ToString()); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (!type.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); + } + + var signature = id.Id; + var oneChild = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + + // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column + signature = this.ParseDirectorySearchElement(child, id.Id); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, id.Id); + break; + case "FileSearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); + id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures + break; + case "FileSearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures + id = new Identifier(AccessModifier.Section, newId); + signature = null; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RegLocatorSymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + Type = type.Value, + Win64 = search64bit, + }); + } + + return signature; + } + + /// + /// Parses a registry search reference element. + /// + /// Element to parse. + /// Signature of referenced search element. + private string ParseRegistrySearchRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.RegLocator, id); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return id; // the id of the RegistrySearchRef element is its signature + } + + /// + /// Parses child elements for search signatures. + /// + /// Node whose children we are parsing. + /// Returns list of string signatures. + private List ParseSearchSignatures(XElement node) + { + var signatures = new List(); + + foreach (var child in node.Elements()) + { + string signature = null; + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ComplianceDrive": + signature = this.ParseComplianceDriveElement(child); + break; + case "ComponentSearch": + signature = this.ParseComponentSearchElement(child); + break; + case "DirectorySearch": + signature = this.ParseDirectorySearchElement(child, String.Empty); + break; + case "DirectorySearchRef": + signature = this.ParseDirectorySearchRefElement(child, String.Empty); + break; + case "IniFileSearch": + signature = this.ParseIniFileSearchElement(child); + break; + case "ProductSearch": + // handled in ParsePropertyElement + break; + case "RegistrySearch": + signature = this.ParseRegistrySearchElement(child); + break; + case "RegistrySearchRef": + signature = this.ParseRegistrySearchRefElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + + + if (!String.IsNullOrEmpty(signature)) + { + signatures.Add(signature); + } + } + + return signatures; + } + + /// + /// Parses a compliance drive element. + /// + /// Element to parse. + /// Signature of nested search elements. + private string ParseComplianceDriveElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string signature = null; + + var oneChild = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchElement(child, "CCP_DRIVE"); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, "CCP_DRIVE"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == signature) + { + this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName)); + } + + return signature; + } + + /// + /// Parses a compilance check element. + /// + /// Element to parse. + private void ParseComplianceCheckElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + string signature = null; + + // see if this property is used for appSearch + var signatures = this.ParseSearchSignatures(node); + foreach (var sig in signatures) + { + // if we haven't picked a signature for this ComplianceCheck pick + // this one + if (null == signature) + { + signature = sig; + } + else if (signature != sig) + { + // all signatures under a ComplianceCheck must be the same + this.Core.Write(ErrorMessages.MultipleIdentifiersFound(sourceLineNumbers, node.Name.LocalName, sig, signature)); + } + } + + if (null == signature) + { + this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName)); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, signature))); + } + } + + /// + /// Parses a component element. + /// + /// Element to parse. + /// Type of component's complex reference parent. Will be Uknown if there is no parent. + /// Optional identifier for component's primary parent. + /// Optional string for component's parent's language. + /// Optional disk id inherited from parent directory. + /// Optional identifier for component's directory. + /// Optional source path for files up to this point. + private void ParseComponentElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage, int diskId, string directoryId, string srcPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + var comPlusBits = CompilerConstants.IntegerNotSet; + string condition = null; + string subdirectory = null; + var encounteredODBCDataSource = false; + var files = 0; + var guid = "*"; + Identifier id = null; + string componentIdPlaceholder = null; + var keyFound = false; + string keyPath = null; + + var keyPathType = ComponentKeyPathType.Directory; + var location = ComponentLocation.LocalOnly; + var disableRegistryReflection = false; + + var neverOverwrite = false; + var permanent = false; + var shared = false; + var sharedDllRefCount = false; + var transitive = false; + var uninstallWhenSuperseded = false; + var win64 = this.Context.IsCurrentPlatform64Bit; + + var multiInstance = false; + var symbols = new List(); + string feature = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + case "ComPlusFlags": + comPlusBits = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "DisableRegistryReflection": + disableRegistryReflection = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "Feature": + feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Guid": + guid = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true, true); + break; + case "KeyPath": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + keyFound = true; + keyPath = null; + } + break; + case "Location": + var locationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (locationValue) + { + case "either": + location = ComponentLocation.Either; + break; + case "local": // this is the default + location = ComponentLocation.LocalOnly; + break; + case "source": + location = ComponentLocation.SourceOnly; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, locationValue, "either", "local", "source")); + break; + } + break; + case "MultiInstance": + multiInstance = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "NeverOverwrite": + neverOverwrite = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Permanent": + permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Shared": + shared = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SharedDllRefCount": + sharedDllRefCount = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Transitive": + transitive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "UninstallWhenSuperseded": + uninstallWhenSuperseded = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (id == null) + { + // Placeholder id for defaulting Component/@Id to keypath id. + componentIdPlaceholder = String.Concat(Compiler.ComponentIdPlaceholderStart, this.componentIdPlaceholders.Count, Compiler.ComponentIdPlaceholderEnd); + id = new Identifier(AccessModifier.Section, componentIdPlaceholder); + } + + if (String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory")); + } + + if (!String.IsNullOrEmpty(subdirectory)) + { + directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); + } + + if (String.IsNullOrEmpty(guid) && shared) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Shared", "yes", "Guid", "")); + } + + if (String.IsNullOrEmpty(guid) && permanent) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Permanent", "yes", "Guid", "")); + } + + if (null != feature) + { + if (this.compilingModule) + { + this.Core.Write(ErrorMessages.IllegalAttributeInMergeModule(sourceLineNumbers, node.Name.LocalName, "Feature")); + } + else + { + if (ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Feature", node.Parent.Name.LocalName)); + } + else + { + this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); + } + } + } + + foreach (var child in node.Elements()) + { + var keyPathSet = YesNoType.NotSet; + string keyPossible = null; + ComponentKeyPathType? keyBit = null; + + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AppId": + this.ParseAppIdElement(child, id.Id, YesNoType.NotSet, null, null, null); + break; + case "Category": + this.ParseCategoryElement(child, id.Id); + break; + case "Class": + this.ParseClassElement(child, id.Id, YesNoType.NotSet, null, null, null, null); + break; + case "CopyFile": + this.ParseCopyFileElement(child, id.Id, null); + break; + case "CreateFolder": + var createdFolder = this.ParseCreateFolderElement(child, id.Id, directoryId, win64); + break; + case "Environment": + this.ParseEnvironmentElement(child, id.Id); + break; + case "Extension": + this.ParseExtensionElement(child, id.Id, YesNoType.NotSet, null); + break; + case "File": + keyPathSet = this.ParseFileElement(child, id.Id, directoryId, diskId, srcPath, out keyPossible, win64, guid); + keyBit = ComponentKeyPathType.File; + files++; + break; + case "IniFile": + this.ParseIniFileElement(child, id.Id); + break; + case "Interface": + this.ParseInterfaceElement(child, id.Id, null, null, null, null); + break; + case "IsolateComponent": + this.ParseIsolateComponentElement(child, id.Id); + break; + case "ODBCDataSource": + keyPathSet = this.ParseODBCDataSource(child, id.Id, null, out keyPossible); + keyBit = ComponentKeyPathType.OdbcDataSource; + encounteredODBCDataSource = true; + break; + case "ODBCDriver": + this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCDriver); + break; + case "ODBCTranslator": + this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCTranslator); + break; + case "ProgId": + var foundExtension = false; + this.ParseProgIdElement(child, id.Id, YesNoType.NotSet, null, null, null, ref foundExtension, YesNoType.NotSet); + break; + case "Provides": + if (win64) + { + this.Messaging.Write(CompilerWarnings.Win64Component(sourceLineNumbers, id.Id)); + } + + keyPathSet = this.ParseProvidesElement(child, null, id.Id, out keyPossible); + keyBit = ComponentKeyPathType.Registry; + break; + + case "RegistryKey": + keyPathSet = this.ParseRegistryKeyElement(child, id.Id, null, null, win64, out keyPossible); + keyBit = ComponentKeyPathType.Registry; + break; + case "RegistryValue": + keyPathSet = this.ParseRegistryValueElement(child, id.Id, null, null, win64, out keyPossible); + keyBit = ComponentKeyPathType.Registry; + break; + case "RemoveFile": + this.ParseRemoveFileElement(child, id.Id, directoryId); + break; + case "RemoveFolder": + this.ParseRemoveFolderElement(child, id.Id, directoryId); + break; + case "RemoveRegistryKey": + this.ParseRemoveRegistryKeyElement(child, id.Id); + break; + case "RemoveRegistryValue": + this.ParseRemoveRegistryValueElement(child, id.Id); + break; + case "ReserveCost": + this.ParseReserveCostElement(child, id.Id, directoryId); + break; + case "ServiceConfig": + this.ParseServiceConfigElement(child, id.Id, null); + break; + case "ServiceConfigFailureActions": + this.ParseServiceConfigFailureActionsElement(child, id.Id, null); + break; + case "ServiceControl": + this.ParseServiceControlElement(child, id.Id); + break; + case "ServiceInstall": + this.ParseServiceInstallElement(child, id.Id, win64); + break; + case "Shortcut": + this.ParseShortcutElement(child, id.Id, node.Name.LocalName, directoryId, YesNoType.No); + break; + case "SymbolPath": + symbols.Add(this.ParseSymbolPathElement(child)); + break; + case "TypeLib": + this.ParseTypeLibElement(child, id.Id, null, win64); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "ComponentId", id?.Id }, { "DirectoryId", directoryId }, { "Win64", win64.ToString() }, }; + var possibleKeyPath = this.Core.ParsePossibleKeyPathExtensionElement(node, child, context); + if (null != possibleKeyPath) + { + if (PossibleKeyPathType.None == possibleKeyPath.Type) + { + keyPathSet = YesNoType.No; + } + else + { + keyPathSet = possibleKeyPath.Explicit ? YesNoType.Yes : YesNoType.NotSet; + + if (!String.IsNullOrEmpty(possibleKeyPath.Id)) + { + keyPossible = possibleKeyPath.Id; + } + + if (PossibleKeyPathType.Registry == possibleKeyPath.Type || PossibleKeyPathType.RegistryFormatted == possibleKeyPath.Type) + { + keyBit = ComponentKeyPathType.Registry; //MsiInterop.MsidbComponentAttributesRegistryKeyPath; + } + } + } + } + + // Verify that either the key path is not set, or it is set along with a key path ID. + Debug.Assert(YesNoType.Yes != keyPathSet || (YesNoType.Yes == keyPathSet && null != keyPossible)); + + if (keyFound && YesNoType.Yes == keyPathSet) + { + this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, node.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); + } + + // if a possible KeyPath has been found and that value was explicitly set as + // the KeyPath of the component, set it now. Alternatively, if a possible + // KeyPath has been found and no KeyPath has been previously set, use this + // value as the default KeyPath of the component + if (!String.IsNullOrEmpty(keyPossible) && (YesNoType.Yes == keyPathSet || (YesNoType.NotSet == keyPathSet && String.IsNullOrEmpty(keyPath) && !keyFound))) + { + keyFound = YesNoType.Yes == keyPathSet; + keyPath = keyPossible; + keyPathType = keyBit.Value; + } + } + + // Check for conditions that exclude this component from using implicit ids and/or generated guids. + var allowImplicitIds = true; + if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType) + { + allowImplicitIds = false; + if (guid == "*") + { + this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers)); + } + } + else if (0 < files && ComponentKeyPathType.Registry == keyPathType) + { + allowImplicitIds = false; + if (guid == "*") + { + this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true)); + } + } + + // Check for implicit KeyPath which can easily be accidentally changed + if (this.ShowPedanticMessages && !keyFound && !allowImplicitIds) + { + this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id)); + } + + // If there isn't an @Id attribute value, replace the placeholder with the id of the keypath. + // either an explicit KeyPath="yes" attribute must be specified or requirements for + // generatable guid must be met. + if (componentIdPlaceholder == id.Id) + { + if (allowImplicitIds || keyFound && !String.IsNullOrEmpty(keyPath)) + { + this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath); + + id = new Identifier(AccessModifier.Section, keyPath); + } + else + { + this.Core.Write(ErrorMessages.CannotDefaultComponentId(sourceLineNumbers)); + } + } + + // finally add the Component table row + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) + { + ComponentId = guid, + DirectoryRef = directoryId, + Location = location, + Condition = condition, + KeyPath = keyPath, + KeyPathType = keyPathType, + DisableRegistryReflection = disableRegistryReflection, + NeverOverwrite = neverOverwrite, + Permanent = permanent, + SharedDllRefCount = sharedDllRefCount, + Shared = shared, + Transitive = transitive, + UninstallWhenSuperseded = uninstallWhenSuperseded, + Win64 = win64, + }); + + if (multiInstance) + { + this.Core.AddSymbol(new WixInstanceComponentSymbol(sourceLineNumbers, id) + { + ComponentRef = id.Id, + }); + } + + if (0 < symbols.Count) + { + this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Component, id.Id)) + { + SymbolType = SymbolPathType.Component, + SymbolId = id.Id, + SymbolPaths = String.Join(";", symbols), + }); + } + + // Complus + if (CompilerConstants.IntegerNotSet != comPlusBits) + { + this.Core.AddSymbol(new ComplusSymbol(sourceLineNumbers) + { + ComponentRef = id.Id, + ExpType = comPlusBits, + }); + } + + // if this is a module, automatically add this component to the references to ensure it gets in the ModuleComponents table + if (this.compilingModule) + { + this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, ComplexReferenceChildType.Component, id.Id, false); + } + else if (ComplexReferenceParentType.Unknown != parentType && null != parentId) // if parent was provided, add a complex reference to that. + { + // If the Component is defined directly under a feature, then mark the complex reference primary. + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id.Id, ComplexReferenceParentType.Feature == parentType); + } + } + } + + /// + /// Parses a component group element. + /// + /// Element to parse. + /// + /// + private void ParseComponentGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string directoryId = null; + string subdirectory = null; + string source = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "Source": + source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + + if (!String.IsNullOrEmpty(source) && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + source = String.Concat(source, Path.DirectorySeparatorChar); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null); + break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null, CompilerConstants.IntegerNotSet, directoryId, source); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixComponentGroupSymbol(sourceLineNumbers, id)); + + // Add this componentGroup and its parent in WixGroup. + this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ComponentGroup, id.Id); + } + } + + /// + /// Parses a component group reference element. + /// + /// Element to parse. + /// ComplexReferenceParentType of parent element. + /// Identifier of parent element (usually a Feature or Module). + /// Optional language of parent (only useful for Modules). + private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) + { + Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var primary = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixComponentGroup, id); + break; + case "Primary": + primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.ComponentGroup, id, (YesNoType.Yes == primary)); + } + + /// + /// Parses a component reference element. + /// + /// Element to parse. + /// ComplexReferenceParentType of parent element. + /// Identifier of parent element (usually a Feature or Module). + /// Optional language of parent (only useful for Modules). + private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) + { + Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var primary = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, id); + break; + case "Primary": + primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id, (YesNoType.Yes == primary)); + } + + /// + /// Parses a component search element. + /// + /// Element to parse. + /// Signature for search element. + private string ParseComponentSearchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string componentId = null; + var type = LocatorType.Filename; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Guid": + componentId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "directory": + type = LocatorType.Directory; + break; + case "file": + type = LocatorType.Filename; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "directory", "file")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("cmp", componentId, type.ToString()); + } + + var signature = id.Id; + var oneChild = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + + // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column + signature = this.ParseDirectorySearchElement(child, id.Id); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, id.Id); + break; + case "FileSearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); + id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures + break; + case "FileSearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures + id = new Identifier(AccessModifier.Section, newId); + signature = null; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CompLocatorSymbol(sourceLineNumbers, id) + { + SignatureRef = id.Id, + ComponentId = componentId, + Type = type, + }); + } + + return signature; + } + + /// + /// Parses a create folder element. + /// + /// Element to parse. + /// Identifier for parent component. + /// Default identifier for directory to create. + /// true if the component is 64-bit. + /// Identifier for the directory that will be created + private string ParseCreateFolderElement(XElement node, string componentId, string directoryId, bool win64Component) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string subdirectory = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Shortcut": + this.ParseShortcutElement(child, componentId, node.Name.LocalName, directoryId, YesNoType.No); + break; + case "Permission": + this.ParsePermissionElement(child, directoryId, "CreateFolder"); + break; + case "PermissionEx": + this.ParsePermissionExElement(child, directoryId, "CreateFolder"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "DirectoryId", directoryId }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CreateFolderSymbol(sourceLineNumbers) + { + DirectoryRef = directoryId, + ComponentRef = componentId, + }); + } + + return directoryId; + } + + /// + /// Parses a copy file element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of file to copy (null if moving the file). + private void ParseCopyFileElement(XElement node, string componentId, string fileId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var delete = false; + string destinationDirectory = null; + string destinationSubdirectory = null; + string destinationName = null; + string destinationShortName = null; + string destinationProperty = null; + string sourceDirectory = null; + string sourceSubdirectory = null; + string sourceFolder = null; + string sourceName = null; + string sourceProperty = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Delete": + delete = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "DestinationDirectory": + destinationDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, destinationDirectory); + break; + case "DestinationSubdirectory": + destinationSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "DestinationName": + destinationName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib); + break; + case "DestinationProperty": + destinationProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "DestinationShortName": + destinationShortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib); + break; + case "FileId": + if (null != fileId) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); + } + fileId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, fileId); + break; + case "SourceDirectory": + sourceDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, sourceDirectory); + break; + case "SourceSubdirectory": + sourceSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "SourceName": + sourceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SourceProperty": + sourceProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null != sourceFolder && null != sourceDirectory) // SourceFolder and SourceDirectory cannot coexist + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceDirectory")); + } + + if (null != sourceFolder && null != sourceProperty) // SourceFolder and SourceProperty cannot coexist + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceProperty")); + } + + if (null != sourceDirectory && null != sourceProperty) // SourceDirectory and SourceProperty cannot coexist + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "SourceDirectory")); + } + + sourceDirectory = this.HandleSubdirectory(sourceLineNumbers, node, sourceDirectory, sourceSubdirectory, "SourceDirectory", "SourceSubdirectory"); + + if (null != destinationDirectory && null != destinationProperty) // DestinationDirectory and DestinationProperty cannot coexist + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationProperty", "DestinationDirectory")); + } + + destinationDirectory = this.HandleSubdirectory(sourceLineNumbers, node, destinationDirectory, destinationSubdirectory, "DestinationDirectory", "DestinationSubdirectory"); + + if (null == id) + { + id = this.Core.CreateIdentifier("cf", sourceFolder, sourceDirectory, sourceProperty, destinationDirectory, destinationProperty, destinationName); + } + + this.Core.ParseForExtensionElements(node); + + if (null == fileId) + { + // DestinationDirectory or DestinationProperty must be specified + if (null == destinationDirectory && null == destinationProperty) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationDirectory", "DestinationProperty", "FileId")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MoveFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + SourceName = sourceName, + DestinationName = destinationName, + DestinationShortName = destinationShortName, + SourceFolder = sourceDirectory ?? sourceProperty, + DestFolder = destinationDirectory ?? destinationProperty, + Delete = delete, + }); + } + } + else // copy the file + { + if (null != sourceDirectory) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceDirectory", "FileId")); + } + + if (null != sourceFolder) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "FileId")); + } + + if (null != sourceName) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceName", "FileId")); + } + + if (null != sourceProperty) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "FileId")); + } + + if (delete) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Delete", "FileId")); + } + + if (null == destinationName && null == destinationDirectory && null == destinationProperty) + { + this.Core.Write(WarningMessages.CopyFileFileIdUseless(sourceLineNumbers)); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new DuplicateFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + FileRef = fileId, + DestinationName = destinationName, + DestinationShortName = destinationShortName, + DestinationFolder = destinationDirectory ?? destinationProperty, + }); + } + } + } + + /// + /// Parses a CustomAction element. + /// + /// Element to parse. + private void ParseCustomActionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var inlineScript = false; + var suppressModularization = YesNoType.NotSet; + string source = null; + string target = null; + var explicitWin64 = false; + + string scriptFile = null; + string subdirectory = null; + + CustomActionSourceType? sourceType = null; + CustomActionTargetType? targetType = null; + var executionType = CustomActionExecutionType.Immediate; + var hidden = false; + var impersonate = true; + var patchUninstall = false; + var tsAware = false; + var win64 = false; + var async = false; + var ignoreResult = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "BinaryRef": + if (null != source) + { + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + sourceType = CustomActionSourceType.Binary; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + explicitWin64 = true; + win64 = false; + break; + case "always64": + explicitWin64 = true; + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + case "Directory": + if (null != source) + { + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileRef", "Property", "Script")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + sourceType = CustomActionSourceType.Directory; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, source); + break; + case "DllEntry": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + targetType = CustomActionTargetType.Dll; + break; + case "Error": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + sourceType = CustomActionSourceType.File; + targetType = CustomActionTargetType.TextData; + + // The target can be either a formatted error string or a literal + // error number. Try to convert to error number to determine whether + // to add a reference. No need to look at the value. + if (Int32.TryParse(target, out var ignored)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Error, target); + } + break; + case "ExeCommand": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid + targetType = CustomActionTargetType.Exe; + break; + case "Execute": + var execute = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (execute) + { + case "commit": + executionType = CustomActionExecutionType.Commit; + break; + case "deferred": + executionType = CustomActionExecutionType.Deferred; + break; + case "firstSequence": + executionType = CustomActionExecutionType.FirstSequence; + break; + case "immediate": + executionType = CustomActionExecutionType.Immediate; + break; + case "oncePerProcess": + executionType = CustomActionExecutionType.OncePerProcess; + break; + case "rollback": + executionType = CustomActionExecutionType.Rollback; + break; + case "secondSequence": + executionType = CustomActionExecutionType.ClientRepeat; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, execute, "commit", "deferred", "firstSequence", "immediate", "oncePerProcess", "rollback", "secondSequence")); + break; + } + break; + case "FileRef": + if (null != source) + { + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + sourceType = CustomActionSourceType.File; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File + break; + case "HideTarget": + hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Impersonate": + impersonate = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "JScriptCall": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid + targetType = CustomActionTargetType.JScript; + break; + case "PatchUninstall": + patchUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Property": + if (null != source) + { + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); + } + source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + sourceType = CustomActionSourceType.Property; + break; + case "Return": + var returnValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (returnValue) + { + case "asyncNoWait": + async = true; + ignoreResult = true; + break; + case "asyncWait": + async = true; + break; + case "check": + break; + case "ignore": + ignoreResult = true; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, returnValue, "asyncNoWait", "asyncWait", "check", "ignore")); + break; + } + break; + case "Script": + if (null != source) + { + this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script")); + } + + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + + // set the source and target to empty string for error messages when the user sets multiple sources or targets + source = String.Empty; + target = String.Empty; + + inlineScript = true; + + var script = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (script) + { + case "jscript": + sourceType = CustomActionSourceType.Directory; + targetType = CustomActionTargetType.JScript; + break; + case "vbscript": + sourceType = CustomActionSourceType.Directory; + targetType = CustomActionTargetType.VBScript; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, script, "jscript", "vbscript")); + break; + } + break; + case "ScriptSourceFile": + scriptFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "SuppressModularization": + suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "TerminalServerAware": + tsAware = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Value": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid + targetType = CustomActionTargetType.TextData; + break; + case "VBScriptCall": + if (null != target) + { + this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid + targetType = CustomActionTargetType.VBScript; + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + if (!explicitWin64 && this.Context.IsCurrentPlatform64Bit && (CustomActionTargetType.VBScript == targetType || CustomActionTargetType.JScript == targetType)) + { + win64 = true; + } + + if (!String.IsNullOrEmpty(subdirectory)) + { + if (sourceType == CustomActionSourceType.Directory) + { + source = this.HandleSubdirectory(sourceLineNumbers, node, source, subdirectory, "Directory", "Subdirectory"); + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Subdirectory", "Directory")); + } + } + + // if we have an in-lined Script CustomAction ensure no source or target attributes were provided + if (inlineScript) + { + if (String.IsNullOrEmpty(scriptFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); + } + } + else if (CustomActionTargetType.VBScript == targetType) // non-inline vbscript + { + if (null == source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "BinaryRef", "FileRef", "Property")); + } + else if (CustomActionSourceType.Directory == sourceType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "Directory")); + } + } + else if (CustomActionTargetType.JScript == targetType) // non-inline jscript + { + if (null == source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "BinaryRef", "FileRef", "Property")); + } + else if (CustomActionSourceType.Directory == sourceType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "Directory")); + } + } + else if (CustomActionTargetType.Exe == targetType) // exe-command + { + if (null == source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryRef", "Directory", "FileRef", "Property")); + } + } + else if (CustomActionTargetType.TextData == targetType && CustomActionSourceType.Directory != sourceType && CustomActionSourceType.Property != sourceType && CustomActionSourceType.File != sourceType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property", "Error")); + } + + if (!inlineScript && !String.IsNullOrEmpty(scriptFile)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script")); + } + + if (win64 && CustomActionTargetType.VBScript != targetType && CustomActionTargetType.JScript != targetType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Win64", "Script", "VBScriptCall", "JScriptCall")); + } + + if (async && ignoreResult && CustomActionTargetType.Exe != targetType) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Return", "asyncNoWait", "ExeCommand")); + } + + // TS-aware CAs are valid only when deferred. + if (tsAware & + CustomActionExecutionType.Deferred != executionType && + CustomActionExecutionType.Rollback != executionType && + CustomActionExecutionType.Commit != executionType) + { + this.Core.Write(ErrorMessages.IllegalTerminalServerCustomActionAttributes(sourceLineNumbers)); + } + + // MSI doesn't support in-script property setting, so disallow it + if (CustomActionSourceType.Property == sourceType && + CustomActionTargetType.TextData == targetType && + (CustomActionExecutionType.Deferred == executionType || + CustomActionExecutionType.Rollback == executionType || + CustomActionExecutionType.Commit == executionType)) + { + this.Core.Write(ErrorMessages.IllegalPropertyCustomActionAttributes(sourceLineNumbers)); + } + + if (!targetType.HasValue /*0 == targetBits*/) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, id) + { + ExecutionType = executionType, + Source = source, + SourceType = sourceType.Value, + Target = target, + TargetType = targetType.Value, + Async = async, + IgnoreResult = ignoreResult, + Impersonate = impersonate, + PatchUninstall = patchUninstall, + TSAware = tsAware, + Win64 = win64, + Hidden = hidden, + ScriptFile = new IntermediateFieldPathValue { Path = scriptFile } + }); + + if (YesNoType.Yes == suppressModularization) + { + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) + { + SuppressIdentifier = id.Id + }); + } + } + } + + /// + /// Parses a simple reference element. + /// + /// Element to parse. + /// Symbol which contains the target of the simple reference. + /// Id of the referenced element. + private string ParseSimpleRefElement(XElement node, IntermediateSymbolDefinition symbolDefinition) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, id); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return id; + } + + /// + /// Parses a PatchFamilyRef element. + /// + /// Element to parse. + /// The parent type. + /// The ID of the parent. + /// Id of the referenced element. + private void ParsePatchFamilyRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var primaryKeys = new string[2]; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + primaryKeys[0] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "ProductCode": + primaryKeys[1] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == primaryKeys[0]) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.MsiPatchSequence, primaryKeys); + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, primaryKeys[0], true); + } + } + + /// + /// Parses an ensure table element. + /// + /// Element to parse. + private void ParseEnsureTableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (31 < id.Length) + { + this.Core.Write(ErrorMessages.TableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id)); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.EnsureTable(sourceLineNumbers, id); + } + + /// + /// Parses a custom table element. + /// + /// Element to parse. + /// not cleaned + private void ParseCustomTableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string tableId = null; + var unreal = false; + var columns = new List(); + var foundColumns = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Unreal": + unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == tableId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (31 < tableId.Length) + { + this.Core.Write(ErrorMessages.CustomTableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", tableId)); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "Column": + foundColumns = true; + + var column = this.ParseColumnElement(child, childSourceLineNumbers, tableId); + if (column != null) + { + columns.Add(column); + } + break; + case "Row": + this.ParseRowElement(child, childSourceLineNumbers, tableId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (columns.Count > 0) + { + if (!columns.Where(c => c.PrimaryKey).Any()) + { + this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers)); + } + + if (!this.Core.EncounteredError) + { + var columnNames = String.Join(new string(WixCustomTableSymbol.ColumnNamesSeparator, 1), columns.Select(c => c.Name)); + + this.Core.AddSymbol(new WixCustomTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, tableId)) + { + ColumnNames = columnNames, + Unreal = unreal, + }); + } + else if (!foundColumns) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Column")); + } + } + } + + /// + /// Parses a CustomTableRef element. + /// + /// Element to parse. + private void ParseCustomTableRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string tableId = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == tableId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "Row": + this.ParseRowElement(child, childSourceLineNumbers, tableId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses a Column element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// Table Id. + private WixCustomTableColumnSymbol ParseColumnElement(XElement child, SourceLineNumber childSourceLineNumbers, string tableId) + { + string columnName = null; + IntermediateFieldType? columnType = null; + var description = String.Empty; + int? keyColumn = null; + var keyTable = String.Empty; + var localizable = false; + long? maxValue = null; + long? minValue = null; + WixCustomTableColumnCategoryType? category = null; + var modularization = WixCustomTableColumnModularizeType.None; + var nullable = false; + var primaryKey = false; + var setValues = String.Empty; + var columnUnreal = false; + var width = 0; + + foreach (var childAttrib in child.Attributes()) + { + switch (childAttrib.Name.LocalName) + { + case "Id": + columnName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, childAttrib); + break; + case "Category": + var categoryValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (categoryValue) + { + case "text": + category = WixCustomTableColumnCategoryType.Text; + break; + case "upperCase": + category = WixCustomTableColumnCategoryType.UpperCase; + break; + case "lowerCase": + category = WixCustomTableColumnCategoryType.LowerCase; + break; + case "integer": + category = WixCustomTableColumnCategoryType.Integer; + break; + case "doubleInteger": + category = WixCustomTableColumnCategoryType.DoubleInteger; + break; + case "timeDate": + category = WixCustomTableColumnCategoryType.TimeDate; + break; + case "identifier": + category = WixCustomTableColumnCategoryType.Identifier; + break; + case "property": + category = WixCustomTableColumnCategoryType.Property; + break; + case "filename": + category = WixCustomTableColumnCategoryType.Filename; + break; + case "wildCardFilename": + category = WixCustomTableColumnCategoryType.WildCardFilename; + break; + case "path": + category = WixCustomTableColumnCategoryType.Path; + break; + case "paths": + category = WixCustomTableColumnCategoryType.Paths; + break; + case "anyPath": + category = WixCustomTableColumnCategoryType.AnyPath; + break; + case "defaultDir": + category = WixCustomTableColumnCategoryType.DefaultDir; + break; + case "regPath": + category = WixCustomTableColumnCategoryType.RegPath; + break; + case "formatted": + category = WixCustomTableColumnCategoryType.Formatted; + break; + case "formattedSddl": + category = WixCustomTableColumnCategoryType.FormattedSddl; + break; + case "template": + category = WixCustomTableColumnCategoryType.Template; + break; + case "condition": + category = WixCustomTableColumnCategoryType.Condition; + break; + case "guid": + category = WixCustomTableColumnCategoryType.Guid; + break; + case "version": + category = WixCustomTableColumnCategoryType.Version; + break; + case "language": + category = WixCustomTableColumnCategoryType.Language; + break; + case "binary": + category = WixCustomTableColumnCategoryType.Binary; + break; + case "customSource": + category = WixCustomTableColumnCategoryType.CustomSource; + break; + case "cabinet": + category = WixCustomTableColumnCategoryType.Cabinet; + break; + case "shortcut": + category = WixCustomTableColumnCategoryType.Shortcut; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Category", categoryValue, + "text", "upperCase", "lowerCase", "integer", "doubleInteger", "timeDate", "identifier", "property", "filename", + "wildCardFilename", "path", "paths", "anyPath", "defaultDir", "regPath", "formatted", "formattedSddl", "template", + "condition", "guid", "version", "language", "binary", "customSource", "cabinet", "shortcut")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; + } + break; + case "Description": + description = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + case "KeyColumn": + keyColumn = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 1, 32); + break; + case "KeyTable": + keyTable = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + case "Localizable": + localizable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + case "MaxValue": + maxValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); + break; + case "MinValue": + minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); + break; + case "Modularize": + var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (modularizeValue) + { + case "column": + modularization = WixCustomTableColumnModularizeType.Column; + break; + case "companionFile": + modularization = WixCustomTableColumnModularizeType.CompanionFile; + break; + case "condition": + modularization = WixCustomTableColumnModularizeType.Condition; + break; + case "controlEventArgument": + modularization = WixCustomTableColumnModularizeType.ControlEventArgument; + break; + case "controlText": + modularization = WixCustomTableColumnModularizeType.ControlText; + break; + case "icon": + modularization = WixCustomTableColumnModularizeType.Icon; + break; + case "none": + modularization = WixCustomTableColumnModularizeType.None; + break; + case "property": + modularization = WixCustomTableColumnModularizeType.Property; + break; + case "semicolonDelimited": + modularization = WixCustomTableColumnModularizeType.SemicolonDelimited; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; + } + break; + case "Nullable": + nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + case "PrimaryKey": + primaryKey = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + case "Set": + setValues = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (typeValue) + { + case "binary": + columnType = IntermediateFieldType.Path; + break; + case "int": + columnType = IntermediateFieldType.Number; + break; + case "string": + columnType = IntermediateFieldType.String; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; + } + break; + case "Width": + width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue); + break; + case "Unreal": + columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; + default: + this.Core.UnexpectedAttribute(child, childAttrib); + break; + } + } + + if (null == columnName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); + } + + if (!columnType.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type")); + } + else if (columnType == IntermediateFieldType.Number) + { + if (2 != width && 4 != width) + { + this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width)); + } + } + else if (columnType == IntermediateFieldType.Path) + { + if (!category.HasValue) + { + category = WixCustomTableColumnCategoryType.Binary; + } + else if (category != WixCustomTableColumnCategoryType.Binary) + { + this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); + } + } + + this.Core.ParseForExtensionElements(child); + + if (this.Core.EncounteredError) + { + return null; + } + + var attributes = primaryKey ? WixCustomTableColumnSymbolAttributes.PrimaryKey : WixCustomTableColumnSymbolAttributes.None; + attributes |= localizable ? WixCustomTableColumnSymbolAttributes.Localizable : WixCustomTableColumnSymbolAttributes.None; + attributes |= nullable ? WixCustomTableColumnSymbolAttributes.Nullable : WixCustomTableColumnSymbolAttributes.None; + attributes |= columnUnreal ? WixCustomTableColumnSymbolAttributes.Unreal : WixCustomTableColumnSymbolAttributes.None; + + var column = this.Core.AddSymbol(new WixCustomTableColumnSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, columnName)) + { + TableRef = tableId, + Name = columnName, + Type = columnType.Value, + Attributes = attributes, + Width = width, + Category = category, + Description = description, + KeyColumn = keyColumn, + KeyTable = keyTable, + MaxValue = maxValue, + MinValue = minValue, + Modularize = modularization, + Set = setValues, + }); + return column; + } + + /// + /// Parses a Row element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// Table Id. + private void ParseRowElement(XElement node, SourceLineNumber sourceLineNumbers, string tableId) + { + var rowId = Guid.NewGuid().ToString("N").ToUpperInvariant(); + + foreach (var attrib in node.Attributes()) + { + this.Core.ParseExtensionAttribute(node, attrib); + } + + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "Data": + string columnName = null; + string data = null; + foreach (var attrib in child.Attributes()) + { + switch (attrib.Name.LocalName) + { + case "Column": + columnName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Value": + data = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.ParseExtensionAttribute(child, attrib); + break; + } + } + + this.Core.InnerTextDisallowed(node); + + if (null == columnName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Column")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixCustomTableCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, rowId, columnName)) + { + RowId = rowId, + ColumnRef = columnName, + TableRef = tableId, + Data = data + }); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + + if (!this.Core.EncounteredError) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId); + } + } + + /// + /// Parses a directory element. + /// + /// Element to parse. + /// Optional identifier of parent directory. + /// Disk id inherited from parent directory. + /// Path to source file as of yet. + private void ParseDirectoryElement(XElement node, string parentId, int diskId, string fileSource) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string componentGuidGenerationSeed = null; + var fileSourceAttribSet = false; + XAttribute nameAttribute = null; + var name = "."; // default to parent directory. + string shortName = null; + string sourceName = null; + string shortSourceName = null; + string symbols = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ComponentGuidGenerationSeed": + componentGuidGenerationSeed = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "FileSource": + fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + fileSourceAttribSet = true; + break; + case "Name": + if ("." == attrib.Value) + { + name = attrib.Value; + } + else + { + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + } + nameAttribute = attrib; + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "ShortSourceName": + shortSourceName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "SourceName": + if ("." == attrib.Value) + { + sourceName = attrib.Value; + } + else + { + sourceName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (nameAttribute == null) + { + if (!String.IsNullOrEmpty(shortName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); + } + } + else if (!String.IsNullOrEmpty(name)) + { + if (String.IsNullOrEmpty(shortName)) + { + } + else if (name == ".") + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name", name)); + } + else if (name.Equals(shortName, StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "Name", "ShortName", name)); + } + } + + if (String.IsNullOrEmpty(sourceName)) + { + if (!String.IsNullOrEmpty(shortSourceName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName")); + } + } + else + { + if (String.IsNullOrEmpty(shortSourceName)) + { + } + else if (sourceName == ".") + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName", sourceName)); + } + else if (sourceName.Equals(shortSourceName, StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "SourceName", "ShortSourceName", sourceName)); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); + } + else if (WindowsInstallerStandard.IsStandardDirectory(id.Id)) + { + if (String.IsNullOrEmpty(sourceName)) + { + this.Core.Write(CompilerWarnings.DefiningStandardDirectoryDeprecated(sourceLineNumbers, id.Id)); + } + + if (id.Id == "TARGETDIR" && name != "SourceDir" && shortName == null && shortSourceName == null && sourceName == null) + { + this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name)); + } + } + + // Update the file source path appropriately. + if (fileSourceAttribSet) + { + if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); + } + } + else // add the appropriate part of this directory element to the file source. + { + string append = String.IsNullOrEmpty(sourceName) ? name : sourceName; + + if (!String.IsNullOrEmpty(append)) + { + fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id.Id, fileSource); + break; + case "Directory": + this.ParseDirectoryElement(child, id.Id, diskId, fileSource); + break; + case "Merge": + this.ParseMergeElement(child, id.Id, diskId); + break; + case "SymbolPath": + if (null != symbols) + { + symbols += ";" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) + { + ParentDirectoryRef = parentId, + Name = name, + ShortName = shortName, + SourceName = sourceName, + SourceShortName = shortSourceName, + ComponentGuidGenerationSeed = componentGuidGenerationSeed + }); + + if (null != symbols) + { + this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, id) + { + SymbolType = SymbolPathType.Directory, + SymbolId = id.Id, + SymbolPaths = symbols, + }); + } + } + } + + /// + /// Parses a directory reference element. + /// + /// Element to parse. + private void ParseDirectoryRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var diskId = CompilerConstants.IntegerNotSet; + var fileSource = String.Empty; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "FileSource": + fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (WindowsInstallerStandard.IsStandardDirectory(id)) + { + this.Core.Write(CompilerWarnings.DirectoryRefStandardDirectoryDeprecated(sourceLineNumbers, id)); + } + + if (!String.IsNullOrEmpty(fileSource) && !fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id, fileSource); + break; + case "Directory": + this.ParseDirectoryElement(child, id, diskId, fileSource); + break; + case "Merge": + this.ParseMergeElement(child, id, diskId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses a directory search element. + /// + /// Element to parse. + /// Signature of parent search element. + /// Signature of search element. + private string ParseDirectorySearchElement(XElement node, string parentSignature) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var depth = CompilerConstants.IntegerNotSet; + string path = null; + var assignToProperty = false; + string signature = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Depth": + depth = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Path": + path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "AssignToProperty": + assignToProperty = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("dir", path, depth.ToString()); + } + + signature = id.Id; + + var oneChild = false; + var hasFileSearch = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchElement(child, id.Id); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, id.Id); + break; + case "FileSearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + hasFileSearch = true; + signature = this.ParseFileSearchElement(child, id.Id, assignToProperty, depth); + break; + case "FileSearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + + // If AssignToProperty is set, only a FileSearch + // or no child element can be nested. + if (assignToProperty) + { + if (!hasFileSearch) + { + this.Core.Write(ErrorMessages.IllegalParentAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AssignToProperty", child.Name.LocalName)); + } + else if (!oneChild) + { + // This a normal directory search. + assignToProperty = false; + } + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var access = id.Access; + var rowId = id.Id; + + // If AssignToProperty is set, the DrLocator row created by + // ParseFileSearchElement creates the directory entry to return + // and the row created here is for the file search. + if (assignToProperty) + { + access = AccessModifier.Section; + rowId = signature; + + // The property should be set to the directory search Id. + signature = id.Id; + } + + var symbol = this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(access, rowId, parentSignature, path)) + { + SignatureRef = rowId, + Parent = parentSignature, + Path = path, + }); + + if (CompilerConstants.IntegerNotSet != depth) + { + symbol.Depth = depth; + } + } + + return signature; + } + + /// + /// Parses a directory search reference element. + /// + /// Element to parse. + /// Signature of parent search element. + /// Signature of search element. + private string ParseDirectorySearchRefElement(XElement node, string parentSignature) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + Identifier parent = null; + string path = null; + string signature = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Parent": + parent = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Path": + path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null != parent) + { + if (!String.IsNullOrEmpty(parentSignature)) + { + this.Core.Write(ErrorMessages.CanNotHaveTwoParents(sourceLineNumbers, id.Id, parent.Id, parentSignature)); + } + else + { + parentSignature = parent.Id; + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("dsr", parentSignature, path); + } + + signature = id.Id; + + var oneChild = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchElement(child, id.Id); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, id.Id); + break; + case "FileSearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); + break; + case "FileSearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.DrLocator, id.Id, parentSignature, path); + + return signature; + } + + /// + /// Parses a feature element. + /// + /// Element to parse. + /// The type of parent. + /// Optional identifer for parent feature. + /// Display value for last feature used to get the features to display in the same order as specified + /// in the source code. + private void ParseFeatureElement(XElement node, ComplexReferenceParentType parentType, string parentId, ref int lastDisplay) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string configurableDirectory = null; + string description = null; + var displayValue = "collapse"; + var level = 1; + string title = null; + + var installDefault = FeatureInstallDefault.Local; + var typicalDefault = FeatureTypicalDefault.Install; + var disallowAbsent = false; + var disallowAdvertise = false; + var display = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "AllowAbsent": + disallowAbsent = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); + break; + case "AllowAdvertise": + disallowAdvertise = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No); + break; + case "ConfigurableDirectory": + configurableDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, configurableDirectory); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Display": + displayValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "InstallDefault": + var installDefaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (installDefaultValue) + { + case "followParent": + if (ComplexReferenceParentType.Product == parentType) + { + this.Core.Write(ErrorMessages.RootFeatureCannotFollowParent(sourceLineNumbers)); + } + //bits = bits | MsiInterop.MsidbFeatureAttributesFollowParent; + installDefault = FeatureInstallDefault.FollowParent; + break; + case "local": // this is the default + installDefault = FeatureInstallDefault.Local; + break; + case "source": + //bits = bits | MsiInterop.MsidbFeatureAttributesFavorSource; + installDefault = FeatureInstallDefault.Source; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installDefaultValue, "followParent", "local", "source")); + break; + } + break; + case "Level": + level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Title": + title = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if ("PUT-FEATURE-TITLE-HERE" == title) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, title)); + } + break; + case "TypicalDefault": + var typicalValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typicalValue) + { + case "advertise": + //bits |= MsiInterop.MsidbFeatureAttributesFavorAdvertise; + typicalDefault = FeatureTypicalDefault.Advertise; + break; + case "install": // this is the default + typicalDefault = FeatureTypicalDefault.Install; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typicalValue, "advertise", "install")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (38 < id.Id.Length) + { + this.Core.Write(ErrorMessages.FeatureNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + + if (null != configurableDirectory && configurableDirectory.ToUpper(CultureInfo.InvariantCulture) != configurableDirectory) + { + this.Core.Write(ErrorMessages.FeatureConfigurableDirectoryNotUppercase(sourceLineNumbers, node.Name.LocalName, "ConfigurableDirectory", configurableDirectory)); + } + + if (FeatureTypicalDefault.Advertise == typicalDefault && disallowAdvertise) + { + this.Core.Write(ErrorMessages.FeatureCannotFavorAndDisallowAdvertise(sourceLineNumbers, node.Name.LocalName, "TypicalDefault", "advertise", "AllowAdvertise", "no")); + } + + var childDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id, null); + break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id.Id, null); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id.Id, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id.Id, ref childDisplay); + break; + case "FeatureGroupRef": + this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id.Id); + break; + case "Level": + this.ParseLevelElement(child, id.Id); + break; + case "MergeRef": + this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + switch (displayValue) + { + case "collapse": + lastDisplay = (lastDisplay | 1) + 1; + display = lastDisplay; + break; + case "expand": + lastDisplay = (lastDisplay + 1) | 1; + display = lastDisplay; + break; + case "hidden": + display = 0; + break; + default: + if (!Int32.TryParse(displayValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out display)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Display", displayValue, "collapse", "expand", "hidden")); + } + else + { + // Save the display value (if its not hidden) for subsequent rows + if (0 != display) + { + lastDisplay = display; + } + } + break; + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, id) + { + ParentFeatureRef = null, // this field is set in the linker + Title = title, + Description = description, + Display = display, + Level = level, + DirectoryRef = configurableDirectory, + DisallowAbsent = disallowAbsent, + DisallowAdvertise = disallowAdvertise, + InstallDefault = installDefault, + TypicalDefault = typicalDefault, + }); + + if (ComplexReferenceParentType.Unknown != parentType) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id.Id, false); + } + } + } + + /// + /// Parses a feature reference element. + /// + /// Element to parse. + /// The type of parent. + /// Optional identifier for parent feature. + private void ParseFeatureRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var ignoreParent = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, id); + break; + case "IgnoreParent": + ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + var lastDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id, null); + break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id, null); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id, ref lastDisplay); + break; + case "FeatureGroup": + this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Feature, id); + break; + case "FeatureGroupRef": + this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id); + break; + case "MergeRef": + this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (ComplexReferenceParentType.Unknown != parentType && YesNoType.Yes != ignoreParent) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id, false); + } + } + } + + /// + /// Parses a feature group element. + /// + /// Element to parse. + /// + /// + private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + var lastDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); + break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, ref lastDisplay); + break; + case "FeatureGroupRef": + this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); + break; + case "MergeRef": + this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixFeatureGroupSymbol(sourceLineNumbers, id)); + + //Add this FeatureGroup and its parent in WixGroup. + this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.FeatureGroup, id.Id); + } + } + + /// + /// Parses a feature group reference element. + /// + /// Element to parse. + /// The type of parent. + /// Identifier of parent element. + private void ParseFeatureGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + Debug.Assert(ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Product == parentType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var ignoreParent = YesNoType.NotSet; + var primary = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixFeatureGroup, id); + break; + case "IgnoreParent": + ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Primary": + primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + if (YesNoType.Yes != ignoreParent) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.FeatureGroup, id, (YesNoType.Yes == primary)); + } + } + } + + /// + /// Parses an environment element. + /// + /// Element to parse. + /// Identifier of parent component. + private void ParseEnvironmentElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string name = null; + EnvironmentActionType? action = null; + EnvironmentPartType? part = null; + var permanent = false; + var separator = ";"; // default to ';' + var system = false; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "create": + action = EnvironmentActionType.Create; + break; + case "set": + action = EnvironmentActionType.Set; + break; + case "remove": + action = EnvironmentActionType.Remove; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "create", "set", "remove")); + break; + } + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Part": + var partValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (partValue) + { + case "all": + part = EnvironmentPartType.All; + break; + case "first": + part = EnvironmentPartType.First; + break; + case "last": + part = EnvironmentPartType.Last; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Part", partValue, "all", "first", "last")); + break; + } + break; + case "Permanent": + permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Separator": + separator = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "System": + system = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("env", ((int?)action)?.ToString(), name, ((int?)part)?.ToString(), system.ToString()); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (part.HasValue && action == EnvironmentActionType.Create) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); + } + + //if (Wix.Environment.PartType.NotSet != partType) + //{ + // if ("+" == action) + // { + // this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create")); + // } + + // switch (partType) + // { + // case Wix.Environment.PartType.all: + // break; + // case Wix.Environment.PartType.first: + // text = String.Concat(text, separator, "[~]"); + // break; + // case Wix.Environment.PartType.last: + // text = String.Concat("[~]", separator, text); + // break; + // } + //} + + //if (permanent) + //{ + // uninstall = null; + //} + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new EnvironmentSymbol(sourceLineNumbers, id) + { + Name = name, + Value = value, + Separator = separator, + Action = action, + Part = part, + Permanent = permanent, + System = system, + ComponentRef = componentId + }); + } + } + + /// + /// Parses an error element. + /// + /// Element to parse. + private void ParseErrorElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var id = CompilerConstants.IntegerNotSet; + string message = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Message": + message = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (CompilerConstants.IntegerNotSet == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = CompilerConstants.IllegalInteger; + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ErrorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id)) + { + Message = message + }); + } + } + + /// + /// Parses an extension element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Flag if this extension is advertised. + /// ProgId for extension. + private void ParseExtensionElement(XElement node, string componentId, YesNoType advertise, string progId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string extension = null; + string mime = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + extension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Advertise": + var extensionAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + if ((YesNoType.No == advertise && YesNoType.Yes == extensionAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == extensionAdvertise)) + { + this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, extensionAdvertise.ToString(), advertise.ToString())); + } + advertise = extensionAdvertise; + break; + case "ContentType": + mime = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + var context = new Dictionary() { { "ProgId", progId }, { "ComponentId", componentId } }; + this.Core.ParseExtensionAttribute(node, attrib, context); + } + } + + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Verb": + this.ParseVerbElement(child, extension, progId, componentId, advertise); + break; + case "MIME": + var newMime = this.ParseMIMEElement(child, extension, componentId, advertise); + if (null != newMime && null == mime) + { + mime = newMime; + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (YesNoType.Yes == advertise) + { + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ExtensionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, extension, componentId)) + { + Extension = extension, + ComponentRef = componentId, + ProgIdRef = progId, + MimeRef = mime, + FeatureRef = Guid.Empty.ToString("B"), + }); + + this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Verb); + } + } + else if (YesNoType.No == advertise) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), String.Empty, progId, componentId); // Extension + if (null != mime) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), "Content Type", mime, componentId); // Extension's MIME ContentType + } + } + } + + + /// + /// Parses a file element. + /// + /// File element to parse. + /// Parent's component id. + /// Ancestor's directory id. + /// Disk id inherited from parent component. + /// Default source path of parent directory. + /// This will be set with the possible keyPath for the parent component. + /// true if the component is 64-bit. + /// + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseFileElement(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, bool win64Component, string componentGuid) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var assemblyType = AssemblyType.NotAnAssembly; + string assemblyApplication = null; + string assemblyManifest = null; + string bindPath = null; + + //int bits = MsiInterop.MsidbFileAttributesVital; + var readOnly = false; + var checksum = false; + bool? compressed = null; + var hidden = false; + var system = false; + var vital = true; // assume all files are vital. + + string companionFile = null; + string defaultLanguage = null; + var defaultSize = 0; + string defaultVersion = null; + string fontTitle = null; + var keyPath = YesNoType.NotSet; + string name = null; + var patchGroup = CompilerConstants.IntegerNotSet; + var patchIgnore = false; + var patchIncludeWholeFile = false; + var patchAllowIgnoreOnError = false; + + string ignoreLengths = null; + string ignoreOffsets = null; + string protectLengths = null; + string protectOffsets = null; + string symbols = null; + + string procArch = null; + int? selfRegCost = null; + string shortName = null; + var source = sourcePath; // assume we'll use the parents as the source for this file + var sourceSet = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Assembly": + var assemblyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (assemblyValue) + { + case ".net": + assemblyType = AssemblyType.DotNetAssembly; + break; + case "no": + assemblyType = AssemblyType.NotAnAssembly; + break; + case "win32": + assemblyType = AssemblyType.Win32Assembly; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "Assembly", assemblyValue, "no", "win32", ".net")); + break; + } + break; + case "AssemblyApplication": + assemblyApplication = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyApplication); + break; + case "AssemblyManifest": + assemblyManifest = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyManifest); + break; + case "BindPath": + bindPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Checksum": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + checksum = true; + //bits |= MsiInterop.MsidbFileAttributesChecksum; + } + break; + case "CompanionFile": + companionFile = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, companionFile); + break; + case "Compressed": + var compressedValue = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + if (YesNoDefaultType.Yes == compressedValue) + { + compressed = true; + //bits |= MsiInterop.MsidbFileAttributesCompressed; + } + else if (YesNoDefaultType.No == compressedValue) + { + compressed = false; + //bits |= MsiInterop.MsidbFileAttributesNoncompressed; + } + break; + case "DefaultLanguage": + defaultLanguage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DefaultSize": + defaultSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "DefaultVersion": + defaultVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "FontTitle": + fontTitle = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Hidden": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + hidden = true; + //bits |= MsiInterop.MsidbFileAttributesHidden; + } + break; + case "KeyPath": + keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "PatchGroup": + patchGroup = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); + break; + case "PatchIgnore": + patchIgnore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "PatchWholeFile": + patchIncludeWholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "PatchAllowIgnoreOnError": + patchAllowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ProcessorArchitecture": + var procArchValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (procArchValue) + { + case "msil": + procArch = "MSIL"; + break; + case "x86": + procArch = "x86"; + break; + case "x64": + procArch = "amd64"; + break; + case "arm64": + procArch = "arm64"; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "ProcessorArchitecture", procArchValue, "msil", "x86", "x64")); + break; + } + break; + case "ReadOnly": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + readOnly = true; + //bits |= MsiInterop.MsidbFileAttributesReadOnly; + } + break; + case "SelfRegCost": + selfRegCost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "Source": + source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + sourceSet = true; + break; + case "System": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + system = true; + //bits |= MsiInterop.MsidbFileAttributesSystem; + } + break; + case "TrueType": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + fontTitle = String.Empty; + } + break; + case "Vital": + var isVital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + if (YesNoType.Yes == isVital) + { + vital = true; + //bits |= MsiInterop.MsidbFileAttributesVital; + } + else if (YesNoType.No == isVital) + { + vital = false; + //bits &= ~MsiInterop.MsidbFileAttributesVital; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null != companionFile) + { + // the companion file cannot be the key path of a component + if (YesNoType.Yes == keyPath) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "CompanionFile", "KeyPath", "yes")); + } + } + + if (sourceSet && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && null == name) + { + name = Path.GetFileName(source); + if (!this.Core.IsValidLongFilename(name, false)) + { + this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); + } + } + + if (name == null) + { + if (shortName == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else + { + name = shortName; + shortName = null; + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("fil", directoryId, name); + } + + if (null != defaultVersion && null != companionFile) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DefaultVersion", "CompanionFile", companionFile)); + } + + if (AssemblyType.NotAnAssembly == assemblyType) + { + if (null != assemblyManifest) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyManifest")); + } + + if (null != assemblyApplication) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyApplication")); + } + } + else + { + if (AssemblyType.Win32Assembly == assemblyType && null == assemblyManifest) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AssemblyManifest", "Assembly", "win32")); + } + + // allow "*" guid components to omit explicit KeyPath as they can have only one file and therefore this file is the keypath + if (YesNoType.Yes != keyPath && "*" != componentGuid) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", (AssemblyType.DotNetAssembly == assemblyType ? ".net" : "win32"), "KeyPath", "yes")); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AppId": + this.ParseAppIdElement(child, componentId, YesNoType.NotSet, id.Id, null, null); + break; + case "AssemblyName": + this.ParseAssemblyName(child, componentId); + break; + case "Class": + this.ParseClassElement(child, componentId, YesNoType.NotSet, id.Id, null, null, null); + break; + case "CopyFile": + this.ParseCopyFileElement(child, componentId, id.Id); + break; + case "IgnoreRange": + this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); + break; + case "ODBCDriver": + this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCDriver); + break; + case "ODBCTranslator": + this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCTranslator); + break; + case "Permission": + this.ParsePermissionElement(child, id.Id, "File"); + break; + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "File"); + break; + case "ProtectRange": + this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); + break; + case "Shortcut": + this.ParseShortcutElement(child, componentId, node.Name.LocalName, id.Id, keyPath); + break; + case "SymbolPath": + if (null != symbols) + { + symbols += ";" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + case "TypeLib": + this.ParseTypeLibElement(child, componentId, id.Id, win64Component); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "FileId", id?.Id }, { "ComponentId", componentId }, { "DirectoryId", directoryId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (!this.Core.EncounteredError) + { + var patchAttributes = PatchAttributeType.None; + if (patchIgnore) + { + patchAttributes |= PatchAttributeType.Ignore; + } + if (patchIncludeWholeFile) + { + patchAttributes |= PatchAttributeType.IncludeWholeFile; + } + if (patchAllowIgnoreOnError) + { + patchAttributes |= PatchAttributeType.AllowIgnoreOnError; + } + + if (String.IsNullOrEmpty(source)) + { + source = name; + } + else if (source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) // if source relies on parent directories, append the file name + { + source = Path.Combine(source, name); + } + + var attributes = FileSymbolAttributes.None; + attributes |= readOnly ? FileSymbolAttributes.ReadOnly : 0; + attributes |= hidden ? FileSymbolAttributes.Hidden : 0; + attributes |= system ? FileSymbolAttributes.System : 0; + attributes |= vital ? FileSymbolAttributes.Vital : 0; + attributes |= checksum ? FileSymbolAttributes.Checksum : 0; + attributes |= compressed.HasValue && compressed == true ? FileSymbolAttributes.Compressed : 0; + attributes |= compressed.HasValue && compressed == false ? FileSymbolAttributes.Uncompressed : 0; + + this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Name = name, + ShortName = shortName, + FileSize = defaultSize, + Version = companionFile ?? defaultVersion, + Language = defaultLanguage, + Attributes = attributes, + + DirectoryRef = directoryId, + DiskId = (CompilerConstants.IntegerNotSet == diskId) ? null : (int?)diskId, + Source = new IntermediateFieldPathValue { Path = source }, + + FontTitle = fontTitle, + SelfRegCost = selfRegCost, + BindPath = bindPath, + + PatchGroup = (CompilerConstants.IntegerNotSet == patchGroup) ? null : (int?)patchGroup, + PatchAttributes = patchAttributes, + + // Delta patching information + RetainLengths = protectLengths, + IgnoreOffsets = ignoreOffsets, + IgnoreLengths = ignoreLengths, + RetainOffsets = protectOffsets, + SymbolPaths = symbols, + }); + + if (AssemblyType.NotAnAssembly != assemblyType) + { + this.Core.AddSymbol(new AssemblySymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + FeatureRef = Guid.Empty.ToString("B"), + ManifestFileRef = assemblyManifest, + ApplicationFileRef = assemblyApplication, + Type = assemblyType, + ProcessorArchitecture = procArch, + }); + } + } + + if (CompilerConstants.IntegerNotSet != diskId) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); + } + + // If this component does not have a companion file this file is a possible keypath. + possibleKeyPath = null; + if (null == companionFile) + { + possibleKeyPath = id.Id; + } + + return keyPath; + } + + /// + /// Parses a file search element. + /// + /// Element to parse. + /// Signature of parent search element. + /// Whether this search element is used to search for the parent directory. + /// The depth specified by the parent search element. + /// Signature of search element. + private string ParseFileSearchElement(XElement node, string parentSignature, bool parentDirectorySearch, int parentDepth) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string languages = null; + var minDate = CompilerConstants.IntegerNotSet; + var maxDate = CompilerConstants.IntegerNotSet; + var maxSize = CompilerConstants.IntegerNotSet; + var minSize = CompilerConstants.IntegerNotSet; + string maxVersion = null; + string minVersion = null; + string name = null; + string shortName = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "MinVersion": + minVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MaxVersion": + maxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MinSize": + minSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "MaxSize": + maxSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "MinDate": + minDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); + break; + case "MaxDate": + maxDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib); + break; + case "Languages": + languages = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Using both ShortName and Name will not always work due to a Windows Installer bug. + if (null != shortName && null != name) + { + this.Core.Write(WarningMessages.FileSearchFileNameIssue(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name")); + } + else if (null == shortName && null == name) // at least one name must be specified. + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (this.Core.IsValidShortFilename(name, false)) + { + if (null == shortName) + { + shortName = name; + name = null; + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName")); + } + } + + if (null == id) + { + if (String.IsNullOrEmpty(parentSignature)) + { + id = this.Core.CreateIdentifier("fs", name ?? shortName); + } + else // reuse parent signature in the Signature table + { + id = new Identifier(AccessModifier.Section, parentSignature); + } + } + + var isSameId = String.Equals(id.Id, parentSignature, StringComparison.Ordinal); + if (parentDirectorySearch) + { + // If searching for the parent directory, the Id attribute + // value must be specified and unique. + if (isSameId) + { + this.Core.Write(ErrorMessages.UniqueFileSearchIdRequired(sourceLineNumbers, parentSignature, node.Name.LocalName)); + } + } + else if (parentDepth > 1) + { + // Otherwise, if the depth > 1 the Id must be absent or the same + // as the parent DirectorySearch if AssignToProperty is not set. + if (!isSameId) + { + this.Core.Write(ErrorMessages.IllegalSearchIdForParentDepth(sourceLineNumbers, id.Id, parentSignature)); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new SignatureSymbol(sourceLineNumbers, id) + { + FileName = name ?? shortName, + MinVersion = minVersion, + MaxVersion = maxVersion, + Languages = languages + }); + + if (CompilerConstants.IntegerNotSet != minSize) + { + symbol.MinSize = minSize; + } + + if (CompilerConstants.IntegerNotSet != maxSize) + { + symbol.MaxSize = maxSize; + } + + if (CompilerConstants.IntegerNotSet != minDate) + { + symbol.MinDate = minDate; + } + + if (CompilerConstants.IntegerNotSet != maxDate) + { + symbol.MaxDate = maxDate; + } + + // Create a DrLocator row to associate the file with a directory + // when a different identifier is specified for the FileSearch. + if (!isSameId) + { + if (parentDirectorySearch) + { + // Creates the DrLocator row for the directory search while + // the parent DirectorySearch creates the file locator row. + this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, parentSignature, id.Id, String.Empty)) + { + SignatureRef = parentSignature, + Parent = id.Id + }); + } + else + { + this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id.Id, parentSignature, String.Empty)) + { + SignatureRef = id.Id, + Parent = parentSignature + }); + } + } + } + + return id.Id; // the id of the FileSearch element is its signature + } + + + /// + /// Parses a fragment element. + /// + /// Element to parse. + private void ParseFragmentElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + this.activeName = null; + this.activeLanguage = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // NOTE: Id is not required for Fragments, this is a departure from the normal run of the mill processing. + + this.Core.CreateActiveSection(id?.Id, SectionType.Fragment, this.Context.CompilationId); + + var featureDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "_locDefinition": + break; + case "AdminExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); + break; + case "AdminUISequence": + this.ParseSequenceElement(child, SequenceTable.AdminUISequence); + break; + case "AdvertiseExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); + break; + case "InstallExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); + break; + case "InstallUISequence": + this.ParseSequenceElement(child, SequenceTable.InstallUISequence); + break; + case "AppId": + this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); + break; + case "Binary": + this.ParseBinaryElement(child); + break; + case "BootstrapperApplication": + this.ParseBootstrapperApplicationElement(child); + break; + case "BootstrapperApplicationRef": + this.ParseBootstrapperApplicationRefElement(child); + break; + case "BundleCustomData": + this.ParseBundleCustomDataElement(child); + break; + case "BundleCustomDataRef": + this.ParseBundleCustomDataRefElement(child); + break; + case "BundleExtension": + this.ParseBundleExtensionElement(child); + break; + case "BundleExtensionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); + break; + case "ComplianceCheck": + this.ParseComplianceCheckElement(child); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "ComponentGroup": + this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); + break; + case "Container": + this.ParseContainerElement(child); + break; + case "CustomAction": + this.ParseCustomActionElement(child); + break; + case "CustomActionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); + break; + case "CustomTable": + this.ParseCustomTableElement(child); + break; + case "CustomTableRef": + this.ParseCustomTableRefElement(child); + break; + case "Directory": + this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); + break; + case "DirectoryRef": + this.ParseDirectoryRefElement(child); + break; + case "EmbeddedChainer": + this.ParseEmbeddedChainerElement(child); + break; + case "EmbeddedChainerRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); + break; + case "EnsureTable": + this.ParseEnsureTableElement(child); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.Unknown, null, ref featureDisplay); + break; + case "FeatureGroup": + this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.Unknown, null); + break; + case "Icon": + this.ParseIconElement(child); + break; + case "Media": + this.ParseMediaElement(child, null); + break; + case "MediaTemplate": + this.ParseMediaTemplateElement(child, null); + break; + case "Launch": + this.ParseLaunchElement(child); + break; + case "PackageGroup": + this.ParsePackageGroupElement(child); + break; + case "PackageCertificates": + case "PatchCertificates": + this.ParseCertificatesElement(child); + break; + case "PatchFamily": + this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id.Id); + break; + case "PatchFamilyGroup": + this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id.Id); + break; + case "PatchFamilyGroupRef": + this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id.Id); + break; + case "PayloadGroup": + this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "PropertyRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Property); + break; + case "RelatedBundle": + this.ParseRelatedBundleElement(child); + break; + case "Requires": + this.ParseRequiresElement(child, null); + break; + case "SetDirectory": + this.ParseSetDirectoryElement(child); + break; + case "SetProperty": + this.ParseSetPropertyElement(child); + break; + case "SetVariable": + this.ParseSetVariableElement(child); + break; + case "SetVariableRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); + break; + case "SFPCatalog": + string parentName = null; + this.ParseSFPCatalogElement(child, ref parentName); + break; + case "StandardDirectory": + this.ParseStandardDirectoryElement(child); + break; + case "UI": + this.ParseUIElement(child); + break; + case "UIRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); + break; + case "Upgrade": + this.ParseUpgradeElement(child); + break; + case "Variable": + this.ParseVariableElement(child); + break; + case "WixVariable": + this.ParseWixVariableElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError && null != id) + { + this.Core.AddSymbol(new WixFragmentSymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parses a launch condition element. + /// + /// Element to parse. + private void ParseLaunchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string condition = null; + string message = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Message": + message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(condition)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); + } + + if (String.IsNullOrEmpty(message)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Message")); + } + + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) + { + Condition = condition, + Description = message + }); + } + } + + /// + /// Parses a IniFile element. + /// + /// Element to parse. + /// Identifier of the parent component. + private void ParseIniFileElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + IniFileActionType? action = null; + string directory = null; + string key = null; + string name = null; + string section = null; + string shortName = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "addLine": + action = IniFileActionType.AddLine; + break; + case "addTag": + action = IniFileActionType.AddTag; + break; + case "removeLine": + action = IniFileActionType.RemoveLine; + break; + case "removeTag": + action = IniFileActionType.RemoveTag; + break; + case "": // error case handled by GetAttributeValue() + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", actionValue, "addLine", "addTag", "createLine", "removeLine", "removeTag")); + break; + } + break; + case "Directory": + directory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Section": + section = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (!action.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); + } + else if (IniFileActionType.AddLine == action || IniFileActionType.AddTag == action || IniFileActionType.CreateLine == action) + { + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == section) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("ini", directory, name ?? shortName, section, key, name); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new IniFileSymbol(sourceLineNumbers, id) + { + FileName = name, + ShortFileName = shortName, + DirProperty = directory, + Section = section, + Key = key, + Value = value, + Action = action.Value, + ComponentRef = componentId + }); + } + } + + /// + /// Parses an IniFile search element. + /// + /// Element to parse. + /// Signature for search element. + private string ParseIniFileSearchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var field = CompilerConstants.IntegerNotSet; + string key = null; + string name = null; + string section = null; + string shortName = null; + string signature = null; + var type = 1; // default is file + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Field": + field = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Section": + section = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "directory": + type = 0; + break; + case "file": + type = 1; + break; + case "raw": + type = 2; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "registry")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == section) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("ini", name, section, key, field.ToString(), type.ToString()); + } + + signature = id.Id; + + var oneChild = false; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "DirectorySearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + + // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column + signature = this.ParseDirectorySearchElement(child, id.Id); + break; + case "DirectorySearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseDirectorySearchRefElement(child, id.Id); + break; + case "FileSearch": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet); + id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures + break; + case "FileSearchRef": + if (oneChild) + { + this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName)); + } + oneChild = true; + var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures + id = new Identifier(AccessModifier.Section, newId); + signature = null; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new IniLocatorSymbol(sourceLineNumbers, id) + { + FileName = name, + ShortFileName = shortName, + Section = section, + Key = key, + Type = type + }); + + if (CompilerConstants.IntegerNotSet != field) + { + symbol.Field = field; + } + } + + return signature; + } + + /// + /// Parses an isolated component element. + /// + /// Element to parse. + /// Identifier of parent component. + private void ParseIsolateComponentElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string shared = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Shared": + shared = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, shared); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == shared) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Shared")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new IsolatedComponentSymbol(sourceLineNumbers) + { + SharedComponentRef = shared, + ApplicationComponentRef = componentId + }); + } + } + + /// + /// Parses a PatchCertificates or PackageCertificates element. + /// + /// The element to parse. + private void ParseCertificatesElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + // no attributes are supported for this element + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + this.Core.UnexpectedAttribute(node, attrib); + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "DigitalCertificate": + var name = this.ParseDigitalCertificateElement(child); + + if (!this.Core.EncounteredError) + { + if ("PatchCertificates" == node.Name.LocalName) + { + this.Core.AddSymbol(new MsiPatchCertificateSymbol(sourceLineNumbers) + { + PatchCertificate = name, + DigitalCertificateRef = name, + }); + } + else + { + this.Core.AddSymbol(new MsiPackageCertificateSymbol(sourceLineNumbers) + { + PackageCertificate = name, + DigitalCertificateRef = name, + }); + } + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses an digital certificate element. + /// + /// Element to parse. + /// The identifier of the certificate. + private string ParseDigitalCertificateElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (40 < id.Id.Length) + { + this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 40)); + + // No need to check for modularization problems since DigitalSignature and thus DigitalCertificate + // currently have no usage in merge modules. + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiDigitalCertificateSymbol(sourceLineNumbers, id) + { + CertData = sourceFile + }); + } + + return id.Id; + } + + /// + /// Parses an digital signature element. + /// + /// Element to parse. + /// Disk id inherited from parent media. + private void ParseDigitalSignatureElement(XElement node, string diskId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string certificateId = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // sanity check for debug to ensure the stream name will not be a problem + if (null != sourceFile) + { + Debug.Assert(62 >= "MsiDigitalSignature.Media.".Length + diskId.Length); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "DigitalCertificate": + certificateId = this.ParseDigitalCertificateElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == certificateId) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "DigitalCertificate")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiDigitalSignatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "Media", diskId)) + { + Table = "Media", + SignObject = diskId, + DigitalCertificateRef = certificateId, + Hash = sourceFile + }); + } + } + + /// + /// Parses a MajorUpgrade element. + /// + /// The element to parse. + /// The current context. + private void ParseMajorUpgradeElement(XElement node, IDictionary contextValues) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var migrateFeatures = true; + var ignoreRemoveFailure = false; + var allowDowngrades = false; + var allowSameVersionUpgrades = false; + var blockUpgrades = false; + string downgradeErrorMessage = null; + string disallowUpgradeErrorMessage = null; + string removeFeatures = null; + string schedule = null; + + var upgradeCode = contextValues["UpgradeCode"]; + if (String.IsNullOrEmpty(upgradeCode)) + { + this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "UpgradeCode", node.Name.LocalName)); + } + + var productVersion = contextValues["ProductVersion"]; + if (String.IsNullOrEmpty(productVersion)) + { + this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "Version", node.Name.LocalName)); + } + + var productLanguage = contextValues["ProductLanguage"]; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AllowDowngrades": + allowDowngrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "AllowSameVersionUpgrades": + allowSameVersionUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Disallow": + blockUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "DowngradeErrorMessage": + downgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisallowUpgradeErrorMessage": + disallowUpgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MigrateFeatures": + migrateFeatures = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "IgnoreLanguage": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + productLanguage = null; + } + break; + case "IgnoreRemoveFailure": + ignoreRemoveFailure = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "RemoveFeatures": + removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Schedule": + schedule = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!allowDowngrades && String.IsNullOrEmpty(downgradeErrorMessage)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes", true)); + } + + if (allowDowngrades && !String.IsNullOrEmpty(downgradeErrorMessage)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes")); + } + + if (allowDowngrades && allowSameVersionUpgrades) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AllowSameVersionUpgrades", "AllowDowngrades", "yes")); + } + + if (blockUpgrades && String.IsNullOrEmpty(disallowUpgradeErrorMessage)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes", true)); + } + + if (!blockUpgrades && !String.IsNullOrEmpty(disallowUpgradeErrorMessage)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes")); + } + + if (!this.Core.EncounteredError) + { + // create the row that performs the upgrade (or downgrade) + var symbol = this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeCode, + Remove = removeFeatures, + MigrateFeatures = migrateFeatures, + IgnoreRemoveFailures = ignoreRemoveFailure, + ActionProperty = WixUpgradeConstants.UpgradeDetectedProperty + }); + + if (allowDowngrades) + { + symbol.VersionMin = "0"; + symbol.Language = productLanguage; + symbol.VersionMinInclusive = true; + } + else + { + symbol.VersionMax = productVersion; + symbol.Language = productLanguage; + symbol.VersionMaxInclusive = allowSameVersionUpgrades; + } + + // Add launch condition that blocks upgrades + if (blockUpgrades) + { + this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) + { + Condition = WixUpgradeConstants.UpgradePreventedCondition, + Description = downgradeErrorMessage + }); + } + + // now create the Upgrade row and launch conditions to prevent downgrades (unless explicitly permitted) + if (!allowDowngrades) + { + this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeCode, + VersionMin = productVersion, + Language = productLanguage, + OnlyDetect = true, + IgnoreRemoveFailures = ignoreRemoveFailure, + ActionProperty = WixUpgradeConstants.DowngradeDetectedProperty + }); + + this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers) + { + Condition = WixUpgradeConstants.DowngradePreventedCondition, + Description = downgradeErrorMessage + }); + } + + // finally, schedule RemoveExistingProducts + string after = null; + switch (schedule) + { + case null: + case "afterInstallValidate": + after = "InstallValidate"; + break; + case "afterInstallInitialize": + after = "InstallInitialize"; + break; + case "afterInstallExecute": + after = "InstallExecute"; + break; + case "afterInstallExecuteAgain": + after = "InstallExecuteAgain"; + break; + case "afterInstallFinalize": + after = "InstallFinalize"; + break; + } + + this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, SequenceTable.InstallExecuteSequence, "RemoveExistingProducts", afterAction: after); + } + } + + /// + /// Parses a media element. + /// + /// Element to parse. + /// Set to the PatchId if parsing Patch/Media element otherwise null. + private void ParseMediaElement(XElement node, string patchId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var id = CompilerConstants.IntegerNotSet; + string cabinet = null; + CompressionLevel? compressionLevel = null; + string diskPrompt = null; + string layout = null; + var patch = null != patchId; + string volumeLabel = null; + string source = null; + string symbols = null; + + var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "Cabinet": + cabinet = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "CompressionLevel": + compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); + break; + case "DiskPrompt": + diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined + break; + case "EmbedCab": + embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Layout": + layout = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "VolumeLabel": + volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Source": + source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (CompilerConstants.IntegerNotSet == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = CompilerConstants.IllegalInteger; + } + + if (YesNoType.IllegalValue != embedCab) + { + if (YesNoType.Yes == embedCab) + { + if (null == cabinet) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "EmbedCab", "yes")); + } + else + { + if (62 < cabinet.Length) + { + this.Core.Write(ErrorMessages.MediaEmbeddedCabinetNameTooLong(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet, cabinet.Length)); + } + + cabinet = String.Concat("#", cabinet); + } + } + else // external cabinet file + { + // external cabinet files must use 8.3 filenames + if (!String.IsNullOrEmpty(cabinet) && !this.Core.IsValidLongFilename(cabinet) && !Common.ContainsValidBinderVariable(cabinet)) + { + this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet)); + } + } + } + + if (compressionLevel.HasValue && String.IsNullOrEmpty(cabinet)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "CompressionLevel")); + } + + if (patch) + { + // Default Source to a form of the Patch Id if none is specified. + if (null == source) + { + source = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); + } + } + + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "DigitalSignature": + if (YesNoType.Yes == embedCab) + { + this.Core.Write(ErrorMessages.SignedEmbeddedCabinet(childSourceLineNumbers)); + } + else if (null == cabinet) + { + this.Core.Write(ErrorMessages.ExpectedSignedCabinetName(childSourceLineNumbers)); + } + else + { + this.ParseDigitalSignatureElement(child, id.ToString(CultureInfo.InvariantCulture.NumberFormat)); + } + break; + case "PatchBaseline": + if (patch) + { + this.ParsePatchBaselineElement(child, id); + } + else + { + this.Core.UnexpectedElement(node, child); + } + break; + case "SymbolPath": + if (null != symbols) + { + symbols += "" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + // add the row to the section + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id)) + { + DiskId = id, + DiskPrompt = diskPrompt, + Cabinet = cabinet, + VolumeLabel = volumeLabel, + Source = source, // the Source column is only set when creating a patch + CompressionLevel = compressionLevel, + Layout = layout + }); + + if (null != symbols) + { + this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Media, id)) + { + SymbolType = SymbolPathType.Media, + SymbolId = id.ToString(CultureInfo.InvariantCulture), + SymbolPaths = symbols + }); + } + } + } + + /// + /// Parses a media template element. + /// + /// Element to parse. + /// Set to the PatchId if parsing Patch/Media element otherwise null. + private void ParseMediaTemplateElement(XElement node, string patchId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var cabinetTemplate = "cab{0}.cab"; + string diskPrompt = null; + var patch = null != patchId; + string volumeLabel = null; + int? maximumUncompressedMediaSize = null; + int? maximumCabinetSizeForLargeFileSplitting = null; + CompressionLevel? compressionLevel = null; // this defaults to 'medium' in the MSI and Burn backends + + var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "CabinetTemplate": + var authoredCabinetTemplateValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + if (!String.IsNullOrEmpty(authoredCabinetTemplateValue)) + { + cabinetTemplate = authoredCabinetTemplateValue; + } + + // Create an example cabinet name using the maximum number of cabinets supported, 999. + var exampleCabinetName = String.Format(cabinetTemplate, "###"); + if (!this.Core.IsValidLocIdentifier(exampleCabinetName)) + { + // The example name should not match the authored template since that would nullify the + // reason for having multiple cabinets. External cabinet files must also be valid file names. + if (exampleCabinetName.Equals(authoredCabinetTemplateValue, StringComparison.OrdinalIgnoreCase) || !this.Core.IsValidLongFilename(exampleCabinetName, false)) + { + this.Core.Write(ErrorMessages.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate)); + } + else if (!this.Core.IsValidLongFilename(exampleCabinetName) && !Common.ContainsValidBinderVariable(exampleCabinetName)) // ignore short names with wix variables because it rarely works out. + { + this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate)); + } + } + break; + case "CompressionLevel": + compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib); + break; + case "DiskPrompt": + diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined + this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "EmbedCab": + embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "VolumeLabel": + volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "MaximumUncompressedMediaSize": + maximumUncompressedMediaSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); + break; + case "MaximumCabinetSizeForLargeFileSplitting": + maximumCabinetSizeForLargeFileSplitting = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Compiler.MinValueOfMaxCabSizeForLargeFileSplitting, Compiler.MaxValueOfMaxCabSizeForLargeFileSplitting); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (YesNoType.Yes == embedCab) + { + cabinetTemplate = String.Concat("#", cabinetTemplate); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, 1)) + { + DiskId = 1 + }); + + this.Core.AddSymbol(new WixMediaTemplateSymbol(sourceLineNumbers) + { + CabinetTemplate = cabinetTemplate, + VolumeLabel = volumeLabel, + DiskPrompt = diskPrompt, + MaximumUncompressedMediaSize = maximumUncompressedMediaSize, + MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting, + CompressionLevel = compressionLevel + }); + + //else + //{ + // mediaTemplateRow.MaximumUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize; + //} + + //else + //{ + // mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting = 0; // Default value of 0 corresponds to max size of 2048 MB (i.e. 2 GB) + //} + } + } + + /// + /// Parses a merge element. + /// + /// Element to parse. + /// Identifier for parent directory. + /// Disk id inherited from parent directory. + private void ParseMergeElement(XElement node, string directoryId, int diskId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var configData = String.Empty; + FileSymbolAttributes attributes = 0; + string language = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat)); + break; + case "FileCompression": + var compress = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + attributes |= compress == YesNoType.Yes ? FileSymbolAttributes.Compressed : 0; + attributes |= compress == YesNoType.No ? FileSymbolAttributes.Uncompressed : 0; + break; + case "Language": + language = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == language) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ConfigurationData": + if (0 == configData.Length) + { + configData = this.ParseConfigurationDataElement(child); + } + else + { + configData = String.Concat(configData, ",", this.ParseConfigurationDataElement(child)); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new WixMergeSymbol(sourceLineNumbers, id) + { + DirectoryRef = directoryId, + SourceFile = sourceFile, + DiskId = diskId, + ConfigurationData = configData, + FileAttributes = attributes, + FeatureRef = Guid.Empty.ToString("B") + }); + + symbol.Set((int)WixMergeSymbolFields.Language, language); + } + } + + /// + /// Parses a standard directory element. + /// + /// Element to parse. + private void ParseStandardDirectoryElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(id)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (!WindowsInstallerStandard.IsStandardDirectory(id)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Id", id, String.Join(", \"", WindowsInstallerStandard.StandardDirectories().Select(d => d.Id.Id)))); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId: CompilerConstants.IntegerNotSet, id, srcPath: String.Empty); + break; + case "Directory": + this.ParseDirectoryElement(child, id, diskId: CompilerConstants.IntegerNotSet, fileSource: String.Empty); + break; + case "Merge": + this.ParseMergeElement(child, id, diskId: CompilerConstants.IntegerNotSet); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses a configuration data element. + /// + /// Element to parse. + /// String in format "name=value" with '%', ',' and '=' hex encoded. + private string ParseConfigurationDataElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string name = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else // need to hex encode these characters + { + name = name.Replace("%", "%25"); + name = name.Replace("=", "%3D"); + name = name.Replace(",", "%2C"); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + else // need to hex encode these characters + { + value = value.Replace("%", "%25"); + value = value.Replace("=", "%3D"); + value = value.Replace(",", "%2C"); + } + + this.Core.ParseForExtensionElements(node); + + return String.Concat(name, "=", value); + } + + /// + /// Parses a Level element. + /// + /// Element to parse. + /// Id of the parent Feature element. + private void ParseLevelElement(XElement node, string featureId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string condition = null; + int? level = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (!level.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); + } + + if (String.IsNullOrEmpty(condition)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + if (CompilerConstants.IntegerNotSet == level) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level")); + level = CompilerConstants.IllegalInteger; + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ConditionSymbol(sourceLineNumbers) + { + FeatureRef = featureId, + Level = level.Value, + Condition = condition + }); + } + } + } + + /// + /// Parses a merge reference element. + /// + /// Element to parse. + /// Parents complex reference type. + /// Identifier for parent feature or feature group. + private void ParseMergeRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var primary = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixMerge, id); + break; + case "Primary": + primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Module, id, (YesNoType.Yes == primary)); + } + + /// + /// Parses a mime element. + /// + /// Element to parse. + /// Identifier for parent extension. + /// Identifier for parent component. + /// Flag if the parent element is advertised. + /// Content type if this is the default for the MIME type. + private string ParseMIMEElement(XElement node, string extension, string componentId, YesNoType parentAdvertised) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string classId = null; + string contentType = null; + var advertise = parentAdvertised; + var returnContentType = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Advertise": + advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Class": + classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "ContentType": + contentType = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Default": + returnContentType = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == contentType) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ContentType")); + } + + // if the advertise state has not been set, default to non-advertised + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + this.Core.ParseForExtensionElements(node); + + if (YesNoType.Yes == advertise) + { + if (YesNoType.Yes != parentAdvertised) + { + this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), parentAdvertised.ToString())); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MIMESymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, contentType)) + { + ContentType = contentType, + ExtensionRef = extension, + CLSID = classId + }); + } + } + else if (YesNoType.No == advertise) + { + if (YesNoType.Yes == returnContentType && YesNoType.Yes == parentAdvertised) + { + this.Core.Write(ErrorMessages.CannotDefaultMismatchedAdvertiseStates(sourceLineNumbers)); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "Extension", String.Concat(".", extension), componentId); + if (null != classId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "CLSID", classId, componentId); + } + } + + return YesNoType.Yes == returnContentType ? contentType : null; + } + + /// + /// Parses a patch property element. + /// + /// The element to parse. + /// True if parsing an patch element. + private void ParsePatchPropertyElement(XElement node, bool patch) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string name = null; + string company = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Company": + company = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (patch) + { + // /Patch/PatchProperty goes directly into MsiPatchMetadata table + this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, company, name)) + { + Company = company, + Property = name, + Value = value + }); + } + else + { + if (null != company) + { + this.Core.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); + } + this.AddPrivateProperty(sourceLineNumbers, name, value); + } + } + + /// + /// Adds a row to the properties table. + /// + /// Source line numbers. + /// Name of the property. + /// Value of the property. + private void AddPrivateProperty(SourceLineNumber sourceLineNumbers, string name, string value) + { + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new PropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) + { + Value = value + }); + } + } + + /// + /// Parses a TargetProductCode element. + /// + /// The element to parse. + /// The id from the node. + private string ParseTargetProductCodeElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (id.Length > 0 && "*" != id) + { + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return id; + } + + /// + /// Parses a ReplacePatch element. + /// + /// The element to parse. + /// The id from the node. + private string ParseReplacePatchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return id; + } + + /// + /// Parses a symbol path element. + /// + /// The element to parse. + /// The path from the node. + private string ParseSymbolPathElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string path = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Path": + path = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == path) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Path")); + } + + this.Core.ParseForExtensionElements(node); + + return path; + } + + /// + /// Parses the All element under a PatchFamily. + /// + /// The element to parse. + private void ParseAllElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + // find unexpected attributes + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + this.Core.UnexpectedAttribute(node, attrib); + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + // Always warn when using the All element. + this.Core.Write(WarningMessages.AllChangesIncludedInPatch(sourceLineNumbers)); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers) + { + Table = "*", + PrimaryKeys = "*", + }); + } + } + + /// + /// Parses all reference elements under a PatchFamily. + /// + /// The element to parse. + /// Table that reference was made to. + private void ParsePatchChildRefElement(XElement node, string tableName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers) + { + Table = tableName, + PrimaryKeys = id + }); + } + } + + /// + /// Parses a PatchBaseline element. + /// + /// The element to parse. + /// Media index from parent element. + private void ParsePatchBaselineElement(XElement node, int? diskId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var parsedValidate = false; + var validationFlags = TransformFlags.PatchTransformDefault; + string baselineFile = null; + string updateFile = null; + string transformFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "BaselineFile": + baselineFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpdateFile": + updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "TransformFile": + transformFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (27 < id.Id.Length) + { + this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, 27)); + } + + if (!String.IsNullOrEmpty(baselineFile) || !String.IsNullOrEmpty(updateFile)) + { + if (String.IsNullOrEmpty(baselineFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "UpdateFile")); + } + + if (String.IsNullOrEmpty(updateFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile", "BaselineFile")); + } + + if (!String.IsNullOrEmpty(transformFile)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TransformFile", !String.IsNullOrEmpty(baselineFile) ? "BaselineFile" : "UpdateFile")); + } + } + else if (String.IsNullOrEmpty(transformFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "TransformFile", true)); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Validate": + if (parsedValidate) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); + } + else + { + this.ParseValidateElement(child, ref validationFlags); + parsedValidate = true; + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPatchBaselineSymbol(sourceLineNumbers, id) + { + DiskId = diskId ?? 1, + ValidationFlags = validationFlags, + BaselineFile = new IntermediateFieldPathValue { Path = baselineFile }, + UpdateFile = new IntermediateFieldPathValue { Path = updateFile }, + TransformFile = new IntermediateFieldPathValue { Path = transformFile }, + }); + } + } + + /// + /// Parses a Validate element. + /// + /// The element to parse. + /// TransformValidation flags to use when creating the authoring patch transform. + private void ParseValidateElement(XElement node, ref TransformFlags validationFlags) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ProductId": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ValidateProduct; + } + else + { + validationFlags &= ~TransformFlags.ValidateProduct; + } + break; + case "ProductLanguage": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ValidateLanguage; + } + else + { + validationFlags &= ~TransformFlags.ValidateLanguage; + } + break; + case "ProductVersion": + var check = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + validationFlags &= ~TransformFlags.ProductVersionMask; + switch (check) + { + case "Major": + case "major": + validationFlags |= TransformFlags.ValidateMajorVersion; + break; + case "Minor": + case "minor": + validationFlags |= TransformFlags.ValidateMinorVersion; + break; + case "Update": + case "update": + validationFlags |= TransformFlags.ValidateUpdateVersion; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Version", check, "Major", "Minor", "Update")); + break; + } + break; + case "ProductVersionOperator": + var op = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + validationFlags &= ~TransformFlags.ProductVersionOperatorMask; + switch (op) + { + case "Lesser": + case "lesser": + validationFlags |= TransformFlags.ValidateNewLessBaseVersion; + break; + case "LesserOrEqual": + case "lesserOrEqual": + validationFlags |= TransformFlags.ValidateNewLessEqualBaseVersion; + break; + case "Equal": + case "equal": + validationFlags |= TransformFlags.ValidateNewEqualBaseVersion; + break; + case "GreaterOrEqual": + case "greaterOrEqual": + validationFlags |= TransformFlags.ValidateNewGreaterEqualBaseVersion; + break; + case "Greater": + case "greater": + validationFlags |= TransformFlags.ValidateNewGreaterBaseVersion; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Operator", op, "Lesser", "LesserOrEqual", "Equal", "GreaterOrEqual", "Greater")); + break; + } + break; + case "UpgradeCode": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ValidateUpgradeCode; + } + else + { + validationFlags &= ~TransformFlags.ValidateUpgradeCode; + } + break; + case "IgnoreAddExistingRow": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorAddExistingRow; + } + else + { + validationFlags &= ~TransformFlags.ErrorAddExistingRow; + } + break; + case "IgnoreAddExistingTable": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorAddExistingTable; + } + else + { + validationFlags &= ~TransformFlags.ErrorAddExistingTable; + } + break; + case "IgnoreDeleteMissingRow": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorDeleteMissingRow; + } + else + { + validationFlags &= ~TransformFlags.ErrorDeleteMissingRow; + } + break; + case "IgnoreDeleteMissingTable": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorDeleteMissingTable; + } + else + { + validationFlags &= ~TransformFlags.ErrorDeleteMissingTable; + } + break; + case "IgnoreUpdateMissingRow": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorUpdateMissingRow; + } + else + { + validationFlags &= ~TransformFlags.ErrorUpdateMissingRow; + } + break; + case "IgnoreChangingCodePage": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + validationFlags |= TransformFlags.ErrorChangeCodePage; + } + else + { + validationFlags &= ~TransformFlags.ErrorChangeCodePage; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + } + + private string HandleSubdirectory(SourceLineNumber sourceLineNumbers, XElement element, string directoryId, string subdirectory, string directoryAttributeName, string subdirectoryAttributename) + { + if (!String.IsNullOrEmpty(subdirectory)) + { + if (String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, element.Name.LocalName, subdirectoryAttributename, directoryAttributeName)); + } + else + { + directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); + } + } + + return directoryId; + } + } +} diff --git a/src/wix/WixToolset.Core/CompilerCore.cs b/src/wix/WixToolset.Core/CompilerCore.cs new file mode 100644 index 00000000..727084eb --- /dev/null +++ b/src/wix/WixToolset.Core/CompilerCore.cs @@ -0,0 +1,1166 @@ +// 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 +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Reflection; + using System.Text; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal enum ValueListKind + { + /// + /// A list of values with nothing before the final value. + /// + None, + + /// + /// A list of values with 'and' before the final value. + /// + And, + + /// + /// A list of values with 'or' before the final value. + /// + Or + } + + /// + /// Core class for the compiler. + /// + internal class CompilerCore + { + internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; + internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; + + // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113) + private static readonly List BuiltinBundleVariables = new List( + new string[] { + "AdminToolsFolder", + "AppDataFolder", + "CommonAppDataFolder", + "CommonFiles64Folder", + "CommonFilesFolder", + "CompatibilityMode", + "Date", + "DesktopFolder", + "FavoritesFolder", + "FontsFolder", + "InstallerName", + "InstallerVersion", + "LocalAppDataFolder", + "LogonUser", + "MyPicturesFolder", + "NTProductType", + "NTSuiteBackOffice", + "NTSuiteDataCenter", + "NTSuiteEnterprise", + "NTSuitePersonal", + "NTSuiteSmallBusiness", + "NTSuiteSmallBusinessRestricted", + "NTSuiteWebServer", + "PersonalFolder", + "Privileged", + "ProgramFiles64Folder", + "ProgramFiles6432Folder", + "ProgramFilesFolder", + "ProgramMenuFolder", + "RebootPending", + "SendToFolder", + "ServicePackLevel", + "StartMenuFolder", + "StartupFolder", + "System64Folder", + "SystemFolder", + "TempFolder", + "TemplateFolder", + "TerminalServer", + "UserLanguageID", + "UserUILanguageID", + "VersionMsi", + "VersionNT", + "VersionNT64", + "WindowsFolder", + "WindowsVolume", + "WixBundleAction", + "WixBundleForcedRestartPackage", + "WixBundleElevated", + "WixBundleInstalled", + "WixBundleProviderKey", + "WixBundleTag", + "WixBundleVersion", + }); + + private static readonly List DisallowedMsiProperties = new List( + new string[] { + "ACTION", + "ADDLOCAL", + "ADDSOURCE", + "ADDDEFAULT", + "ADVERTISE", + "ALLUSERS", + "REBOOT", + "REINSTALL", + "REINSTALLMODE", + "REMOVE" + }); + + private readonly Dictionary extensions; + private readonly IParseHelper parseHelper; + private readonly Intermediate intermediate; + private readonly IMessaging messaging; + private Dictionary activeSectionCachedInlinedDirectoryIds; + private HashSet activeSectionSimpleReferences; + + /// + /// Constructor for all compiler core. + /// + /// The Intermediate object representing compiled source document. + /// + /// + /// The WiX extensions collection. + internal CompilerCore(Intermediate intermediate, IMessaging messaging, IParseHelper parseHelper, Dictionary extensions) + { + this.extensions = extensions; + this.parseHelper = parseHelper; + this.intermediate = intermediate; + this.messaging = messaging; + } + + /// + /// Gets the section the compiler is currently emitting symbols into. + /// + /// The section the compiler is currently emitting symbols into. + public IntermediateSection ActiveSection { get; private set; } + + /// + /// Gets whether the compiler core encountered an error while processing. + /// + /// Flag if core encountered an error during processing. + public bool EncounteredError => this.messaging.EncounteredError; + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + public bool ShowPedanticMessages { get; set; } + + /// + /// Add a symbol to the active section. + /// + /// Symbol to add. + public T AddSymbol(T symbol) + where T : IntermediateSymbol + { + return this.ActiveSection.AddSymbol(symbol); + } + + /// + /// Convert a bit array into an int value. + /// + /// The bit array to convert. + /// The converted int value. + public int CreateIntegerFromBitArray(BitArray bits) + { + if (32 != bits.Length) + { + throw new ArgumentException(String.Format("Can only convert a bit array with 32-bits to integer. Actual number of bits in array: {0}", bits.Length), "bits"); + } + + int[] intArray = new int[1]; + bits.CopyTo(intArray, 0); + + return intArray[0]; + } + + /// + /// Sets a bit in a bit array based on the index at which an attribute name was found in a string array. + /// + /// Array of attributes that map to bits. + /// Name of attribute to check. + /// Value of attribute to check. + /// The bit array in which the bit will be set if found. + /// The offset into the bit array. + /// true if the bit was set; false otherwise. + public bool TrySetBitFromName(string[] attributeNames, string attributeName, YesNoType attributeValue, BitArray bits, int offset) + { + for (int i = 0; i < attributeNames.Length; i++) + { + if (attributeName.Equals(attributeNames[i], StringComparison.Ordinal)) + { + bits.Set(i + offset, YesNoType.Yes == attributeValue); + return true; + } + } + + return false; + } + + internal void InnerTextDisallowed(XElement element) + { + this.parseHelper.InnerTextDisallowed(element); + } + + /// + /// Verifies that a filename is ambiguous. + /// + /// Filename to verify. + /// true if the filename is ambiguous; false otherwise. + public static bool IsAmbiguousFilename(string filename) + { + if (String.IsNullOrEmpty(filename)) + { + return false; + } + + var tilde = filename.IndexOf('~'); + return (tilde > 0 && tilde < filename.Length) && Char.IsNumber(filename[tilde + 1]); + } + + /// + /// Verifies that a value is a legal identifier. + /// + /// The value to verify. + /// true if the value is an identifier; false otherwise. + public bool IsValidIdentifier(string value) + { + return this.parseHelper.IsValidIdentifier(value); + } + + /// + /// Verifies if an identifier is a valid loc identifier. + /// + /// Identifier to verify. + /// True if the identifier is a valid loc identifier. + public bool IsValidLocIdentifier(string identifier) + { + return this.parseHelper.IsValidLocIdentifier(identifier); + } + + /// + /// Verifies if a filename is a valid long filename. + /// + /// Filename to verify. + /// true if wildcards are allowed in the filename. + /// true if relative paths are allowed in the filename. + /// True if the filename is a valid long filename + public bool IsValidLongFilename(string filename, bool allowWildcards = false, bool allowRelative = false) + { + return this.parseHelper.IsValidLongFilename(filename, allowWildcards, allowRelative); + } + + /// + /// Verifies if a filename is a valid short filename. + /// + /// Filename to verify. + /// true if wildcards are allowed in the filename. + /// True if the filename is a valid short filename + public bool IsValidShortFilename(string filename, bool allowWildcards) + { + return this.parseHelper.IsValidShortFilename(filename, allowWildcards); + } + + /// + /// Replaces the illegal filename characters to create a legal name. + /// + /// Filename to make valid. + /// Replacement string for invalid characters in filename. + /// Valid filename. + public static string MakeValidLongFileName(string filename, char replace) + { + if (String.IsNullOrEmpty(filename)) + { + return filename; + } + + StringBuilder sb = null; + + var found = filename.IndexOfAny(Common.IllegalLongFilenameCharacters); + while (found != -1) + { + if (sb == null) + { + sb = new StringBuilder(filename); + } + + sb[found] = replace; + + found = (found + 1 < filename.Length) ? filename.IndexOfAny(Common.IllegalLongFilenameCharacters, found + 1) : -1; + } + + return sb?.ToString() ?? filename; + } + + /// + /// Verifies the given string is a valid product version. + /// + /// The product version to verify. + /// True if version is a valid product version + public static bool IsValidProductVersion(string version) + { + if (!Common.IsValidBinderVariable(version)) + { + Version ver = new Version(version); + + if (255 < ver.Major || 255 < ver.Minor || 65535 < ver.Build) + { + return false; + } + } + + return true; + } + + /// + /// Verifies the given string is a valid module or bundle version. + /// + /// The version to verify. + /// True if version is a valid module or bundle version. + public static bool IsValidModuleOrBundleVersion(string version) + { + return Common.IsValidFourPartVersion(version); + } + + /// + /// Creates group and ordering information. + /// + /// Source line numbers. + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of this item. + /// Identifier for this item. + /// Type of previous item, if known. + /// Identifier of previous item, if known + public void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers, + ComplexReferenceParentType parentType, string parentId, + ComplexReferenceChildType type, string id, + ComplexReferenceChildType previousType, string previousId) + { + if (this.EncounteredError) + { + return; + } + + if (parentType != ComplexReferenceParentType.Unknown && parentId != null) + { + this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id); + } + + if (previousType != ComplexReferenceChildType.Unknown && previousId != null) + { + // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"? + // TODO: Also, we could potentially include an 'Attributes' field to track things like + // 'before' vs. 'after', and explicit vs. inferred dependencies. + this.AddSymbol(new WixOrderingSymbol(sourceLineNumbers) + { + ItemType = type, + ItemIdRef = id, + DependsOnType = previousType, + DependsOnIdRef = previousId, + }); + } + } + + /// + /// Creates a version 3 name-based UUID. + /// + /// The namespace UUID. + /// The value. + /// The generated GUID for the given namespace and value. + public string CreateGuid(Guid namespaceGuid, string value) + { + return this.parseHelper.CreateGuid(namespaceGuid, value); + } + + /// + /// Creates directories using the inline directory syntax. + /// + /// Source line information. + /// Optional identifier of parent directory. + /// Optional inline syntax to override attribute's value. + /// Identifier of the leaf directory created. + public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, string parentId, string inlineSyntax = null) + { + return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute: null, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds); + } + + /// + /// Creates a Registry row in the active section. + /// + /// Source and line number of the current row. + /// The registry entry root. + /// The registry entry key. + /// The registry entry name. + /// The registry entry value. + /// The component which will control installation/uninstallation of the registry entry. + public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId) + { + return this.parseHelper.CreateRegistrySymbol(this.ActiveSection, sourceLineNumbers, root, key, name, value, componentId, true); + } + + /// + /// Create a WixSimpleReferenceSymbol in the active section. + /// + /// Source line information for the row. + /// The symbol name of the simple reference. + /// The primary key of the simple reference. + public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) + { + if (!this.EncounteredError) + { + var id = String.Concat(symbolName, ":", primaryKey); + + // If this simple reference hasn't been added to the active section already, add it. + if (this.activeSectionSimpleReferences.Add(id)) + { + this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKey); + } + } + } + + /// + /// Create a WixSimpleReferenceSymbol in the active section. + /// + /// Source line information for the row. + /// The symbol name of the simple reference. + /// The primary keys of the simple reference. + public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) + { + if (!this.EncounteredError) + { + var joinedKeys = String.Join("/", primaryKeys); + var id = String.Concat(symbolName, ":", joinedKeys); + + // If this simple reference hasn't been added to the active section already, add it. + if (this.activeSectionSimpleReferences.Add(id)) + { + this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKeys); + } + } + } + + /// + /// Create a WixSimpleReferenceSymbol in the active section. + /// + /// Source line information for the row. + /// The symbol definition of the simple reference. + /// The primary key of the simple reference. + public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) + { + this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKey); + } + + /// + /// Create a WixSimpleReferenceSymbol in the active section. + /// + /// Source line information for the row. + /// The symbol definition of the simple reference. + /// The primary keys of the simple reference. + public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) + { + this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKeys); + } + + /// + /// A row in the WixGroup table is added for this child node and its parent node. + /// + /// Source line information for the row. + /// Type of child's complex reference parent. + /// Id of the parenet node. + /// Complex reference type of child + /// Id of the Child Node. + public void CreateWixGroupRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) + { + if (!this.EncounteredError) + { + this.parseHelper.CreateWixGroupSymbol(this.ActiveSection, sourceLineNumbers, parentType, parentId, childType, childId); + } + } + + /// + /// Add the appropriate symbols to make sure that the given table shows up + /// in the resulting output. + /// + /// Source line numbers. + /// Name of the table to ensure existance of. + public void EnsureTable(SourceLineNumber sourceLineNumbers, string tableName) + { + if (!this.EncounteredError) + { + this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableName); + } + } + + /// + /// Add the appropriate symbols to make sure that the given table shows up + /// in the resulting output. + /// + /// Source line numbers. + /// Definition of the table to ensure existance of. + public void EnsureTable(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) + { + if (!this.EncounteredError) + { + this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableDefinition); + } + } + + /// + /// Get an attribute value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// A rule for the contents of the value. If the contents do not follow the rule, an error is thrown. + /// The attribute's value. + public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) + { + return this.parseHelper.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); + } + + /// + /// Get a valid code page by web name or number from a string attribute. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// A valid code page integer value. + public int GetAttributeCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + if (null == attribute) + { + throw new ArgumentNullException(nameof(attribute)); + } + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + try + { + return Common.GetValidCodePage(value); + } + catch (NotSupportedException) + { + this.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); + } + + return CompilerConstants.IllegalInteger; + } + + /// + /// Get a valid code page by web name or number from a string attribute. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// Whether to allow Unicode (UCS) or UTF code pages. + /// A valid code page integer value or variable expression. + public string GetAttributeLocalizableCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool onlyAnsi = false) + { + if (null == attribute) + { + throw new ArgumentNullException(nameof(attribute)); + } + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + // Allow for localization of code page names and values. + if (this.IsValidLocIdentifier(value)) + { + return value; + } + + try + { + var codePage = Common.GetValidCodePage(value, false, onlyAnsi, sourceLineNumbers); + return codePage.ToString(CultureInfo.InvariantCulture); + } + catch (NotSupportedException) + { + // Not a valid windows code page. + this.messaging.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName)); + } + catch (WixException e) + { + this.messaging.Write(e.Error); + } + + return null; + } + + /// + /// Get an integer attribute value and displays an error for an illegal integer value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The minimum legal value. + /// The maximum legal value. + /// The attribute's integer value or a special value if an error occurred during conversion. + public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) + { + return this.parseHelper.GetAttributeIntegerValue(sourceLineNumbers, attribute, minimum, maximum); + } + + /// + /// Get a long integral attribute value and displays an error for an illegal long value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The minimum legal value. + /// The maximum legal value. + /// The attribute's long value or a special value if an error occurred during conversion. + public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) + { + return this.parseHelper.GetAttributeLongValue(sourceLineNumbers, attribute, minimum, maximum); + } + + /// + /// Get a date time attribute value and display errors for illegal values. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// Int representation of the date time. + public int GetAttributeDateTimeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + if (null == attribute) + { + throw new ArgumentNullException("attribute"); + } + + string value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (0 < value.Length) + { + try + { + DateTime date = DateTime.Parse(value, CultureInfo.InvariantCulture.DateTimeFormat); + + return ((((date.Year - 1980) * 512) + (date.Month * 32 + date.Day)) * 65536) + + (date.Hour * 2048) + (date.Minute * 32) + (date.Second / 2); + } + catch (ArgumentOutOfRangeException) + { + this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + catch (FormatException) + { + this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + catch (OverflowException) + { + this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return CompilerConstants.IllegalInteger; + } + + /// + /// Get an integer attribute value or localize variable and displays an error for + /// an illegal value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The minimum legal value. + /// The maximum legal value. + /// The attribute's integer value or localize variable as a string or a special value if an error occurred during conversion. + public string GetAttributeLocalizableIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) + { + if (null == attribute) + { + throw new ArgumentNullException("attribute"); + } + + Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing."); + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (0 < value.Length) + { + if (this.IsValidLocIdentifier(value) || Common.IsValidBinderVariable(value)) + { + return value; + } + else + { + try + { + var integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat); + + if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer) + { + this.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer)); + } + else if (minimum > integer || maximum < integer) + { + this.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum)); + integer = CompilerConstants.IllegalInteger; + } + + return value; + } + catch (FormatException) + { + this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + catch (OverflowException) + { + this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + } + + return null; + } + + /// + /// Get a guid attribute value and displays an error for an illegal guid value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// Determines whether the guid can be automatically generated. + /// If true, no error is raised on empty value. If false, an error is raised. + /// The attribute's guid value or a special value if an error occurred. + public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) + { + return this.parseHelper.GetAttributeGuidValue(sourceLineNumbers, attribute, generatable, canBeEmpty); + } + + /// + /// Get an identifier attribute value and displays an error for an illegal identifier value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's identifier value or a special value if an error occurred. + public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return this.parseHelper.GetAttributeIdentifier(sourceLineNumbers, attribute); + } + + /// + /// Get an identifier attribute value and displays an error for an illegal identifier value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's identifier value or a special value if an error occurred. + public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return this.parseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attribute); + } + + /// + /// Gets a yes/no value and displays an error for an illegal yes/no value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's YesNoType value. + public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return this.parseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute); + } + + /// + /// Gets a yes/no/default value and displays an error for an illegal yes/no value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's YesNoDefaultType value. + public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return this.parseHelper.GetAttributeYesNoDefaultValue(sourceLineNumbers, attribute); + } + + /// + /// Gets a short filename value and displays an error for an illegal short filename value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// true if wildcards are allowed in the filename. + /// The attribute's short filename value. + public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false) + { + if (null == attribute) + { + throw new ArgumentNullException("attribute"); + } + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (0 < value.Length) + { + if (!this.parseHelper.IsValidShortFilename(value, allowWildcards) && !Common.ContainsValidBinderVariable(value)) + { + this.Write(ErrorMessages.IllegalShortFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + else if (CompilerCore.IsAmbiguousFilename(value)) + { + this.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return value; + } + + /// + /// Gets a long filename value and displays an error for an illegal long filename value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// true if wildcards are allowed in the filename. + /// true if relative paths are allowed in the filename. + /// The attribute's long filename value. + public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false, bool allowRelative = false) + { + return this.parseHelper.GetAttributeLongFilename(sourceLineNumbers, attribute, allowWildcards, allowRelative); + } + + /// + /// Gets a version value or possibly a binder variable and displays an error for an illegal version value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's version value. + public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return this.parseHelper.GetAttributeVersionValue(sourceLineNumbers, attribute); + } + + /// + /// Gets a RegistryRoot as a MsiInterop.MsidbRegistryRoot value and displays an error for an illegal value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// Whether HKMU is returned as -1 (true), or treated as an error (false). + /// The attribute's RegisitryRootType value. + public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) + { + return this.parseHelper.GetAttributeRegistryRootValue(sourceLineNumbers, attribute, allowHkmu); + } + + /// + /// Gets a Bundle variable value and displays an error for an illegal value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's value. + public string GetAttributeBundleVariableValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + string value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (!String.IsNullOrEmpty(value)) + { + if (CompilerCore.BuiltinBundleVariables.Contains(value)) + { + string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.BuiltinBundleVariables); + this.Write(ErrorMessages.IllegalAttributeValueWithIllegalList(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, illegalValues)); + } + } + + return value; + } + + /// + /// Gets an MsiProperty name value and displays an error for an illegal value. + /// + /// Source line information about the owner element. + /// The attribute containing the value to get. + /// The attribute's value. + public string GetAttributeMsiPropertyNameValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + string value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (0 < value.Length) + { + if (CompilerCore.DisallowedMsiProperties.Contains(value)) + { + string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.DisallowedMsiProperties); + this.Write(ErrorMessages.DisallowedMsiProperty(sourceLineNumbers, value, illegalValues)); + } + } + + return value; + } + + /// + /// Checks if the string contains a property (i.e. "foo[Property]bar") + /// + /// String to evaluate for properties. + /// True if a property is found in the string. + public bool ContainsProperty(string possibleProperty) + { + return this.parseHelper.ContainsProperty(possibleProperty); + } + + /// + /// Generate an identifier by hashing data from the row. + /// + /// Three letter or less prefix for generated row identifier. + /// Information to hash. + /// The generated identifier. + public Identifier CreateIdentifier(string prefix, params string[] args) + { + return this.parseHelper.CreateIdentifier(prefix, args); + } + + /// + /// Create an identifier based on passed file name + /// + /// File name to generate identifer from + /// + public Identifier CreateIdentifierFromFilename(string filename) + { + return this.parseHelper.CreateIdentifierFromFilename(filename); + } + + /// + /// Attempts to use an extension to parse the attribute. + /// + /// Element containing attribute to be parsed. + /// Attribute to be parsed. + /// Extra information about the context in which this element is being parsed. + public void ParseExtensionAttribute(XElement element, XAttribute attribute, IDictionary context = null) + { + this.parseHelper.ParseExtensionAttribute(this.extensions.Values, this.intermediate, this.ActiveSection, element, attribute, context); + } + + /// + /// Attempts to use an extension to parse the element. + /// + /// Element containing element to be parsed. + /// Element to be parsed. + /// Extra information about the context in which this element is being parsed. + public void ParseExtensionElement(XElement parentElement, XElement element, IDictionary context = null) + { + this.parseHelper.ParseExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); + } + + /// + /// Process all children of the element looking for extensions and erroring on the unexpected. + /// + /// Element to parse children. + public void ParseForExtensionElements(XElement element) + { + this.parseHelper.ParseForExtensionElements(this.extensions.Values, this.intermediate, this.ActiveSection, element); + } + + /// + /// Attempts to use an extension to parse the element, with support for setting component keypath. + /// + /// Element containing element to be parsed. + /// Element to be parsed. + /// Extra information about the context in which this element is being parsed. + public IComponentKeyPath ParsePossibleKeyPathExtensionElement(XElement parentElement, XElement element, IDictionary context) + { + return this.parseHelper.ParsePossibleKeyPathExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context); + } + + /// + /// Displays an unexpected attribute error if the attribute is not the namespace attribute. + /// + /// Element containing unexpected attribute. + /// The unexpected attribute. + public void UnexpectedAttribute(XElement element, XAttribute attribute) + { + this.parseHelper.UnexpectedAttribute(element, attribute); + } + + /// + /// Display an unexepected element error. + /// + /// The parent element. + /// The unexpected child element. + public void UnexpectedElement(XElement parentElement, XElement childElement) + { + this.parseHelper.UnexpectedElement(parentElement, childElement); + } + + /// + /// Sends a message. + /// + /// Message to write. + public void Write(Message message) + { + this.messaging.Write(message); + } + + /// + /// Verifies that the calling assembly version is equal to or newer than the given . + /// + /// Source line information about the owner element. + /// The version required of the calling assembly. + internal void VerifyRequiredVersion(SourceLineNumber sourceLineNumbers, string requiredVersion) + { + // an null or empty string means any version will work + if (!String.IsNullOrEmpty(requiredVersion)) + { + Assembly caller = Assembly.GetCallingAssembly(); + AssemblyName name = caller.GetName(); + FileVersionInfo fv = FileVersionInfo.GetVersionInfo(caller.Location); + + Version versionRequired = new Version(requiredVersion); + Version versionCurrent = new Version(fv.FileVersion); + + if (versionRequired > versionCurrent) + { + if (this.GetType().Assembly.Equals(caller)) + { + this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired)); + } + else + { + this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired, name.Name)); + } + } + } + } + + /// + /// Creates a new section and makes it the active section in the core. + /// + /// Unique identifier for the section. + /// Type of section to create. + /// Unique identifier for the compilation. + /// New section. + internal IntermediateSection CreateActiveSection(string id, SectionType type, string compilationId) + { + this.ActiveSection = this.CreateSection(id, type, compilationId); + + this.activeSectionCachedInlinedDirectoryIds = new Dictionary(); + this.activeSectionSimpleReferences = new HashSet(); + + return this.ActiveSection; + } + + /// + /// Creates a new section. + /// + /// Unique identifier for the section. + /// Type of section to create. + /// Unique identifier for the compilation. + /// New section. + internal IntermediateSection CreateSection(string id, SectionType type, string compilationId) + { + var section = new IntermediateSection(id, type, compilationId); + + this.intermediate.AddSection(section); + + return section; + } + + /// + /// Creates WixComplexReference and WixGroup rows in the active section. + /// + /// Source line information. + /// The parent type. + /// The parent id. + /// The parent language. + /// The child type. + /// The child id. + /// Whether the child is primary. + public void CreateComplexReference(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) + { + this.parseHelper.CreateComplexReference(this.ActiveSection, sourceLineNumbers, parentType, parentId, parentLanguage, childType, childId, isPrimary); + } + + /// + /// Creates a directory row from a name. + /// + /// Source line information. + /// Optional identifier for the new row. + /// Optional identifier for the parent row. + /// Long name of the directory. + /// Optional short name of the directory. + /// Optional source name for the directory. + /// Optional short source name for the directory. + /// Identifier for the newly created row. + internal Identifier CreateDirectorySymbol(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) + { + return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, shortName, sourceName, shortSourceName); + } + + public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) + { + this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); + } + + internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false) + { + return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable); + } + + private static string CreateValueList(ValueListKind kind, IEnumerable values) + { + // Ideally, we could denote the list kind (and the list itself) directly in the + // message XML, and detect and expand in the MessageHandler.GenerateMessageString() + // method. Doing so would make vararg-style messages much easier, but impacts + // every single message we format. For now, callers just have to know when a + // message takes a list of values in a single string argument, the caller will + // have to do the expansion themselves. (And, unfortunately, hard-code the knowledge + // that the list is an 'and' or 'or' list.) + + // For a localizable solution, we need to be able to get the list format string + // from resources. We aren't currently localized right now, so the values are + // just hard-coded. + const string valueFormat = "'{0}'"; + const string valueSeparator = ", "; + string terminalTerm = String.Empty; + + switch (kind) + { + case ValueListKind.None: + terminalTerm = ""; + break; + case ValueListKind.And: + terminalTerm = "and "; + break; + case ValueListKind.Or: + terminalTerm = "or "; + break; + } + + StringBuilder list = new StringBuilder(); + + // This weird construction helps us determine when we're adding the last value + // to the list. Instead of adding them as we encounter them, we cache the current + // value and append the *previous* one. + string previousValue = null; + bool haveValues = false; + foreach (string value in values) + { + if (null != previousValue) + { + if (haveValues) + { + list.Append(valueSeparator); + } + list.AppendFormat(valueFormat, previousValue); + haveValues = true; + } + + previousValue = value; + } + + // If we have no previous value, that means that the list contained no values, and + // something has gone very wrong. + Debug.Assert(null != previousValue); + if (null != previousValue) + { + if (haveValues) + { + list.Append(valueSeparator); + list.Append(terminalTerm); + } + list.AppendFormat(valueFormat, previousValue); + haveValues = true; + } + + return list.ToString(); + } + } +} diff --git a/src/wix/WixToolset.Core/CompilerErrors.cs b/src/wix/WixToolset.Core/CompilerErrors.cs new file mode 100644 index 00000000..10646dfd --- /dev/null +++ b/src/wix/WixToolset.Core/CompilerErrors.cs @@ -0,0 +1,43 @@ +// 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 +{ + using WixToolset.Data; + + internal static class CompilerErrors + { + public static Message IllegalCharactersInProvider(SourceLineNumber sourceLineNumbers, string attributeName, char illegalChar, string illegalChars) + { + return Message(sourceLineNumbers, Ids.IllegalCharactersInProvider, "The provider key authored into the {0} attribute contains an illegal character, '{1}'. Please author the provider key without any of the following characters: {2}", attributeName, illegalChar, illegalChars); + } + + public static Message ReservedValue(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string attributeValue) + { + return Message(sourceLineNumbers, Ids.ReservedValue, "The {0}/@{1} attribute value '{2}' is reserved and cannot be used here. Please choose a different value.", elementName, attributeName, attributeValue); + } + + public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name) + { + return Message(sourceLineNumbers, Ids.IllegalName, "The Tag/@Name attribute value, '{1}', contains invalid filename identifiers. The Tag/@Name may have defaulted from the {0}/@Name attrbute. If so, use the Tag/@Name attribute to provide a valid filename. Any character except for the follow may be used: \\ ? | > < : / * \".", parentElement, name); + } + + public static Message ExampleRegid(SourceLineNumber sourceLineNumbers, string regid) + { + return Message(sourceLineNumbers, Ids.ExampleRegid, "Regid '{0}' is a placeholder that must be replaced with an appropriate value for your installation. Use the simplified URI for your organization or project.", regid); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + IllegalCharactersInProvider = 5400, + ReservedValue = 5401, + + IllegalName = 6601, + ExampleRegid = 6602, + } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. + } +} diff --git a/src/wix/WixToolset.Core/CompilerWarnings.cs b/src/wix/WixToolset.Core/CompilerWarnings.cs new file mode 100644 index 00000000..5c11b878 --- /dev/null +++ b/src/wix/WixToolset.Core/CompilerWarnings.cs @@ -0,0 +1,65 @@ +// 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 +{ + using WixToolset.Data; + + internal static class CompilerWarnings + { + public static Message DirectoryRefStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) + { + return Message(sourceLineNumbers, Ids.DirectoryRefStandardDirectoryDeprecated, "Using DirectoryRef to reference the standard directory '{0}' is deprecated. Use the StandardDirectory element instead.", directoryId); + } + + public static Message DefiningStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId) + { + return Message(sourceLineNumbers, Ids.DefiningStandardDirectoryDeprecated, "It is no longer necessary to define the standard directory '{0}'. Use the StandardDirectory element instead.", directoryId); + } + + public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified in an MSI package. The ProductVersion will be used by default."); + } + + public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers, string id) + { + return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified for MSI package {0}. The ProductVersion will be used by default.", id); + } + + public static Message PropertyRemoved(string name) + { + return Message(null, Ids.PropertyRemoved, "The property {0} was authored in the package with a value and will be removed. The property should not be authored.", name); + } + + public static Message ProvidesKeyNotFound(SourceLineNumber sourceLineNumbers, string id) + { + return Message(sourceLineNumbers, Ids.ProvidesKeyNotFound, "The provider key with identifier {0} was not found in the WixDependencyProvider table. Related registry rows will not be removed from authoring.", id); + } + + public static Message RequiresKeyNotFound(SourceLineNumber sourceLineNumbers, string id) + { + return Message(sourceLineNumbers, Ids.RequiresKeyNotFound, "The dependency key with identifier {0} was not found in the WixDependency table. Related registry rows will not be removed from authoring.", id); + } + + public static Message Win64Component(SourceLineNumber sourceLineNumbers, string componentId) + { + return Message(sourceLineNumbers, Ids.Win64Component, "The Provides element should not be authored in the 64-bit component with identifier {0}. The dependency feature may not work if installing this package on 64-bit Windows operating systems prior to Windows 7 and Windows Server 2008 R2. Set the Component/@Bitness attribute to \"always32\" to ensure the dependency feature works correctly on legacy operating systems.", componentId); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); + } + + public enum Ids + { + ProvidesKeyNotFound = 5431, + RequiresKeyNotFound = 5432, + PropertyRemoved = 5433, + DiscouragedVersionAttribute = 5434, + Win64Component = 5435, + DirectoryRefStandardDirectoryDeprecated = 5436, + DefiningStandardDirectoryDeprecated = 5437, + } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors. + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Bundle.cs b/src/wix/WixToolset.Core/Compiler_Bundle.cs new file mode 100644 index 00000000..6d2e75f7 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Bundle.cs @@ -0,0 +1,3266 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + private static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnUXContainerName); + private static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnDefaultAttachedContainerName); + private static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Section, BurnConstants.BundleLayoutOnlyPayloadsName); + + /// + /// Parses an ApprovedExeForElevation element. + /// + /// Element to parse + private void ParseApprovedExeForElevation(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string valueName = null; + var win64 = this.Context.IsCurrentPlatform64Bit; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + valueName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + var attributes = WixApprovedExeForElevationAttributes.None; + + if (win64) + { + attributes |= WixApprovedExeForElevationAttributes.Win64; + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixApprovedExeForElevationSymbol(sourceLineNumbers, id) + { + Key = key, + ValueName = valueName, + Attributes = attributes, + }); + } + } + + /// + /// Parses a Bundle element. + /// + /// Element to parse + private void ParseBundleElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string copyright = null; + string aboutUrl = null; + var compressed = YesNoDefaultType.Default; + WixBundleAttributes attributes = 0; + string helpTelephone = null; + string helpUrl = null; + string manufacturer = null; + string name = null; + string tag = null; + string updateUrl = null; + string upgradeCode = null; + string version = null; + string condition = null; + string parentName = null; + + string fileSystemSafeBundleName = null; + string logVariablePrefixAndExtension = null; + string iconSourceFile = null; + string splashScreenSourceFile = null; + + // Process only standard attributes until the active section is initialized. + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AboutUrl": + aboutUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Compressed": + compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Copyright": + copyright = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisableModify": + var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (value) + { + case "button": + attributes |= WixBundleAttributes.SingleChangeUninstallButton; + break; + case "yes": + attributes |= WixBundleAttributes.DisableModify; + break; + case "no": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no")); + break; + } + break; + case "DisableRemove": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixBundleAttributes.DisableRemove; + } + break; + case "HelpTelephone": + helpTelephone = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HelpUrl": + helpUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Manufacturer": + manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "IconSourceFile": + iconSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ParentName": + parentName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ProviderKey": + // This can't be processed until we create the section. + break; + case "SplashScreenSourceFile": + splashScreenSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Tag": + tag = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpdateUrl": + updateUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UpgradeCode": + upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Version": + version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + if (String.IsNullOrEmpty(version)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) + { + this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Bundle", version)); + } + + if (String.IsNullOrEmpty(upgradeCode)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpgradeCode")); + } + + if (String.IsNullOrEmpty(copyright)) + { + if (String.IsNullOrEmpty(manufacturer)) + { + copyright = "Copyright (c). All rights reserved."; + } + else + { + copyright = String.Format("Copyright (c) {0}. All rights reserved.", manufacturer); + } + } + + if (String.IsNullOrEmpty(name)) + { + logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup:log"); + } + else + { + // Ensure only allowable path characters are in "name" (and change spaces to underscores). + fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), '_'); + logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":log"); + } + + this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name; + this.Core.CreateActiveSection(this.activeName, SectionType.Bundle, this.Context.CompilationId); + + // Now that the active section is initialized, process only extension attributes and the special ProviderKey attribute. + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ProviderKey": + this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib); + break; + // Unknown attributes were reported earlier. + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + var baSeen = false; + var chainSeen = false; + var logSeen = false; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ApprovedExeForElevation": + this.ParseApprovedExeForElevation(child); + break; + case "BootstrapperApplication": + if (baSeen) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "BootstrapperApplication")); + } + this.ParseBootstrapperApplicationElement(child); + baSeen = true; + break; + case "BootstrapperApplicationRef": + this.ParseBootstrapperApplicationRefElement(child); + break; + case "BundleCustomData": + this.ParseBundleCustomDataElement(child); + break; + case "BundleCustomDataRef": + this.ParseBundleCustomDataRefElement(child); + break; + case "BundleExtension": + this.ParseBundleExtensionElement(child); + break; + case "BundleExtensionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension); + break; + case "OptionalUpdateRegistration": + this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name); + break; + case "Chain": + if (chainSeen) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Chain")); + } + this.ParseChainElement(child); + chainSeen = true; + break; + case "Container": + this.ParseContainerElement(child); + break; + case "ContainerRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleContainer); + break; + case "Log": + if (logSeen) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Log")); + } + logVariablePrefixAndExtension = this.ParseLogElement(child, fileSystemSafeBundleName); + logSeen = true; + break; + case "PayloadGroup": + this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads); + break; + case "PayloadGroupRef": + this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads, ComplexReferenceChildType.Unknown, null); + break; + case "RelatedBundle": + this.ParseRelatedBundleElement(child); + break; + case "Requires": + this.ParseRequiresElement(child, null); + break; + case "SetVariable": + this.ParseSetVariableElement(child); + break; + case "SetVariableRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); + break; + case "SoftwareTag": + this.ParseBundleTagElement(child); + break; + case "Update": + this.ParseUpdateElement(child); + break; + case "Variable": + this.ParseVariableElement(child); + break; + case "WixVariable": + this.ParseWixVariableElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!chainSeen) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Chain")); + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new WixBundleSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeCode, + Version = version, + Copyright = copyright, + Name = name, + Manufacturer = manufacturer, + Attributes = attributes, + AboutUrl = aboutUrl, + HelpUrl = helpUrl, + HelpTelephone = helpTelephone, + UpdateUrl = updateUrl, + Compressed = YesNoDefaultType.Yes == compressed ? true : YesNoDefaultType.No == compressed ? (bool?)false : null, + IconSourceFile = iconSourceFile, + SplashScreenSourceFile = splashScreenSourceFile, + Condition = condition, + Tag = tag, + Platform = this.CurrentPlatform, + ParentName = parentName, + }); + + if (!String.IsNullOrEmpty(logVariablePrefixAndExtension)) + { + var split = logVariablePrefixAndExtension.Split(':'); + symbol.LogPathVariable = split[0]; + symbol.LogPrefix = split[1]; + symbol.LogExtension = split[2]; + } + + if (null != upgradeCode) + { + this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) + { + BundleId = upgradeCode, + Action = RelatedBundleActionType.Upgrade, + }); + } + + this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnDefaultAttachedContainerId) + { + Name = "bundle-attached.cab", + Type = ContainerType.Attached, + }); + + // Ensure that the bundle stores the well-known persisted values. + this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_NAME)) + { + Hidden = false, + Persisted = true, + }); + + this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE)) + { + Hidden = false, + Persisted = true, + }); + + this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER)) + { + Hidden = false, + Persisted = true, + }); + + this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_LAST_USED_SOURCE)) + { + Hidden = false, + Persisted = true, + }); + } + } + + /// + /// Parse a Container element. + /// + /// Element to parse + /// + private string ParseLogElement(XElement node, string fileSystemSafeBundleName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var disableLog = YesNoType.NotSet; + var variable = "WixBundleLog"; + var logPrefix = fileSystemSafeBundleName ?? "Setup"; + var logExtension = ".log"; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Disable": + disableLog = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "PathVariable": + variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Prefix": + logPrefix = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Extension": + logExtension = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (!logExtension.StartsWith(".", StringComparison.Ordinal)) + { + logExtension = String.Concat(".", logExtension); + } + + this.Core.ParseForExtensionElements(node); + + return YesNoType.Yes == disableLog ? null : String.Join(":", variable, logPrefix, logExtension); + } + + /// + /// Parse a Container element. + /// + /// Element to parse + private void ParseContainerElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string downloadUrl = null; + string name = null; + var type = ContainerType.Detached; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + if (id?.Id == BurnConstants.BurnUXContainerName || id?.Id == BurnConstants.BurnDefaultAttachedContainerName) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + break; + case "DownloadUrl": + downloadUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Type": + var typeString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeString) + { + case "attached": + type = ContainerType.Attached; + break; + case "detached": + type = ContainerType.Detached; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + if (!String.IsNullOrEmpty(name)) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (!Common.IsIdentifier(id.Id)) + { + this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + } + else if (null == name) + { + name = id.Id; + } + + if (!String.IsNullOrEmpty(downloadUrl) && ContainerType.Detached != type) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "Type", "attached")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "PackageGroupRef": + this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.Container, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, id) + { + Name = name, + Type = type, + DownloadUrl = downloadUrl + }); + } + } + + /// + /// Parse the BoostrapperApplication element. + /// + /// Element to parse + private void ParseBootstrapperApplicationElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + Identifier previousId = null; + var previousType = ComplexReferenceChildType.Unknown; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "BootstrapperApplicationDll": + previousId = this.ParseBootstrapperApplicationDllElement(child, id, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (id != null) + { + this.Core.AddSymbol(new WixBootstrapperApplicationSymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parse the BoostrapperApplication element. + /// + /// Element to parse + /// + /// + /// + private Identifier ParseBootstrapperApplicationDllElement(XElement node, Identifier defaultId, ComplexReferenceChildType previousType, Identifier previousId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) + { + Id = defaultId, + }; + var dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + case "DpiAwareness": + var dpiAwarenessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (dpiAwarenessValue) + { + case "gdiScaled": + dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.GdiScaled; + break; + case "perMonitor": + dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitor; + break; + case "perMonitorV2": + dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; + break; + case "system": + dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.System; + break; + case "unaware": + dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.Unaware; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "DpiAwareness", dpiAwarenessValue, "gdiScaled", "perMonitor", "perMonitorV2", "system", "unaware")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + extensionAttributes.Add(attrib); + } + } + + compilerPayload.FinishCompilingPayload(); + + // Now that the Id is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); + this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnUXContainerId) + { + Name = "bundle-ux.cab", + Type = ContainerType.Attached + }); + + this.Core.AddSymbol(new WixBootstrapperApplicationDllSymbol(sourceLineNumbers, compilerPayload.Id) + { + DpiAwareness = dpiAwareness, + }); + } + + return compilerPayload.Id; + } + + /// + /// Parse the BoostrapperApplicationRef element. + /// + /// Element to parse + private void ParseBootstrapperApplicationRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + Identifier previousId = null; + var previousType = ComplexReferenceChildType.Unknown; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (String.IsNullOrEmpty(id)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBootstrapperApplication, id); + } + } + + + + /// + /// Parses a BundleCustomData element. + /// + /// Element to parse. + private void ParseBundleCustomDataElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string customDataId = null; + WixBundleCustomDataType? customDataType = null; + string extensionId = null; + var attributeDefinitions = new List(); + var foundAttributeDefinitions = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "bootstrapperApplication": + customDataType = WixBundleCustomDataType.BootstrapperApplication; + break; + case "bundleExtension": + customDataType = WixBundleCustomDataType.BundleExtension; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "bootstrapperApplication", "bundleExtension")); + customDataType = WixBundleCustomDataType.Unknown; // set a value to prevent expected attribute error below. + break; + } + break; + case "ExtensionId": + extensionId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleExtension, extensionId); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == customDataId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + var hasExtensionId = null != extensionId; + if (!customDataType.HasValue) + { + customDataType = hasExtensionId ? WixBundleCustomDataType.BundleExtension : WixBundleCustomDataType.BootstrapperApplication; + } + + if (!customDataType.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); + } + else if (hasExtensionId) + { + if (customDataType.Value == WixBundleCustomDataType.BootstrapperApplication) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensonId", "Type", "bootstrapperApplication")); + } + } + else if (customDataType.Value == WixBundleCustomDataType.BundleExtension) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensionId", "Type", "bundleExtension")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "BundleAttributeDefinition": + foundAttributeDefinitions = true; + + var attributeDefinition = this.ParseBundleAttributeDefinitionElement(child, childSourceLineNumbers, customDataId); + if (attributeDefinition != null) + { + attributeDefinitions.Add(attributeDefinition); + } + break; + case "BundleElement": + this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (attributeDefinitions.Count > 0) + { + if (!this.Core.EncounteredError) + { + var attributeNames = String.Join(new string(WixBundleCustomDataSymbol.AttributeNamesSeparator, 1), attributeDefinitions.Select(c => c.Name)); + + this.Core.AddSymbol(new WixBundleCustomDataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, customDataId)) + { + AttributeNames = attributeNames, + Type = customDataType.Value, + BundleExtensionRef = extensionId, + }); + } + } + else if (!foundAttributeDefinitions) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "BundleAttributeDefinition")); + } + } + + /// + /// Parses a BundleCustomDataRef element. + /// + /// Element to parse. + private void ParseBundleCustomDataRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string customDataId = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == customDataId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "BundleElement": + this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses a BundleAttributeDefinition element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// BundleCustomData Id. + private WixBundleCustomDataAttributeSymbol ParseBundleAttributeDefinitionElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) + { + string attributeName = null; + + foreach (var attrib in node.Attributes()) + { + switch (attrib.Name.LocalName) + { + case "Id": + attributeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + + if (null == attributeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (this.Core.EncounteredError) + { + return null; + } + + var customDataAttribute = this.Core.AddSymbol(new WixBundleCustomDataAttributeSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, attributeName)) + { + CustomDataRef = customDataId, + Name = attributeName, + }); + return customDataAttribute; + } + + /// + /// Parses a BundleElement element. + /// + /// Element to parse. + /// Element's SourceLineNumbers. + /// BundleCustomData Id. + private void ParseBundleElementElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId) + { + var elementId = Guid.NewGuid().ToString("N").ToUpperInvariant(); + + foreach (var attrib in node.Attributes()) + { + this.Core.ParseExtensionAttribute(node, attrib); + } + + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "BundleAttribute": + string attributeName = null; + string value = null; + foreach (var attrib in child.Attributes()) + { + switch (attrib.Name.LocalName) + { + case "Id": + attributeName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.ParseExtensionAttribute(child, attrib); + break; + } + } + + if (null == attributeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleCustomDataCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, elementId, attributeName)) + { + ElementId = elementId, + AttributeRef = attributeName, + CustomDataRef = customDataId, + Value = value, + }); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + + if (!this.Core.EncounteredError) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId); + } + } + + /// + /// Parse the BundleExtension element. + /// + /// Element to parse + private void ParseBundleExtensionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); + Identifier previousId = null; + var previousType = ComplexReferenceChildType.Unknown; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + extensionAttributes.Add(attrib); + } + } + + compilerPayload.FinishCompilingPayload(); + + // Now that the Id is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); + } + + compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); + previousId = compilerPayload.Id; + previousType = ComplexReferenceChildType.Payload; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + // Add the BundleExtension. + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleExtensionSymbol(sourceLineNumbers, compilerPayload.Id) + { + PayloadRef = compilerPayload.Id.Id, + }); + } + } + + /// + /// Parse the OptionalUpdateRegistration element. + /// + /// The element to parse. + /// The manufacturer. + /// The product family. + /// The bundle name. + private void ParseOptionalUpdateRegistrationElement(XElement node, string defaultManufacturer, string defaultProductFamily, string defaultName) + { + const string defaultClassification = "Update"; + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string manufacturer = null; + string department = null; + string productFamily = null; + string name = null; + var classification = defaultClassification; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Manufacturer": + manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Department": + department = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ProductFamily": + productFamily = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Classification": + classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(manufacturer)) + { + if (!String.IsNullOrEmpty(defaultManufacturer)) + { + manufacturer = defaultManufacturer; + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Manufacturer", node.Parent.Name.LocalName)); + } + } + + if (String.IsNullOrEmpty(productFamily)) + { + if (!String.IsNullOrEmpty(defaultProductFamily)) + { + productFamily = defaultProductFamily; + } + } + + if (String.IsNullOrEmpty(name)) + { + if (!String.IsNullOrEmpty(defaultName)) + { + name = defaultName; + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Name", node.Parent.Name.LocalName)); + } + } + + if (String.IsNullOrEmpty(classification)) + { + this.Core.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, node.Name.LocalName, "Classification", defaultClassification)); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixUpdateRegistrationSymbol(sourceLineNumbers) + { + Manufacturer = manufacturer, + Department = department, + ProductFamily = productFamily, + Name = name, + Classification = classification + }); + } + } + + /// + /// Parse Payload element. + /// + /// Element to parse + /// ComplexReferenceParentType of parent element. (BA or PayloadGroup) + /// Identifier of parent element. + /// + /// + private Identifier ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) + { + Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); + Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + var allowed = true; + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Compressed": + compilerPayload.ParseCompressed(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + case "DownloadUrl": + compilerPayload.ParseDownloadUrl(attrib); + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedAttribute(node, attrib); + } + } + else + { + extensionAttributes.Add(attrib); + } + } + + compilerPayload.FinishCompilingPayload(); + + // Now that the PayloadId is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child, context); + } + } + + compilerPayload.CreatePayloadSymbol(parentType, parentId?.Id, previousType, previousId?.Id); + + return compilerPayload.Id; + } + + /// + /// Parse PayloadGroup element. + /// + /// Element to parse + /// Optional ComplexReferenceParentType of parent element. (typically another PayloadGroup) + /// Identifier of parent element. + private void ParsePayloadGroupElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId) + { + Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + var previousType = ComplexReferenceChildType.Unknown; + Identifier previousId = null; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + WixBundlePackageType? packageType = null; + switch (child.Name.LocalName) + { + case "ExePackagePayload": + packageType = WixBundlePackageType.Exe; + break; + case "MsiPackagePayload": + packageType = WixBundlePackageType.Msi; + break; + case "MspPackagePayload": + packageType = WixBundlePackageType.Msp; + break; + case "MsuPackagePayload": + packageType = WixBundlePackageType.Msu; + break; + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + + if (packageType.HasValue) + { + var compilerPayload = this.ParsePackagePayloadElement(null, child, packageType.Value, null); + var payloadSymbol = compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.PayloadGroup, id?.Id, previousType, previousId?.Id); + if (payloadSymbol != null) + { + previousId = payloadSymbol.Id; + previousType = ComplexReferenceChildType.Payload; + + this.CreatePackagePayloadSymbol(payloadSymbol.SourceLineNumbers, packageType.Value, payloadSymbol.Id, ComplexReferenceParentType.PayloadGroup, id); + } + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundlePayloadGroupSymbol(sourceLineNumbers, id)); + + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null); + } + } + + /// + /// Parses a payload group reference element. + /// + /// Element to parse. + /// ComplexReferenceParentType of parent element (BA or PayloadGroup). + /// Identifier of parent element. + /// + /// + private Identifier ParsePayloadGroupRefElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId) + { + Debug.Assert(ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); + Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePayloadGroup, id.Id); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id?.Id, previousType, previousId?.Id); + + return id; + } + + /// + /// Parse ExitCode element. + /// + /// Element to parse + /// Id of parent element + private void ParseExitCodeElement(XElement node, string packageId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var value = CompilerConstants.IntegerNotSet; + var behavior = ExitCodeBehaviorType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + value = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); + break; + case "Behavior": + var behaviorString = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (behaviorString) + { + case "error": + behavior = ExitCodeBehaviorType.Error; + break; + case "forceReboot": + behavior = ExitCodeBehaviorType.ForceReboot; + break; + case "scheduleReboot": + behavior = ExitCodeBehaviorType.ScheduleReboot; + break; + case "success": + behavior = ExitCodeBehaviorType.Success; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Behavior", behaviorString, "success, error, scheduleReboot, forceReboot")); + behavior = ExitCodeBehaviorType.Success; // set value to avoid ExpectedAttribute below. + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (ExitCodeBehaviorType.NotSet == behavior) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Behavior")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundlePackageExitCodeSymbol(sourceLineNumbers) + { + ChainPackageId = packageId, + Code = value, + Behavior = behavior + }); + } + } + + /// + /// Parse Chain element. + /// + /// Element to parse + private void ParseChainElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var attributes = WixChainAttributes.None; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "DisableRollback": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixChainAttributes.DisableRollback; + } + break; + case "DisableSystemRestore": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixChainAttributes.DisableSystemRestore; + } + break; + case "ParallelCache": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixChainAttributes.ParallelCache; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + string previousId = null; + var previousType = ComplexReferenceChildType.Unknown; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "MsiPackage": + previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "MspPackage": + previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "MsuPackage": + previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "ExePackage": + previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "RollbackBoundary": + previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "PackageGroupRef": + previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId); + previousType = ComplexReferenceChildType.PackageGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (null == previousId) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "MsiPackage", "ExePackage", "PackageGroupRef")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixChainSymbol(sourceLineNumbers) + { + Attributes = attributes + }); + } + } + + /// + /// Parse MsiPackage element + /// + /// Element to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + private string ParseMsiPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + return this.ParseChainPackage(node, WixBundlePackageType.Msi, parentType, parentId, previousType, previousId); + } + + /// + /// Parse MspPackage element + /// + /// Element to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + private string ParseMspPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + return this.ParseChainPackage(node, WixBundlePackageType.Msp, parentType, parentId, previousType, previousId); + } + + /// + /// Parse MsuPackage element + /// + /// Element to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + private string ParseMsuPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + return this.ParseChainPackage(node, WixBundlePackageType.Msu, parentType, parentId, previousType, previousId); + } + + /// + /// Parse ExePackage element + /// + /// Element to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + private string ParseExePackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + return this.ParseChainPackage(node, WixBundlePackageType.Exe, parentType, parentId, previousType, previousId); + } + + /// + /// Parse RollbackBoundary element + /// + /// Element to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + private string ParseRollbackBoundaryElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); + Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var vital = YesNoType.Yes; + var transaction = YesNoType.No; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + var allowed = true; + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + if (id?.Id == BurnConstants.BundleDefaultBoundaryId) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + break; + case "Vital": + vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Transaction": + transaction = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedAttribute(node, attrib); + } + } + else + { + // Save the extension attributes for later... + extensionAttributes.Add(attrib); + } + } + + if (null == id) + { + if (!String.IsNullOrEmpty(previousId)) + { + id = this.Core.CreateIdentifier("rba", previousId); + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if (!Common.IsIdentifier(id.Id)) + { + this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + } + + // Now that the rollback identifier is known, we can parse the extension attributes... + var contextValues = new Dictionary + { + ["RollbackBoundaryId"] = id.Id + }; + foreach (var attribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, attribute, contextValues); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.CreateRollbackBoundary(sourceLineNumbers, id, vital, transaction, parentType, parentId, previousType, previousId); + } + + return id.Id; + } + + /// + /// Parses one of the ChainPackage elements + /// + /// Element to parse + /// Type of package to parse + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier for package element. + /// This method contains the shared logic for parsing all of the ChainPackage + /// types, as there is more in common between them than different. + private string ParseChainPackage(XElement node, WixBundlePackageType packageType, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); + Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) + { + IsRequired = false, + }; + string after = null; + string installCondition = null; + var cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space. + string cacheId = null; + string description = null; + string displayName = null; + var logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; + var rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; + var permanent = YesNoType.NotSet; + var visible = YesNoType.NotSet; + var vital = YesNoType.Yes; + string installArguments = null; + string repairArguments = null; + string uninstallArguments = null; + var perMachine = YesNoDefaultType.NotSet; + string detectCondition = null; + string protocol = null; + var installSize = CompilerConstants.IntegerNotSet; + string msuKB = null; + var enableFeatureSelection = YesNoType.NotSet; + var forcePerMachine = YesNoType.NotSet; + CompilerPayload childPackageCompilerPayload = null; + var slipstream = YesNoType.NotSet; + var hasPayloadInfo = false; + + var expectedNetFx4Args = new string[] { "/q", "/norestart" }; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + var allowed = true; + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + hasPayloadInfo = true; + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + hasPayloadInfo = true; + break; + case "DownloadUrl": + compilerPayload.ParseDownloadUrl(attrib); + hasPayloadInfo = true; + break; + case "After": + after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "InstallCondition": + installCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Cache": + var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (value) + { + case "always": + cache = YesNoAlwaysType.Always; + break; + case "yes": + cache = YesNoAlwaysType.Yes; + break; + case "no": + cache = YesNoAlwaysType.No; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "always", "yes", "no")); + break; + } + break; + case "CacheId": + cacheId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "EnableFeatureSelection": + enableFeatureSelection = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Msi); + break; + case "ForcePerMachine": + forcePerMachine = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Msi); + break; + case "LogPathVariable": + logPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "RollbackLogPathVariable": + rollbackPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Permanent": + permanent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Visible": + visible = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Msi); + break; + case "Vital": + vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "InstallArguments": + installArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Exe); + break; + case "RepairArguments": + repairArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + allowed = (packageType == WixBundlePackageType.Exe); + break; + case "UninstallArguments": + uninstallArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Exe); + break; + case "PerMachine": + perMachine = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msp); + break; + case "DetectCondition": + detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu); + break; + case "Protocol": + protocol = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Exe); + break; + case "InstallSize": + installSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "KB": + msuKB = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Msu); + break; + case "Compressed": + compilerPayload.ParseCompressed(attrib); + hasPayloadInfo = true; + break; + case "Slipstream": + slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + allowed = (packageType == WixBundlePackageType.Msp); + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedAttribute(node, attrib); + } + } + else + { + // Save the extension attributes for later... + extensionAttributes.Add(attrib); + } + } + + // We need to handle the package payload up front because it affects Id generation. Id is needed by other child elements. + var packagePayloadElementName = packageType + "PackagePayload"; + foreach (var child in node.Elements(CompilerCore.WixNamespace + packagePayloadElementName)) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + + if (childPackageCompilerPayload != null) + { + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); + } + else if (hasPayloadInfo) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "SourceFile", "Name", "DownloadUrl", "Compressed")); + } + + childPackageCompilerPayload = this.ParsePackagePayloadElement(childSourceLineNumbers, child, packageType, compilerPayload.Id); + } + + if (compilerPayload.Id == null && childPackageCompilerPayload != null) + { + compilerPayload.Id = childPackageCompilerPayload.Id; + } + + compilerPayload.FinishCompilingPackage(); + var id = compilerPayload.Id; + + if (id.Id == BurnConstants.BundleDefaultBoundaryId) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + + if (null == logPathVariable) + { + logPathVariable = String.Concat("WixBundleLog_", id.Id); + } + + if (null == rollbackPathVariable) + { + rollbackPathVariable = String.Concat("WixBundleRollbackLog_", id.Id); + } + + if (!String.IsNullOrEmpty(protocol) && !protocol.Equals("burn", StringComparison.Ordinal) && !protocol.Equals("netfx4", StringComparison.Ordinal) && !protocol.Equals("none", StringComparison.Ordinal)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Protocol", protocol, "none, burn, netfx4")); + } + + if (!String.IsNullOrEmpty(protocol) && protocol.Equals("netfx4", StringComparison.Ordinal)) + { + foreach (var expectedArgument in expectedNetFx4Args) + { + if (null == installArguments || -1 == installArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallArguments", installArguments, expectedArgument, "Protocol", "netfx4")); + } + + if (!String.IsNullOrEmpty(repairArguments) && -1 == repairArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairArguments", repairArguments, expectedArgument, "Protocol", "netfx4")); + } + + if (!String.IsNullOrEmpty(uninstallArguments) && -1 == uninstallArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallArguments", uninstallArguments, expectedArgument, "Protocol", "netfx4")); + } + } + } + + // Only set default scope for EXEs and MSPs if not already set. + if ((WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msp == packageType) && YesNoDefaultType.NotSet == perMachine) + { + perMachine = YesNoDefaultType.Default; + } + + // Detect condition is recommended or required for Exe and Msu packages + // (depending on whether uninstall arguments were provided). + if ((packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu) && String.IsNullOrEmpty(detectCondition)) + { + if (String.IsNullOrEmpty(uninstallArguments)) + { + this.Core.Write(WarningMessages.DetectConditionRecommended(sourceLineNumbers, node.Name.LocalName)); + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeWithValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DetectCondition", "UninstallArguments")); + } + } + + // Now that the package ID is known, we can parse the extension attributes... + var contextValues = new Dictionary() { { "PackageId", id.Id } }; + foreach (var attribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, attribute, contextValues); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var allowed = true; + switch (child.Name.LocalName) + { + case "SlipstreamMsp": + allowed = (packageType == WixBundlePackageType.Msi); + if (allowed) + { + this.ParseSlipstreamMspElement(child, id.Id); + } + break; + case "MsiProperty": + allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp); + if (allowed) + { + this.ParseMsiPropertyElement(child, id.Id); + } + break; + case "Payload": + this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); + break; + case "PayloadGroupRef": + this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null); + break; + case "Provides": + this.ParseProvidesElement(child, packageType, id.Id, out _); + break; + case "ExitCode": + allowed = (packageType == WixBundlePackageType.Exe); + if (allowed) + { + this.ParseExitCodeElement(child, id.Id); + } + break; + case "CommandLine": + allowed = (packageType == WixBundlePackageType.Exe); + if (allowed) + { + this.ParseCommandLineElement(child, id.Id); + } + break; + case "ExePackagePayload": + case "MsiPackagePayload": + case "MspPackagePayload": + case "MsuPackagePayload": + allowed = packagePayloadElementName == child.Name.LocalName; + // Handled previously + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedElement(node, child); + } + } + else + { + var context = new Dictionary() { { "Id", id.Id } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (!this.Core.EncounteredError) + { + var packageCompilerPayload = childPackageCompilerPayload ?? (hasPayloadInfo ? compilerPayload : null); + if (packageCompilerPayload != null) + { + var payload = packageCompilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id); + + this.CreatePackagePayloadSymbol(sourceLineNumbers, packageType, payload.Id, ComplexReferenceParentType.Package, id); + } + + this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); + + WixBundlePackageAttributes attributes = 0; + attributes |= (YesNoType.Yes == permanent) ? WixBundlePackageAttributes.Permanent : 0; + attributes |= (YesNoType.Yes == visible) ? WixBundlePackageAttributes.Visible : 0; + + var chainPackageSymbol = this.Core.AddSymbol(new WixBundlePackageSymbol(sourceLineNumbers, id) + { + Type = packageType, + Attributes = attributes, + InstallCondition = installCondition, + CacheId = cacheId, + Description = description, + DisplayName = displayName, + LogPathVariable = logPathVariable, + RollbackLogPathVariable = rollbackPathVariable, + }); + + if (YesNoAlwaysType.NotSet != cache) + { + chainPackageSymbol.Cache = cache; + } + + if (YesNoType.NotSet != vital) + { + chainPackageSymbol.Vital = (vital == YesNoType.Yes); + } + + if (YesNoDefaultType.NotSet != perMachine) + { + chainPackageSymbol.PerMachine = perMachine; + } + + if (CompilerConstants.IntegerNotSet != installSize) + { + chainPackageSymbol.InstallSize = installSize; + } + + switch (packageType) + { + case WixBundlePackageType.Exe: + this.Core.AddSymbol(new WixBundleExePackageSymbol(sourceLineNumbers, id) + { + Attributes = WixBundleExePackageAttributes.None, + DetectCondition = detectCondition, + InstallCommand = installArguments, + RepairCommand = repairArguments, + UninstallCommand = uninstallArguments, + ExeProtocol = protocol + }); + break; + + case WixBundlePackageType.Msi: + WixBundleMsiPackageAttributes msiAttributes = 0; + msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0; + msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0; + + this.Core.AddSymbol(new WixBundleMsiPackageSymbol(sourceLineNumbers, id) + { + Attributes = msiAttributes + }); + break; + + case WixBundlePackageType.Msp: + WixBundleMspPackageAttributes mspAttributes = 0; + mspAttributes |= (YesNoType.Yes == slipstream) ? WixBundleMspPackageAttributes.Slipstream : 0; + + this.Core.AddSymbol(new WixBundleMspPackageSymbol(sourceLineNumbers, id) + { + Attributes = mspAttributes + }); + break; + + case WixBundlePackageType.Msu: + this.Core.AddSymbol(new WixBundleMsuPackageSymbol(sourceLineNumbers, id) + { + DetectCondition = detectCondition, + MsuKB = msuKB + }); + break; + } + + this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, after); + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ContainerPackage, id.Id, ComplexReferenceChildType.Unknown, null); + } + + return id.Id; + } + + private void CreatePackagePayloadSymbol(SourceLineNumber sourceLineNumbers, WixBundlePackageType packageType, Identifier payloadId, ComplexReferenceParentType parentType, Identifier parentId) + { + switch (packageType) + { + case WixBundlePackageType.Exe: + this.Core.AddSymbol(new WixBundleExePackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + + case WixBundlePackageType.Msi: + this.Core.AddSymbol(new WixBundleMsiPackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + + case WixBundlePackageType.Msp: + this.Core.AddSymbol(new WixBundleMspPackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + + case WixBundlePackageType.Msu: + this.Core.AddSymbol(new WixBundleMsuPackagePayloadSymbol(sourceLineNumbers, payloadId)); + break; + } + + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PackagePayload, payloadId?.Id, ComplexReferenceChildType.Unknown, null); + } + + private CompilerPayload ParsePackagePayloadElement(SourceLineNumber sourceLineNumbers, XElement node, WixBundlePackageType packageType, Identifier defaultId) + { + sourceLineNumbers = sourceLineNumbers ?? Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) + { + Id = defaultId, + IsRemoteAllowed = packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu, + }; + + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + var allowed = true; + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Compressed": + compilerPayload.ParseCompressed(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + case "DownloadUrl": + compilerPayload.ParseDownloadUrl(attrib); + break; + case "Description": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseDescription(attrib); + } + break; + case "Hash": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseHash(attrib); + } + break; + case "ProductName": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseProductName(attrib); + } + break; + case "Size": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseSize(attrib); + } + break; + case "Version": + allowed = compilerPayload.IsRemoteAllowed; + if (allowed) + { + compilerPayload.ParseVersion(attrib); + } + break; + default: + allowed = false; + break; + } + + if (!allowed) + { + this.Core.UnexpectedAttribute(node, attrib); + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + compilerPayload.FinishCompilingPackagePayload(); + + // Now that the PayloadId is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); + } + + this.Core.ParseForExtensionElements(node); + + return compilerPayload; + } + + /// + /// Parse CommandLine element. + /// + /// Element to parse + /// Parent packageId + private void ParseCommandLineElement(XElement node, string packageId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string installArgument = null; + string uninstallArgument = null; + string repairArgument = null; + string condition = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "InstallArgument": + installArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "UninstallArgument": + uninstallArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "RepairArgument": + repairArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(condition)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundlePackageCommandLineSymbol(sourceLineNumbers) + { + WixBundlePackageRef = packageId, + InstallArgument = installArgument, + UninstallArgument = uninstallArgument, + RepairArgument = repairArgument, + Condition = condition + }); + } + } + + /// + /// Parse PackageGroup element. + /// + /// Element to parse + private void ParsePackageGroupElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + if (id?.Id == BurnConstants.BundleChainPackageGroupId) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + var previousType = ComplexReferenceChildType.Unknown; + string previousId = null; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "MsiPackage": + previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "MspPackage": + previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "MsuPackage": + previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "ExePackage": + previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "RollbackBoundary": + previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.Package; + break; + case "PackageGroupRef": + previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId); + previousType = ComplexReferenceChildType.PackageGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundlePackageGroupSymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parses a package group reference element. + /// + /// Element to parse. + /// ComplexReferenceParentType of parent element (Unknown or PackageGroup). + /// Identifier of parent element. + /// Identifier for package group element. + private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + return this.ParsePackageGroupRefElement(node, parentType, parentId, ComplexReferenceChildType.Unknown, null); + } + + /// + /// Parses a package group reference element. + /// + /// Element to parse. + /// ComplexReferenceParentType of parent element (Unknown or PackageGroup). + /// Identifier of parent element. + /// + /// + /// Identifier for package group element. + private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.PackageGroup == parentType || ComplexReferenceParentType.Container == parentType); + Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string after = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + if (id == BurnConstants.BundleChainPackageGroupId) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id)); + } + else + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackageGroup, id); + } + break; + case "After": + after = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null != after && ComplexReferenceParentType.Container == parentType) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "After", parentId)); + } + + this.Core.ParseForExtensionElements(node); + + if (ComplexReferenceParentType.Container == parentType) + { + this.Core.CreateWixGroupRow(sourceLineNumbers, ComplexReferenceParentType.Container, parentId, ComplexReferenceChildType.PackageGroup, id); + } + else + { + this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PackageGroup, id, previousType, previousId, after); + } + + return id; + } + + /// + /// Creates rollback boundary. + /// + /// Source line numbers. + /// Identifier for the rollback boundary. + /// Indicates whether the rollback boundary is vital or not. + /// Indicates whether the rollback boundary will use an MSI transaction. + /// Type of parent group. + /// Identifier of parent group. + /// Type of previous item, if any. + /// Identifier of previous item, if any. + private void CreateRollbackBoundary(SourceLineNumber sourceLineNumbers, Identifier id, YesNoType vital, YesNoType transaction, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId) + { + this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); + + var rollbackBoundary = this.Core.AddSymbol(new WixBundleRollbackBoundarySymbol(sourceLineNumbers, id)); + + if (YesNoType.NotSet != vital) + { + rollbackBoundary.Vital = (vital == YesNoType.Yes); + } + + if (YesNoType.NotSet != transaction) + { + rollbackBoundary.Transaction = (transaction == YesNoType.Yes); + } + + this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, null); + } + + /// + /// Creates group and ordering information for packages + /// + /// Source line numbers. + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of this item. + /// Identifier for this item. + /// Type of previous item, if known. + /// Identifier of previous item, if known + /// Identifier of explicit 'After' attribute, if given. + private void CreateChainPackageMetaRows(SourceLineNumber sourceLineNumbers, + ComplexReferenceParentType parentType, string parentId, + ComplexReferenceChildType type, string id, + ComplexReferenceChildType previousType, string previousId, string afterId) + { + // If there's an explicit 'After' attribute, it overrides the inferred previous item. + if (null != afterId) + { + previousType = ComplexReferenceChildType.Package; + previousId = afterId; + } + + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId); + } + + /// + /// Parse MsiProperty element + /// + /// Element to parse + /// Id of parent element + private void ParseMsiPropertyElement(XElement node, string packageId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string name = null; + string value = null; + string condition = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.Core.GetAttributeMsiPropertyNameValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new WixBundleMsiPropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, name)) + { + PackageRef = packageId, + Name = name, + Value = value + }); + + if (!String.IsNullOrEmpty(condition)) + { + symbol.Condition = condition; + } + } + } + + /// + /// Parse SlipstreamMsp element + /// + /// Element to parse + /// Id of parent element + private void ParseSlipstreamMspElement(XElement node, string packageId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackage, id); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleSlipstreamMspSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, id)) + { + TargetPackageRef = packageId, + MspPackageRef = id + }); + } + } + + /// + /// Parse RelatedBundle element + /// + /// Element to parse + private void ParseRelatedBundleElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var actionType = RelatedBundleActionType.Detect; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Action": + var action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (action) + { + case "Detect": + case "detect": + actionType = RelatedBundleActionType.Detect; + break; + case "Upgrade": + case "upgrade": + actionType = RelatedBundleActionType.Upgrade; + break; + case "Addon": + case "addon": + actionType = RelatedBundleActionType.Addon; + break; + case "Patch": + case "patch": + actionType = RelatedBundleActionType.Patch; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", action, "Detect", "Upgrade", "Addon", "Patch")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers) + { + BundleId = id, + Action = actionType, + }); + } + } + + /// + /// Parse Update element + /// + /// Element to parse + private void ParseUpdateElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string location = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Location": + location = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == location) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Location")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleUpdateSymbol(sourceLineNumbers) + { + Location = location + }); + } + } + + /// + /// Parse SetVariable element + /// + /// Element to parse + private void ParseSetVariableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string variable = null; + string condition = null; + string after = null; + string value = null; + string typeValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Variable": + variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Type": + typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib, null); + } + } + + var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); + + this.Core.ParseForExtensionElements(node); + + if (id == null) + { + id = this.Core.CreateIdentifier("sbv", variable, condition, after, value, type.ToString()); + } + + this.Core.CreateWixSearchSymbol(sourceLineNumbers, node.Name.LocalName, id, variable, condition, after); + + if (!this.Messaging.EncounteredError) + { + this.Core.AddSymbol(new WixSetVariableSymbol(sourceLineNumbers, id) + { + Value = value, + Type = type, + }); + } + } + + /// + /// Parse Variable element + /// + /// Element to parse + private void ParseVariableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var hidden = false; + string name = null; + var persisted = false; + string value = null; + string typeValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Hidden": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + hidden = true; + } + break; + case "Name": + name = this.Core.GetAttributeBundleVariableValue(sourceLineNumbers, attrib); + break; + case "Persisted": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + persisted = true; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Type": + typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else if (name.StartsWith("Wix", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(ErrorMessages.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix")); + } + + if (hidden && persisted) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "yes", "Persisted")); + } + + var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value); + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name)) + { + Value = value, + Type = type, + Hidden = hidden, + Persisted = persisted + }); + } + } + + private WixBundleVariableType ValidateVariableTypeWithValue(SourceLineNumber sourceLineNumbers, XElement node, string typeValue, string value) + { + WixBundleVariableType type; + switch (typeValue) + { + case "formatted": + type = WixBundleVariableType.Formatted; + break; + case "numeric": + type = WixBundleVariableType.Numeric; + break; + case "string": + type = WixBundleVariableType.String; + break; + case "version": + type = WixBundleVariableType.Version; + break; + case null: + type = WixBundleVariableType.Unknown; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "formatted", "numeric", "string", "version")); + return WixBundleVariableType.Unknown; + } + + if (type != WixBundleVariableType.Unknown) + { + if (value == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type")); + } + + return type; + } + else if (value == null) + { + return type; + } + + // Infer the type from the current value... + if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase)) + { + // Version constructor does not support simple "v#" syntax so check to see if the value is + // non-negative real quick. + if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _)) + { + return WixBundleVariableType.Version; + } + else if (Version.TryParse(value.Substring(1), out var _)) + { + return WixBundleVariableType.Version; + } + } + + // Not a version, check for numeric. + if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _)) + { + return WixBundleVariableType.Numeric; + } + + return WixBundleVariableType.String; + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Dependency.cs b/src/wix/WixToolset.Core/Compiler_Dependency.cs new file mode 100644 index 00000000..7c863883 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Dependency.cs @@ -0,0 +1,384 @@ +// 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 +{ + using System; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + // The root registry key for the dependency extension. We write to Software\Classes explicitly + // based on the current security context instead of HKCR. See + // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information. + private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\"; + + private static readonly char[] InvalidDependencyCharacters = new char[] { ' ', '\"', ';', '\\' }; + + /// + /// Processes the ProviderKey bundle attribute. + /// + /// Source line number for the parent element. + /// Parent element of attribute. + /// The XML attribute for the ProviderKey attribute. + private void ParseBundleProviderKeyAttribute(SourceLineNumber sourceLineNumbers, XElement parentElement, XAttribute attribute) + { + var providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attribute); + int illegalChar; + + // Make sure the key does not contain any illegal characters or values. + if (String.IsNullOrEmpty(providerKey)) + { + this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, attribute.Name.LocalName)); + } + else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) + { + this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); + } + else if ("ALL" == providerKey) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, parentElement.Name.LocalName, "ProviderKey", providerKey)); + } + + if (!this.Messaging.EncounteredError) + { + // Generate the primary key for the row. + var id = this.Core.CreateIdentifier("dep", attribute.Name.LocalName, providerKey); + + // Create the provider symbol for the bundle. The Component_ field is required + // in the table definition but unused for bundles, so just set it to the valid ID. + this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) + { + ParentRef = id.Id, + ProviderKey = providerKey, + Attributes = WixDependencyProviderAttributes.ProvidesAttributesBundle, + }); + } + } + + /// + /// Processes the Provides element. + /// + /// The XML node for the Provides element. + /// The type of the package being chained into a bundle, or null if building an MSI package. + /// The identifier of the parent component or package. + /// Possible KeyPath identifier. + /// Yes if this is the keypath. + private YesNoType ParseProvidesElement(XElement node, WixBundlePackageType? packageType, string parentId, out string possibleKeyPath) + { + possibleKeyPath = null; + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string version = null; + string displayName = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Version": + version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Make sure the key is valid. The key will default to the ProductCode for MSI packages + // and the package code for MSP packages in the binder if not specified. + if (!String.IsNullOrEmpty(key)) + { + int illegalChar; + + // Make sure the key does not contain any illegal characters or values. + if (0 <= (illegalChar = key.IndexOfAny(InvalidDependencyCharacters))) + { + this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "Key", key[illegalChar], String.Join(" ", InvalidDependencyCharacters))); + } + else if ("ALL" == key) + { + this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Key", key)); + } + } + else if (!packageType.HasValue) + { + // Make sure the ProductCode is authored and set the key. + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "ProductCode"); + key = "!(bind.property.ProductCode)"; + } + else if (WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msu == packageType) + { + // Must specify the provider key when authored for a package. + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + // The Version attribute should not be authored in or for an MSI package. + if (!String.IsNullOrEmpty(version)) + { + switch (packageType) + { + case null: + this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers)); + break; + case WixBundlePackageType.Msi: + this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers, parentId)); + break; + } + } + else if (WixBundlePackageType.Msp == packageType || WixBundlePackageType.Msu == packageType) + { + // Must specify the Version when authored for packages that do not contain a version. + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + + // Need the element ID for child element processing, so generate now if not authored. + if (null == id) + { + id = this.Core.CreateIdentifier("dep", node.Name.LocalName, parentId, key); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Requires": + this.ParseRequiresElement(child, id.Id); + break; + case "RequiresRef": + this.ParseRequiresRefElement(child, id.Id, requiresAction: !packageType.HasValue); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Messaging.EncounteredError) + { + var symbol = this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id) + { + ParentRef = parentId, + ProviderKey = key, + }); + + if (!String.IsNullOrEmpty(version)) + { + symbol.Version = version; + } + + if (!String.IsNullOrEmpty(displayName)) + { + symbol.DisplayName = displayName; + } + + if (!packageType.HasValue) + { + // Generate registry rows for the provider using binder properties. + var keyProvides = String.Concat(DependencyRegistryRoot, key); + var root = RegistryRootType.MachineUser; + + var value = "[ProductCode]"; + this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, null, value, parentId); + + value = !String.IsNullOrEmpty(version) ? version : "[ProductVersion]"; + var versionRegistrySymbol = this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "Version", value, parentId); + + value = !String.IsNullOrEmpty(displayName) ? displayName : "[ProductName]"; + this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "DisplayName", value, parentId); + + // Use the Version registry value and use that as a potential key path. + possibleKeyPath = versionRegistrySymbol.Id; + } + } + + return YesNoType.NotSet; + } + + /// + /// Processes the Requires element. + /// + /// The XML node for the Requires element. + /// The parent provider identifier. + private void ParseRequiresElement(XElement node, string providerId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string providerKey = null; + string minVersion = null; + string maxVersion = null; + var attributes = WixDependencySymbolAttributes.None; + var illegalChar = -1; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ProviderKey": + providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Minimum": + minVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Maximum": + maxVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "IncludeMinimum": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixDependencySymbolAttributes.RequiresAttributesMinVersionInclusive; + } + break; + case "IncludeMaximum": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= WixDependencySymbolAttributes.RequiresAttributesMaxVersionInclusive; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (null == id) + { + // Generate an ID only if this element is authored under a Provides element; otherwise, a RequiresRef + // element will be necessary and the Id attribute will be required. + if (!String.IsNullOrEmpty(providerId)) + { + id = this.Core.CreateIdentifier("dep", node.Name.LocalName, providerKey); + } + else + { + this.Messaging.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Id", "Provides")); + id = Identifier.Invalid; + } + } + + if (String.IsNullOrEmpty(providerKey)) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProviderKey")); + } + // Make sure the key does not contain any illegal characters. + else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters))) + { + this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters))); + } + + if (!this.Messaging.EncounteredError) + { + this.Core.AddSymbol(new WixDependencySymbol(sourceLineNumbers, id) + { + ProviderKey = providerKey, + MinVersion = minVersion, + MaxVersion = maxVersion, + Attributes = attributes + }); + + // Create the relationship between this WixDependency symbol and the WixDependencyProvider symbol. + if (!String.IsNullOrEmpty(providerId)) + { + this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) + { + WixDependencyProviderRef = providerId, + WixDependencyRef = id.Id, + }); + } + } + } + + /// + /// Processes the RequiresRef element. + /// + /// The XML node for the RequiresRef element. + /// The parent provider identifier. + /// Whether the Requires custom action should be referenced. + private void ParseRequiresRefElement(XElement node, string providerId, bool requiresAction) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(id)) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + // Create a link dependency on the row that contains information we'll need during bind. + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixDependency, id); + + // Create the relationship between the WixDependency row and the parent WixDependencyProvider row. + this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers) + { + WixDependencyProviderRef = providerId, + WixDependencyRef = id, + }); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs b/src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs new file mode 100644 index 00000000..ede03933 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs @@ -0,0 +1,417 @@ +// 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 +{ + using System; + using System.IO; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses an EmbeddedChaniner element. + /// + /// Element to parse. + private void ParseEmbeddedChainerElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string commandLine = null; + string condition = null; + string source = null; + var type = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "BinarySource": + if (null != source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "FileSource", "PropertySource")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + type = 0x2; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary + break; + case "CommandLine": + commandLine = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "FileSource": + if (null != source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "PropertySource")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + type = 0x12; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File + break; + case "PropertySource": + if (null != source) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "FileSource")); + } + source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + type = 0x32; + // cannot add a reference to a Property because it may be created at runtime. + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("mec", source, type.ToString()); + } + + if (null == source) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "BinarySource", "FileSource", "PropertySource")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiEmbeddedChainerSymbol(sourceLineNumbers, id) + { + Condition = condition, + CommandLine = commandLine, + Source = source, + Type = type + }); + } + } + + /// + /// Parses an EmbeddedUI element. + /// + /// Element to parse. + private void ParseEmbeddedUIElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string name = null; + var supportsBasicUI = false; + var messageFilter = WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT | WindowsInstallerConstants.INSTALLLOGMODE_ERROR | WindowsInstallerConstants.INSTALLLOGMODE_WARNING | WindowsInstallerConstants.INSTALLLOGMODE_USER + | WindowsInstallerConstants.INSTALLLOGMODE_INFO | WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE | WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE + | WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA + | WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS | WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA | WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE + | WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE | WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG | WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE + | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "IgnoreFatalExit": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT; + } + break; + case "IgnoreError": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ERROR; + } + break; + case "IgnoreWarning": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_WARNING; + } + break; + case "IgnoreUser": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_USER; + } + break; + case "IgnoreInfo": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INFO; + } + break; + case "IgnoreFilesInUse": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE; + } + break; + case "IgnoreResolveSource": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE; + } + break; + case "IgnoreOutOfDiskSpace": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE; + } + break; + case "IgnoreActionStart": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART; + } + break; + case "IgnoreActionData": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA; + } + break; + case "IgnoreProgress": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS; + } + break; + case "IgnoreCommonData": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA; + } + break; + case "IgnoreInitialize": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE; + } + break; + case "IgnoreTerminate": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE; + } + break; + case "IgnoreShowDialog": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG; + } + break; + case "IgnoreRMFilesInUse": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE; + } + break; + case "IgnoreInstallStart": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART; + } + break; + case "IgnoreInstallEnd": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND; + } + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SupportBasicUI": + supportsBasicUI = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(sourceFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + else if (String.IsNullOrEmpty(name)) + { + name = Path.GetFileName(sourceFile); + if (!this.Core.IsValidLongFilename(name, false)) + { + this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); + } + } + + if (null == id) + { + if (!String.IsNullOrEmpty(name)) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (!Common.IsIdentifier(id.Id)) + { + this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + } + else if (String.IsNullOrEmpty(name)) + { + name = id.Id; + } + + if (!name.Contains(".")) + { + this.Core.Write(ErrorMessages.InvalidEmbeddedUIFileName(sourceLineNumbers, name)); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "EmbeddedUIResource": + this.ParseEmbeddedUIResourceElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id) + { + FileName = name, + EntryPoint = true, + SupportsBasicUI = supportsBasicUI, + MessageFilter = messageFilter, + Source = sourceFile + }); + } + } + + /// + /// Parses a embedded UI resource element. + /// + /// Element to parse. + private void ParseEmbeddedUIResourceElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string name = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(sourceFile)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + else if (String.IsNullOrEmpty(name)) + { + name = Path.GetFileName(sourceFile); + if (!this.Core.IsValidLongFilename(name, false)) + { + this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name)); + } + } + + if (null == id) + { + if (!String.IsNullOrEmpty(name)) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (!Common.IsIdentifier(id.Id)) + { + this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); + } + } + else if (String.IsNullOrEmpty(name)) + { + name = id.Id; + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id) + { + FileName = name, + Source = sourceFile + }); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Module.cs b/src/wix/WixToolset.Core/Compiler_Module.cs new file mode 100644 index 00000000..3986c8da --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Module.cs @@ -0,0 +1,662 @@ +// 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 +{ + using System; + using System.Globalization; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a module element. + /// + /// Element to parse. + private void ParseModuleElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var codepage = 0; + string moduleId = null; + string version = null; + var setCodepage = false; + var setPackageName = false; + var setKeywords = false; + var ignoredForMergeModules = false; + + this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); + + this.activeName = null; + this.activeLanguage = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if ("PUT-MODULE-NAME-HERE" == this.activeName) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); + } + else + { + this.activeName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + } + break; + case "Codepage": + codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); + break; + case "Guid": + moduleId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "InstallerVersion": + msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Language": + this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Version": + version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == this.activeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == moduleId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Guid")); + } + + if (null == this.activeLanguage) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); + } + + if (null == version) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + else if (!CompilerCore.IsValidModuleOrBundleVersion(version)) + { + this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Module", version)); + } + + try + { + this.compilingModule = true; // notice that we are actually building a Merge Module here + this.Core.CreateActiveSection(this.activeName, SectionType.Module, this.Context.CompilationId); + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AdminExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); + break; + case "AdminUISequence": + this.ParseSequenceElement(child, SequenceTable.AdminUISequence); + break; + case "AdvertiseExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); + break; + case "InstallExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); + break; + case "InstallUISequence": + this.ParseSequenceElement(child, SequenceTable.InstallUISequence); + break; + case "AppId": + this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); + break; + case "Binary": + this.ParseBinaryElement(child); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, CompilerConstants.IntegerNotSet, null, null); + break; + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); + break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage); + break; + case "Configuration": + this.ParseConfigurationElement(child); + break; + case "CustomAction": + this.ParseCustomActionElement(child); + break; + case "CustomActionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); + break; + case "CustomTable": + this.ParseCustomTableElement(child); + break; + case "CustomTableRef": + this.ParseCustomTableRefElement(child); + break; + case "Dependency": + this.ParseDependencyElement(child); + break; + case "Directory": + this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); + break; + case "DirectoryRef": + this.ParseDirectoryRefElement(child); + break; + case "EmbeddedChainer": + this.ParseEmbeddedChainerElement(child); + break; + case "EmbeddedChainerRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); + break; + case "EnsureTable": + this.ParseEnsureTableElement(child); + break; + case "Exclusion": + this.ParseExclusionElement(child); + break; + case "Icon": + this.ParseIconElement(child); + break; + case "IgnoreTable": + this.ParseIgnoreTableElement(child); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "PropertyRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Property); + break; + case "Requires": + this.ParseRequiresElement(child, null); + break; + case "SetDirectory": + this.ParseSetDirectoryElement(child); + break; + case "SetProperty": + this.ParseSetPropertyElement(child); + break; + case "SFPCatalog": + string parentName = null; + this.ParseSFPCatalogElement(child, ref parentName); + break; + case "StandardDirectory": + this.ParseStandardDirectoryElement(child); + break; + case "Substitution": + this.ParseSubstitutionElement(child); + break; + case "SummaryInformation": + this.ParseSummaryInformationElement(child, ref setCodepage, ref setPackageName, ref setKeywords, ref ignoredForMergeModules); + break; + case "UI": + this.ParseUIElement(child); + break; + case "UIRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); + break; + case "WixVariable": + this.ParseWixVariableElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + if (!setPackageName) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = this.activeName + }); + } + + if (!setKeywords) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = "Installer" + }); + } + + var symbol = this.Core.AddSymbol(new WixModuleSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, this.activeName, this.activeLanguage)) + { + ModuleId = this.activeName, + Language = this.activeLanguage, + Version = version + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.PackageCode, + Value = moduleId + }); + + this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, this.activeLanguage); + } + } + finally + { + this.compilingModule = false; // notice that we are no longer building a Merge Module here + } + } + + /// + /// Parses a dependency element. + /// + /// Element to parse. + private void ParseDependencyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string requiredId = null; + var requiredLanguage = CompilerConstants.IntegerNotSet; + string requiredVersion = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "RequiredId": + requiredId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "RequiredLanguage": + requiredLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "RequiredVersion": + requiredVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == requiredId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredId")); + requiredId = String.Empty; + } + + if (CompilerConstants.IntegerNotSet == requiredLanguage) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredLanguage")); + requiredLanguage = CompilerConstants.IllegalInteger; + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ModuleDependencySymbol(sourceLineNumbers) + { + ModuleID = this.activeName, + RequiredID = requiredId, + RequiredLanguage = requiredLanguage, + RequiredVersion = requiredVersion + }); + + symbol.Set((int)ModuleDependencySymbolFields.ModuleLanguage, this.activeLanguage); + } + } + + /// + /// Parses an exclusion element. + /// + /// Element to parse. + private void ParseExclusionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string excludedId = null; + var excludeExceptLanguage = CompilerConstants.IntegerNotSet; + var excludeLanguage = CompilerConstants.IntegerNotSet; + var excludedLanguageField = "0"; + string excludedMaxVersion = null; + string excludedMinVersion = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ExcludedId": + excludedId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "ExcludeExceptLanguage": + excludeExceptLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "ExcludeLanguage": + excludeLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "ExcludedMaxVersion": + excludedMaxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ExcludedMinVersion": + excludedMinVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == excludedId) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExcludedId")); + excludedId = String.Empty; + } + + if (CompilerConstants.IntegerNotSet != excludeExceptLanguage && CompilerConstants.IntegerNotSet != excludeLanguage) + { + this.Core.Write(ErrorMessages.IllegalModuleExclusionLanguageAttributes(sourceLineNumbers)); + } + else if (CompilerConstants.IntegerNotSet != excludeExceptLanguage) + { + excludedLanguageField = Convert.ToString(-excludeExceptLanguage, CultureInfo.InvariantCulture); + } + else if (CompilerConstants.IntegerNotSet != excludeLanguage) + { + excludedLanguageField = Convert.ToString(excludeLanguage, CultureInfo.InvariantCulture); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ModuleExclusionSymbol(sourceLineNumbers) + { + ModuleID = this.activeName, + ExcludedID = excludedId, + ExcludedMinVersion = excludedMinVersion, + ExcludedMaxVersion = excludedMaxVersion + }); + + symbol.Set((int)ModuleExclusionSymbolFields.ModuleLanguage, this.activeLanguage); + symbol.Set((int)ModuleExclusionSymbolFields.ExcludedLanguage, excludedLanguageField); + } + } + + /// + /// Parses a configuration element for a configurable merge module. + /// + /// Element to parse. + private void ParseConfigurationElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string contextData = null; + string defaultValue = null; + string description = null; + string displayName = null; + var format = CompilerConstants.IntegerNotSet; + string helpKeyword = null; + string helpLocation = null; + bool keyNoOrphan = false; + bool nonNullable = false; + Identifier name = null; + string type = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ContextData": + contextData = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DefaultValue": + defaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Format": + var formatStr = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (formatStr) + { + case "Text": + case "text": + format = 0; + break; + case "Key": + case "key": + format = 1; + break; + case "Integer": + case "integer": + format = 2; + break; + case "Bitfield": + case "bitfield": + format = 3; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Format", formatStr, "Text", "Key", "Integer", "Bitfield")); + break; + } + break; + case "HelpKeyword": + helpKeyword = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HelpLocation": + helpLocation = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "KeyNoOrphan": + keyNoOrphan = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "NonNullable": + nonNullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Type": + type = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (CompilerConstants.IntegerNotSet == format) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Format")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ModuleConfigurationSymbol(sourceLineNumbers, name) + { + Format = format, + Type = type, + ContextData = contextData, + DefaultValue = defaultValue, + KeyNoOrphan = keyNoOrphan, + NonNullable = nonNullable, + DisplayName = displayName, + Description = description, + HelpLocation = helpLocation, + HelpKeyword = helpKeyword + }); + } + } + + /// + /// Parses a substitution element for a configurable merge module. + /// + /// Element to parse. + private void ParseSubstitutionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string column = null; + string rowKeys = null; + string table = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Column": + column = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Row": + rowKeys = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Table": + table = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == column) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Column")); + column = String.Empty; + } + + if (null == table) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Table")); + table = String.Empty; + } + + if (null == rowKeys) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Row")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ModuleSubstitutionSymbol(sourceLineNumbers) + { + Table = table, + Row = rowKeys, + Column = column, + Value = value + }); + } + } + + /// + /// Parses an IgnoreTable element. + /// + /// Element to parse. + private void ParseIgnoreTableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ModuleIgnoreTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, id))); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Package.cs b/src/wix/WixToolset.Core/Compiler_Package.cs new file mode 100644 index 00000000..87ccceb7 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Package.cs @@ -0,0 +1,4996 @@ +// 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 +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a product element. + /// + /// Element to parse. + private void ParsePackageElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compressed = YesNoDefaultType.Default; + var sourceBits = 0; + string codepage = null; + var productCode = "*"; + string productLanguage = null; + var isPerMachine = true; + string upgradeCode = null; + string manufacturer = null; + string version = null; + string symbols = null; + var isCodepageSet = false; + var isPackageNameSet = false; + var isKeywordsSet = false; + var isPackageAuthorSet = false; + + this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion); + + this.activeName = null; + this.activeLanguage = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Codepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); + break; + case "Compressed": + compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + break; + case "InstallerVersion": + msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Language": + productLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Manufacturer": + manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); + if ("PUT-COMPANY-NAME-HERE" == manufacturer) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer)); + } + break; + case "Name": + this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters); + if ("PUT-PRODUCT-NAME-HERE" == this.activeName) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName)); + } + break; + case "ProductCode": + productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); + break; + case "Scope": + var installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (installScope) + { + case "perMachine": + // handled below after we create the section. + break; + case "perUser": + isPerMachine = false; + sourceBits |= 8; + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser")); + break; + } + break; + case "ShortNames": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + sourceBits |= 1; + } + break; + case "UpgradeCode": + upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Version": // if the attribute is valid version, use the attribute value as is (so "1.0000.01.01" would *not* get translated to "1.0.1.1"). + var verifiedVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + if (!String.IsNullOrEmpty(verifiedVersion)) + { + version = attrib.Value; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == productCode) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == manufacturer) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); + } + + if (null == this.activeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == upgradeCode) + { + this.Core.Write(WarningMessages.MissingUpgradeCode(sourceLineNumbers)); + } + + if (null == version) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + else if (!CompilerCore.IsValidProductVersion(version)) + { + this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); + } + + if (compressed != YesNoDefaultType.No) + { + sourceBits |= 2; + } + + if (this.Core.EncounteredError) + { + return; + } + + try + { + this.compilingProduct = true; + this.Core.CreateActiveSection(productCode, SectionType.Product, this.Context.CompilationId); + + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true); + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true); + if (null != upgradeCode) + { + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true); + } + + if (isPerMachine) + { + this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false); + } + + this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, productLanguage); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WordCount, + Value = sourceBits.ToString(CultureInfo.InvariantCulture) + }); + + var contextValues = new Dictionary + { + ["ProductLanguage"] = productLanguage, + ["ProductVersion"] = version, + ["UpgradeCode"] = upgradeCode + }; + + var featureDisplay = 0; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "_locDefinition": + break; + case "AdminExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence); + break; + case "AdminUISequence": + this.ParseSequenceElement(child, SequenceTable.AdminUISequence); + break; + case "AdvertiseExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence); + break; + case "InstallExecuteSequence": + this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence); + break; + case "InstallUISequence": + this.ParseSequenceElement(child, SequenceTable.InstallUISequence); + break; + case "AppId": + this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null); + break; + case "Binary": + this.ParseBinaryElement(child); + break; + case "ComplianceCheck": + this.ParseComplianceCheckElement(child); + break; + case "Component": + this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); + break; + case "ComponentGroup": + this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); + break; + case "CustomAction": + this.ParseCustomActionElement(child); + break; + case "CustomActionRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction); + break; + case "CustomTable": + this.ParseCustomTableElement(child); + break; + case "CustomTableRef": + this.ParseCustomTableRefElement(child); + break; + case "Directory": + this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty); + break; + case "DirectoryRef": + this.ParseDirectoryRefElement(child); + break; + case "EmbeddedChainer": + this.ParseEmbeddedChainerElement(child); + break; + case "EmbeddedChainerRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer); + break; + case "EnsureTable": + this.ParseEnsureTableElement(child); + break; + case "Feature": + this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay); + break; + case "FeatureRef": + this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode); + break; + case "FeatureGroupRef": + this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode); + break; + case "Icon": + this.ParseIconElement(child); + break; + case "InstanceTransforms": + this.ParseInstanceTransformsElement(child); + break; + case "Launch": + this.ParseLaunchElement(child); + break; + case "MajorUpgrade": + this.ParseMajorUpgradeElement(child, contextValues); + break; + case "Media": + this.ParseMediaElement(child, null); + break; + case "MediaTemplate": + this.ParseMediaTemplateElement(child, null); + break; + case "PackageCertificates": + case "PatchCertificates": + this.ParseCertificatesElement(child); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "PropertyRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Property); + break; + case "Requires": + this.ParseRequiresElement(child, null); + break; + case "SetDirectory": + this.ParseSetDirectoryElement(child); + break; + case "SetProperty": + this.ParseSetPropertyElement(child); + break; + case "SFPCatalog": + string parentName = null; + this.ParseSFPCatalogElement(child, ref parentName); + break; + case "SoftwareTag": + this.ParsePackageTagElement(child); + break; + case "StandardDirectory": + this.ParseStandardDirectoryElement(child); + break; + case "SummaryInformation": + this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); + break; + case "SymbolPath": + if (null != symbols) + { + symbols += ";" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + case "UI": + this.ParseUIElement(child); + break; + case "UIRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); + break; + case "Upgrade": + this.ParseUpgradeElement(child); + break; + case "WixVariable": + this.ParseWixVariableElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPackageSymbol(sourceLineNumbers) + { + PackageId = productCode, + UpgradeCode = upgradeCode, + Name = this.activeName, + Language = productLanguage, + Version = version, + Manufacturer = manufacturer, + Attributes = isPerMachine ? WixPackageAttributes.PerMachine : WixPackageAttributes.None, + Codepage = codepage, + }); + + if (!isPackageNameSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = this.activeName + }); + } + + if (!isPackageAuthorSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = manufacturer + }); + } + + if (!isKeywordsSet) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = "Installer" + }); + } + + if (null != symbols) + { + this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers) + { + SymbolId = productCode, + SymbolType = SymbolPathType.Product, + SymbolPaths = symbols, + }); + } + } + } + finally + { + this.compilingProduct = false; + } + } + + private void GetDefaultPlatformAndInstallerVersion(out string platform, out int msiVersion) + { + // Let's default to a modern version of MSI. Users can override, + // of course, subject to platform-specific limitations. + msiVersion = 500; + + switch (this.CurrentPlatform) + { + case Platform.X86: + platform = "Intel"; + break; + case Platform.X64: + platform = "x64"; + break; + case Platform.ARM64: + platform = "Arm64"; + break; + default: + throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString()); + } + } + + private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform, string language) + { + if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion) + { + msiVersion = 200; + this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers)); + } + + if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion) + { + msiVersion = 500; + this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers)); + } + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Comments, + Value = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Title, + Value = "Installation Database" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.PlatformAndLanguage, + Value = $"{platform};{language}" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WindowsInstallerVersion, + Value = msiVersion.ToString(CultureInfo.InvariantCulture) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Security, + Value = "2" + }); + } + + /// + /// Parses an odbc driver or translator element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Default identifer for driver/translator file. + /// Symbol type we're processing for. + private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, SymbolDefinitionType symbolDefinitionType) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var driver = fileId; + string name = null; + var setup = fileId; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "File": + driver = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, driver); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SetupFile": + setup = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, setup); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("odb", name, fileId, setup); + } + + // drivers have a few possible children + if (SymbolDefinitionType.ODBCDriver == symbolDefinitionType) + { + // process any data sources for the driver + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ODBCDataSource": + string ignoredKeyPath = null; + this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath); + break; + case "Property": + this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCAttribute); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + else + { + this.Core.ParseForExtensionElements(node); + } + + if (!this.Core.EncounteredError) + { + switch (symbolDefinitionType) + { + case SymbolDefinitionType.ODBCDriver: + this.Core.AddSymbol(new ODBCDriverSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + FileRef = driver, + SetupFileRef = setup, + }); + break; + case SymbolDefinitionType.ODBCTranslator: + this.Core.AddSymbol(new ODBCTranslatorSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + FileRef = driver, + SetupFileRef = setup, + }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); + } + } + } + + /// + /// Parses a Property element underneath an ODBC driver or translator. + /// + /// Element to parse. + /// Identifier of parent driver or translator. + /// Name of the table to create property in. + private void ParseODBCProperty(XElement node, string parentId, SymbolDefinitionType symbolDefinitionType) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string propertyValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + propertyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var identifier = new Identifier(AccessModifier.Section, parentId, id); + switch (symbolDefinitionType) + { + case SymbolDefinitionType.ODBCAttribute: + this.Core.AddSymbol(new ODBCAttributeSymbol(sourceLineNumbers, identifier) + { + DriverRef = parentId, + Attribute = id, + Value = propertyValue, + }); + break; + case SymbolDefinitionType.ODBCSourceAttribute: + this.Core.AddSymbol(new ODBCSourceAttributeSymbol(sourceLineNumbers, identifier) + { + DataSourceRef = parentId, + Attribute = id, + Value = propertyValue, + }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType)); + } + } + } + + /// + /// Parse an odbc data source element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Default name of driver. + /// Identifier of this element in case it is a keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var keyPath = YesNoType.NotSet; + string name = null; + var registration = CompilerConstants.IntegerNotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "DriverName": + driverName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "KeyPath": + keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Registration": + var registrationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (registrationValue) + { + case "machine": + registration = 0; + break; + case "user": + registration = 1; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (CompilerConstants.IntegerNotSet == registration) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration")); + registration = CompilerConstants.IllegalInteger; + } + + if (null == id) + { + id = this.Core.CreateIdentifier("odc", name, driverName, registration.ToString()); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Property": + this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCSourceAttribute); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ODBCDataSourceSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + Description = name, + DriverDescription = driverName, + Registration = registration + }); + } + + possibleKeyPath = id.Id; + return keyPath; + } + + /// + /// Parses a package element. + /// + /// Element to parse. + /// + /// + /// + /// + private void ParseSummaryInformationElement(XElement node, ref bool isCodepageSet, ref bool isPackageNameSet, ref bool isKeywordsSet, ref bool isPackageAuthorSet) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string codepage = null; + string packageName = null; + string keywords = null; + string packageAuthor = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Codepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true); + break; + case "Description": + packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Keywords": + keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Manufacturer": + packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if ("PUT-COMPANY-NAME-HERE" == packageAuthor) + { + this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor)); + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + if (null != codepage) + { + isCodepageSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = codepage + }); + } + + if (null != packageName) + { + isPackageNameSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = packageName + }); + } + + if (null != packageAuthor) + { + isPackageAuthorSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = packageAuthor + }); + } + + if (null != keywords) + { + isKeywordsSet = true; + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = keywords + }); + } + } + } + + /// + /// Parses a patch information element. + /// + /// Element to parse. + private void ParsePatchInformationElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var codepage = "1252"; + string comments = null; + var keywords = "Installer,Patching,PCP,Database"; + var msiVersion = 1; // Should always be 1 for patches + string packageAuthor = null; + var packageName = this.activeName; + var security = YesNoDefaultType.Default; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AdminImage": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Comments": + comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Compressed": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Description": + packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Keywords": + keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Languages": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "Manufacturer": + packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Platforms": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "ReadOnly": + security = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + break; + case "ShortNames": + this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + break; + case "SummaryCodepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Codepage, + Value = codepage + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Title, + Value = "Patch" + }); + + if (null != packageName) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Subject, + Value = packageName + }); + } + + if (null != packageAuthor) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Author, + Value = packageAuthor + }); + } + + if (null != keywords) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Keywords, + Value = keywords + }); + } + + if (null != comments) + { + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Comments, + Value = comments + }); + } + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WindowsInstallerVersion, + Value = msiVersion.ToString(CultureInfo.InvariantCulture) + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.WordCount, + Value = "0" + }); + + this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers) + { + PropertyId = SummaryInformationType.Security, + Value = YesNoDefaultType.No == security ? "0" : YesNoDefaultType.Yes == security ? "4" : "2" + }); + } + } + + /// + /// Parses a permission element. + /// + /// Element to parse. + /// Identifier of object to be secured. + /// Name of table that contains objectId. + private void ParsePermissionElement(XElement node, string objectId, string tableName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var bits = new BitArray(32); + string domain = null; + string[] specialPermissions = null; + string user = null; + + switch (tableName) + { + case "CreateFolder": + specialPermissions = LockPermissionConstants.FolderPermissions; + break; + case "File": + specialPermissions = LockPermissionConstants.FilePermissions; + break; + case "Registry": + specialPermissions = LockPermissionConstants.RegistryPermissions; + break; + default: + this.Core.UnexpectedElement(node.Parent, node); + return; // stop processing this element since no valid permissions are available + } + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Domain": + domain = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "User": + user = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "FileAllRights": + // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127) + bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[16] = bits[17] = bits[18] = bits[19] = bits[20] = true; + break; + case "SpecificRightsAll": + // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111) + bits[0] = bits[1] = bits[2] = bits[3] = bits[4] = bits[5] = bits[6] = bits[7] = bits[8] = bits[9] = bits[10] = bits[11] = bits[12] = bits[13] = bits[14] = bits[15] = true; + break; + default: + var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + if (!this.Core.TrySetBitFromName(LockPermissionConstants.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16)) + { + if (!this.Core.TrySetBitFromName(LockPermissionConstants.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28)) + { + if (!this.Core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0)) + { + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == user) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User")); + } + + var permission = this.Core.CreateIntegerFromBitArray(bits); + + if (Int32.MinValue == permission) // just GENERIC_READ, which is MSI_NULL + { + this.Core.Write(ErrorMessages.GenericReadNotAllowed(sourceLineNumbers)); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new LockPermissionsSymbol(sourceLineNumbers) + { + LockObject = objectId, + Table = tableName, + Domain = domain, + User = user, + Permission = permission + }); + } + } + + /// + /// Parses an extended permission element. + /// + /// Element to parse. + /// Identifier of object to be secured. + /// Name of table that contains objectId. + private void ParsePermissionExElement(XElement node, string objectId, string tableName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string condition = null; + Identifier id = null; + string sddl = null; + + switch (tableName) + { + case "CreateFolder": + case "File": + case "Registry": + case "ServiceInstall": + break; + default: + this.Core.UnexpectedElement(node.Parent, node); + return; // stop processing this element since nothing will be valid. + } + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Sddl": + sddl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == sddl) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl")); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("pme", objectId, tableName, sddl); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiLockPermissionsExSymbol(sourceLineNumbers, id) + { + LockObject = objectId, + Table = tableName, + SDDLText = sddl, + Condition = condition + }); + } + } + + /// + /// Parses a progid element + /// + /// Element to parse. + /// Identifier of parent component. + /// Flag if progid is advertised. + /// CLSID related to ProgId. + /// Default description of ProgId + /// Optional parent ProgId + /// Set to true if an extension is found; used for error-checking. + /// Whether or not this ProgId is the first one found in the parent class. + /// This element's Id. + private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string icon = null; + var iconIndex = CompilerConstants.IntegerNotSet; + string noOpen = null; + string progId = null; + var progIdAdvertise = YesNoType.NotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + progId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Advertise": + progIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "Icon": + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "IconIndex": + iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); + break; + case "NoOpen": + noOpen = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise)) + { + this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString())); + } + else if (YesNoType.NotSet != progIdAdvertise) + { + advertise = progIdAdvertise; + } + + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex)) + { + this.Core.Write(ErrorMessages.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers)); + } + + var firstProgIdForNestedClass = YesNoType.Yes; + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Extension": + this.ParseExtensionElement(child, componentId, advertise, progId); + foundExtension = true; + break; + case "ProgId": + // Only allow one nested ProgId. If we have a child, we should not have a parent. + if (null == parent) + { + if (YesNoType.Yes == advertise) + { + this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass); + } + else if (YesNoType.No == advertise) + { + this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass); + } + + firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first. + } + else + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.ProgIdNestedTooDeep(childSourceLineNumbers)); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (YesNoType.Yes == advertise) + { + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ProgIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, progId)) + { + ProgId = progId, + ParentProgIdRef = parent, + ClassRef = classId, + Description = description, + }); + + if (null != icon) + { + symbol.IconRef = icon; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); + } + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + symbol.IconIndex = iconIndex; + } + + this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Class); + } + } + else if (YesNoType.No == advertise) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, String.Empty, description, componentId); + if (null != classId) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId); + if (null != parent) // if this is a version independent ProgId + { + if (YesNoType.Yes == firstProgIdForClass) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId); + } + else + { + if (YesNoType.Yes == firstProgIdForClass) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId); + } + } + } + + if (null != icon) // ProgId's Default Icon + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon); + + icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon); + + if (CompilerConstants.IntegerNotSet != iconIndex) + { + icon = String.Concat(icon, ",", iconIndex); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId); + } + } + + if (null != noOpen) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name + } + + // raise an error for an orphaned ProgId + if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId) + { + this.Core.Write(WarningMessages.OrphanedProgId(sourceLineNumbers, progId)); + } + + return progId; + } + + /// + /// Parses a property element. + /// + /// Element to parse. + private void ParsePropertyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var admin = false; + var complianceCheck = false; + var hidden = false; + var secure = false; + var suppressModularization = YesNoType.NotSet; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Admin": + admin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ComplianceCheck": + complianceCheck = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Hidden": + hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Secure": + secure = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SuppressModularization": + suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + else if ("ProductID" == id.Id) + { + this.Core.Write(WarningMessages.ProductIdAuthored(sourceLineNumbers)); + } + else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id) + { + this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id)); + } + + if ("ErrorDialog" == id.Id) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, value); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + { + switch (child.Name.LocalName) + { + case "ProductSearch": + this.ParseProductSearchElement(child, id.Id); + secure = true; + break; + default: + // let ParseSearchSignatures handle standard AppSearch children and unknown elements + break; + } + } + } + } + + this.Core.InnerTextDisallowed(node); + + // see if this property is used for appSearch + var signatures = this.ParseSearchSignatures(node); + + // If we're doing CCP then there must be a signature. + if (complianceCheck && 0 == signatures.Count) + { + this.Core.Write(ErrorMessages.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes")); + } + + foreach (var sig in signatures) + { + if (complianceCheck && !this.Core.EncounteredError) + { + this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, sig))); + } + + this.AddAppSearch(sourceLineNumbers, id, sig); + } + + // If we're doing AppSearch get that setup. + if (0 < signatures.Count) + { + this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); + } + else // just a normal old property. + { + // If the property value is empty and none of the flags are set, print out a warning that we're ignoring + // the element. + if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden) + { + this.Core.Write(WarningMessages.PropertyUseless(sourceLineNumbers, id.Id)); + } + else // there is a value and/or a flag set, do that. + { + this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false); + } + } + + if (!this.Core.EncounteredError && YesNoType.Yes == suppressModularization) + { + this.Core.Write(WarningMessages.PropertyModularizationSuppressed(sourceLineNumbers)); + + this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers) + { + SuppressIdentifier = id.Id + }); + } + } + + /// + /// Parses a RegistryKey element. + /// + /// Element to parse. + /// Identifier for parent component. + /// Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet. + /// Parent key for this Registry element when nested. + /// true if the component is 64-bit. + /// Identifier of this registry key since it could be the component's keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseRegistryKeyElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var key = parentKey; // default to parent key path + var forceCreateOnInstall = false; + var forceDeleteOnUninstall = false; + var keyPath = YesNoType.NotSet; + + possibleKeyPath = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ForceCreateOnInstall": + forceCreateOnInstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ForceDeleteOnUninstall": + forceDeleteOnUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (null != parentKey) + { + key = Path.Combine(parentKey, key); + } + key = key?.TrimEnd('\\'); + break; + case "Root": + if (root.HasValue) + { + this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); + } + + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + var name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null); + + if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present + { + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + } + else // does not generate a Registry row, so no Id should be present + { + if (null != id) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true)); + } + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + key = String.Empty; // set the key to something to prevent null reference exceptions + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + string possibleChildKeyPath = null; + + switch (child.Name.LocalName) + { + case "RegistryKey": + if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) + { + if (YesNoType.Yes == keyPath) + { + this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); + } + + possibleKeyPath = possibleChildKeyPath; // the child is the key path + keyPath = YesNoType.Yes; + } + else if (null == possibleKeyPath && null != possibleChildKeyPath) + { + possibleKeyPath = possibleChildKeyPath; + } + break; + case "RegistryValue": + if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath)) + { + if (YesNoType.Yes == keyPath) + { + this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource")); + } + + possibleKeyPath = possibleChildKeyPath; // the child is the key path + keyPath = YesNoType.Yes; + } + else if (null == possibleKeyPath && null != possibleChildKeyPath) + { + possibleKeyPath = possibleChildKeyPath; + } + break; + case "Permission": + if (!forceCreateOnInstall) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); + } + this.ParsePermissionElement(child, id.Id, "Registry"); + break; + case "PermissionEx": + if (!forceCreateOnInstall) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes")); + } + this.ParsePermissionExElement(child, id.Id, "Registry"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (!this.Core.EncounteredError && null != name) + { + this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + ComponentRef = componentId, + }); + } + + return keyPath; + } + + /// + /// Parses a RegistryValue element. + /// + /// Element to parse. + /// Identifier for parent component. + /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. + /// Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet. + /// true if the component is 64-bit. + /// Identifier of this registry key since it could be the component's keypath. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseRegistryValueElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var key = parentKey; // default to parent key path + string name = null; + string value = null; + string action = null; + var valueType = RegistryValueType.String; + var actionType = RegistryValueActionType.Write; + var keyPath = YesNoType.NotSet; + + possibleKeyPath = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "append": + actionType = RegistryValueActionType.Append; + break; + case "prepend": + actionType = RegistryValueActionType.Prepend; + break; + case "write": + actionType = RegistryValueActionType.Write; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "append", "prepend", "write")); + break; + } + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (null != parentKey) + { + if (parentKey.EndsWith("\\", StringComparison.Ordinal)) + { + key = String.Concat(parentKey, key); + } + else + { + key = String.Concat(parentKey, "\\", key); + } + } + break; + case "KeyPath": + keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + if (root.HasValue) + { + this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers)); + } + + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "binary": + valueType = RegistryValueType.Binary; + break; + case "expandable": + valueType = RegistryValueType.Expandable; + break; + case "integer": + valueType = RegistryValueType.Integer; + break; + case "multiString": + valueType = RegistryValueType.MultiString; + break; + case "string": + valueType = RegistryValueType.String; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "binary", "expandable", "integer", "multiString", "string")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)(root ?? RegistryRootType.Unknown)).ToString(), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (RegistryValueType.MultiString != valueType && (RegistryValueActionType.Append == actionType || RegistryValueActionType.Prepend == actionType)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "MultiString": + case "MultiStringValue": + if (RegistryValueType.MultiString != valueType && null != value) + { + this.Core.Write(ErrorMessages.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type")); + } + else + { + value = this.ParseRegistryMultiStringElement(child, value); + } + break; + case "Permission": + this.ParsePermissionElement(child, id.Id, "Registry"); + break; + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "Registry"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + //switch (typeType) + //{ + //case Wix.RegistryValue.TypeType.binary: + // value = String.Concat("#x", value); + // break; + //case Wix.RegistryValue.TypeType.expandable: + // value = String.Concat("#%", value); + // break; + //case Wix.RegistryValue.TypeType.integer: + // value = String.Concat("#", value); + // break; + //case Wix.RegistryValue.TypeType.multiString: + // switch (actionType) + // { + // case Wix.RegistryValue.ActionType.append: + // value = String.Concat("[~]", value); + // break; + // case Wix.RegistryValue.ActionType.prepend: + // value = String.Concat(value, "[~]"); + // break; + // case Wix.RegistryValue.ActionType.write: + // default: + // if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) + // { + // value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); + // } + // break; + // } + // break; + //case Wix.RegistryValue.TypeType.@string: + // // escape the leading '#' character for string registry keys + // if (null != value && value.StartsWith("#", StringComparison.Ordinal)) + // { + // value = String.Concat("#", value); + // } + // break; + //} + + // value may be set by child MultiStringValue elements, so it must be checked here + if (null == value && valueType != RegistryValueType.Binary) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + else if (0 == value?.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values + { + this.Core.Write(ErrorMessages.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name)); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + Value = value, + ValueType = valueType, + ValueAction = actionType, + ComponentRef = componentId, + }); + } + + // If this was just a regular registry key (that could be the key path) + // and no child registry key set the possible key path, let's make this + // Registry/@Id a possible key path. + if (null == possibleKeyPath) + { + possibleKeyPath = id.Id; + } + + return keyPath; + } + + private string ParseRegistryMultiStringElement(XElement node, string value) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string multiStringValue = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + multiStringValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + this.Core.ParseForExtensionElements(node); + + return null == value ? multiStringValue ?? "[~]" : String.Concat(value, "[~]", multiStringValue); + } + + /// + /// Parses a RemoveRegistryKey element. + /// + /// The element to parse. + /// The component identifier of the parent element. + private void ParseRemoveRegistryKeyElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + RemoveRegistryActionType? actionType = null; + string key = null; + var name = "-"; + RegistryRootType? root = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Action": + var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (actionValue) + { + case "removeOnInstall": + actionType = RemoveRegistryActionType.RemoveOnInstall; + break; + case "removeOnUninstall": + actionType = RemoveRegistryActionType.RemoveOnUninstall; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "removeOnInstall", "removeOnUninstall")); + break; + } + //if (0 < action.Length) + //{ + // if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType)) + // { + // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall")); + // } + //} + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + if (!actionType.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + Action = actionType.Value, + ComponentRef = componentId, + }); + } + } + + /// + /// Parses a RemoveRegistryValue element. + /// + /// The element to parse. + /// The component identifier of the parent element. + private void ParseRemoveRegistryValueElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string name = null; + RegistryRootType? root = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Root": + root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // generate the identifier if it wasn't provided + if (null == id) + { + id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name)); + } + + if (!root.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root")); + } + + if (null == key) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id) + { + Root = root.Value, + Key = key, + Name = name, + ComponentRef = componentId + }); + } + } + + /// + /// Parses a remove file element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of the parent component's directory. + private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string directoryId = null; + string subdirectory = null; + string name = null; + bool? onInstall = null; + bool? onUninstall = null; + string propertyId = null; + string shortName = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, true); + break; + case "On": + var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (onValue) + { + case "install": + onInstall = true; + break; + case "uninstall": + onUninstall = true; + break; + case "both": + onInstall = true; + onUninstall = true; + break; + } + break; + case "Property": + propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (!onInstall.HasValue && !onUninstall.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); + } + + if (String.IsNullOrEmpty(propertyId)) + { + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); + } + else if (!String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); + } + else if (!String.IsNullOrEmpty(subdirectory)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); + } + + if (null == id) + { + var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; + id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString()); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + FileName = name, + ShortFileName = shortName, + DirPropertyRef = directoryId ?? propertyId ?? parentDirectory, + OnInstall = onInstall, + OnUninstall = onUninstall, + }); + } + } + + /// + /// Parses a RemoveFolder element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of parent component's directory. + private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string directoryId = null; + string subdirectory = null; + bool? onInstall = null; + bool? onUninstall = null; + string propertyId = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "On": + var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (onValue) + { + case "install": + onInstall = true; + break; + case "uninstall": + onUninstall = true; + break; + case "both": + onInstall = true; + onUninstall = true; + break; + } + break; + case "Property": + propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (!onInstall.HasValue && !onUninstall.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On")); + } + + if (String.IsNullOrEmpty(propertyId)) + { + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory"); + } + else if (!String.IsNullOrEmpty(directoryId)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId)); + } + else if (!String.IsNullOrEmpty(subdirectory)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory)); + } + + if (null == id) + { + var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0; + id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId, on.ToString()); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + DirPropertyRef = directoryId ?? propertyId, + OnInstall = onInstall, + OnUninstall = onUninstall + }); + } + } + + /// + /// Parses a reserve cost element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional and default identifier of referenced directory. + private void ParseReserveCostElement(XElement node, string componentId, string directoryId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string subdirectory = null; + var runFromSource = CompilerConstants.IntegerNotSet; + var runLocal = CompilerConstants.IntegerNotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "RunFromSource": + runFromSource = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "RunLocal": + runLocal = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + + if (null == id) + { + id = this.Core.CreateIdentifier("rc", componentId, directoryId); + } + + if (CompilerConstants.IntegerNotSet == runFromSource) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource")); + } + + if (CompilerConstants.IntegerNotSet == runLocal) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ReserveCostSymbol(sourceLineNumbers, id) + { + ComponentRef = componentId, + ReserveFolder = directoryId, + ReserveLocal = runLocal, + ReserveSource = runFromSource + }); + } + } + + /// + /// Parses a sequence element. + /// + /// Element to parse. + /// Name of sequence table. + private void ParseSequenceElement(XElement node, SequenceTable sequenceTable) + { + // Parse each action in the sequence. + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + var actionName = child.Name.LocalName; + string afterAction = null; + string beforeAction = null; + string condition = null; + var customAction = "Custom" == actionName; + var overridable = false; + var exitSequence = CompilerConstants.IntegerNotSet; + var sequence = CompilerConstants.IntegerNotSet; + var showDialog = "Show" == actionName; + var specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName; + var specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName; + var suppress = false; + + foreach (var attrib in child.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + if (customAction) + { + actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.CustomAction, actionName); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "After": + if (customAction || showDialog || specialAction || specialStandardAction) + { + afterAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), afterAction); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Before": + if (customAction || showDialog || specialAction || specialStandardAction) + { + beforeAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), beforeAction); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Condition": + condition = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Dialog": + if (showDialog) + { + actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib); + this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.Dialog, actionName); + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "OnExit": + if (customAction || showDialog || specialAction) + { + var exitValue = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + switch (exitValue) + { + case "success": + exitSequence = -1; + break; + case "cancel": + exitSequence = -2; + break; + case "error": + exitSequence = -3; + break; + case "suspend": + exitSequence = -4; + break; + } + } + else + { + this.Core.UnexpectedAttribute(child, attrib); + } + break; + case "Overridable": + overridable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); + break; + case "Sequence": + sequence = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "Suppress": + suppress = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (customAction && "Custom" == actionName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action")); + } + else if (showDialog && "Show" == actionName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog")); + } + + if (CompilerConstants.IntegerNotSet != sequence) + { + if (CompilerConstants.IntegerNotSet != exitSequence) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit")); + } + else if (null != beforeAction || null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After")); + } + } + else // sequence not specified use OnExit (which may also be not set). + { + sequence = exitSequence; + } + + if (null != beforeAction && null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before")); + } + else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction) + { + this.Core.Write(ErrorMessages.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName)); + } + + // action that is scheduled to occur before/after itself + if (beforeAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction)); + } + else if (afterAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction)); + } + + // normal standard actions cannot be set overridable by the user (since they are overridable by default) + if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction) + { + this.Core.Write(ErrorMessages.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable")); + } + + // suppress cannot be specified at the same time as Before, After, or Sequence + if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable")); + } + + this.Core.ParseForExtensionElements(child); + + // add the row and any references needed + if (!this.Core.EncounteredError) + { + if (suppress) + { + this.Core.AddSymbol(new WixSuppressActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName)) + { + SequenceTable = sequenceTable, + Action = actionName + }); + } + else + { + var symbol = this.Core.AddSymbol(new WixActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName)) + { + SequenceTable = sequenceTable, + Action = actionName, + Condition = condition, + Before = beforeAction, + After = afterAction, + Overridable = overridable, + }); + + if (CompilerConstants.IntegerNotSet != sequence) + { + symbol.Sequence = sequence; + } + } + } + } + + this.Core.InnerTextDisallowed(node); + } + + + /// + /// Parses a service config element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional element containing parent's service name. + private void ParseServiceConfigElement(XElement node, string componentId, string serviceName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string delayedAutoStart = null; + string failureActionsWhen = null; + var name = serviceName; + var install = false; + var reinstall = false; + var uninstall = false; + string preShutdownDelay = null; + string requiredPrivileges = null; + string sid = null; + + this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "DelayedAutoStart": + delayedAutoStart = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (delayedAutoStart) + { + case "no": + delayedAutoStart = "0"; + break; + case "yes": + delayedAutoStart = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "FailureActionsWhen": + failureActionsWhen = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (failureActionsWhen) + { + case "failedToStop": + failureActionsWhen = "0"; + break; + case "failedToStopOrReturnedError": + failureActionsWhen = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "OnInstall": + install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == install) + //{ + // events |= MsiInterop.MsidbServiceConfigEventInstall; + //} + break; + case "OnReinstall": + reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == reinstall) + //{ + // events |= MsiInterop.MsidbServiceConfigEventReinstall; + //} + break; + case "OnUninstall": + uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + //if (YesNoType.Yes == uninstall) + //{ + // events |= MsiInterop.MsidbServiceConfigEventUninstall; + //} + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + case "PreShutdownDelay": + preShutdownDelay = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "ServiceName": + if (!String.IsNullOrEmpty(serviceName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); + } + + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ServiceSid": + sid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sid) + { + case "none": + sid = "0"; + break; + case "restricted": + sid = "3"; + break; + case "unrestricted": + sid = "1"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Get the ServiceConfig required privilegs. + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "RequiredPrivilege": + requiredPrivileges = this.ParseRequiredPrivilege(child, requiredPrivileges); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (!install && !reinstall && !uninstall) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); + } + + if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid)) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege")); + } + + if (!this.Core.EncounteredError) + { + if (!String.IsNullOrEmpty(delayedAutoStart)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".DS"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.DelayedAutoStart, + Argument = delayedAutoStart, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(failureActionsWhen)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".FA"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.FailureActionsFlag, + Argument = failureActionsWhen, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(sid)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".SS"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.ServiceSidInfo, + Argument = sid, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(requiredPrivileges)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".RP"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.RequiredPrivilegesInfo, + Argument = requiredPrivileges, + ComponentRef = componentId, + }); + } + + if (!String.IsNullOrEmpty(preShutdownDelay)) + { + this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".PD"))) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ConfigType = MsiServiceConfigType.PreshutdownInfo, + Argument = preShutdownDelay, + ComponentRef = componentId, + }); + } + } + } + + private string ParseRequiredPrivilege(XElement node, string requiredPrivileges) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string privilege = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + privilege = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (privilege) + { + case "assignPrimaryToken": + privilege = "SeAssignPrimaryTokenPrivilege"; + break; + case "audit": + privilege = "SeAuditPrivilege"; + break; + case "backup": + privilege = "SeBackupPrivilege"; + break; + case "changeNotify": + privilege = "SeChangeNotifyPrivilege"; + break; + case "createGlobal": + privilege = "SeCreateGlobalPrivilege"; + break; + case "createPagefile": + privilege = "SeCreatePagefilePrivilege"; + break; + case "createPermanent": + privilege = "SeCreatePermanentPrivilege"; + break; + case "createSymbolicLink": + privilege = "SeCreateSymbolicLinkPrivilege"; + break; + case "createToken": + privilege = "SeCreateTokenPrivilege"; + break; + case "debug": + privilege = "SeDebugPrivilege"; + break; + case "enableDelegation": + privilege = "SeEnableDelegationPrivilege"; + break; + case "impersonate": + privilege = "SeImpersonatePrivilege"; + break; + case "increaseBasePriority": + privilege = "SeIncreaseBasePriorityPrivilege"; + break; + case "increaseQuota": + privilege = "SeIncreaseQuotaPrivilege"; + break; + case "increaseWorkingSet": + privilege = "SeIncreaseWorkingSetPrivilege"; + break; + case "loadDriver": + privilege = "SeLoadDriverPrivilege"; + break; + case "lockMemory": + privilege = "SeLockMemoryPrivilege"; + break; + case "machineAccount": + privilege = "SeMachineAccountPrivilege"; + break; + case "manageVolume": + privilege = "SeManageVolumePrivilege"; + break; + case "profileSingleProcess": + privilege = "SeProfileSingleProcessPrivilege"; + break; + case "relabel": + privilege = "SeRelabelPrivilege"; + break; + case "remoteShutdown": + privilege = "SeRemoteShutdownPrivilege"; + break; + case "restore": + privilege = "SeRestorePrivilege"; + break; + case "security": + privilege = "SeSecurityPrivilege"; + break; + case "shutdown": + privilege = "SeShutdownPrivilege"; + break; + case "syncAgent": + privilege = "SeSyncAgentPrivilege"; + break; + case "systemEnvironment": + privilege = "SeSystemEnvironmentPrivilege"; + break; + case "systemProfile": + privilege = "SeSystemProfilePrivilege"; + break; + case "systemTime": + case "modifySystemTime": + privilege = "SeSystemtimePrivilege"; + break; + case "takeOwnership": + privilege = "SeTakeOwnershipPrivilege"; + break; + case "tcb": + case "trustedComputerBase": + privilege = "SeTcbPrivilege"; + break; + case "timeZone": + case "modifyTimeZone": + privilege = "SeTimeZonePrivilege"; + break; + case "trustedCredManAccess": + case "trustedCredentialManagerAccess": + privilege = "SeTrustedCredManAccessPrivilege"; + break; + case "undock": + privilege = "SeUndockPrivilege"; + break; + case "unsolicitedInput": + privilege = "SeUnsolicitedInputPrivilege"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + if (privilege == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + this.Core.ParseForExtensionElements(node); + + return (requiredPrivileges == null) ? privilege : String.Concat(requiredPrivileges, "[~]", privilege); + } + + /// + /// Parses a service config failure actions element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Optional element containing parent's service name. + private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var name = serviceName; + var install = false; + var reinstall = false; + var uninstall = false; + int? resetPeriod = null; + string rebootMessage = null; + string command = null; + string actions = null; + string actionsDelays = null; + + this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName)); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Command": + command = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "OnInstall": + install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnReinstall": + reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnUninstall": + uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RebootMessage": + rebootMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + case "ResetPeriod": + resetPeriod = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "ServiceName": + if (!String.IsNullOrEmpty(serviceName)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall")); + } + + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + // Get the ServiceConfigFailureActions actions. + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Failure": + string action = null; + string delay = null; + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + + foreach (var childAttrib in child.Attributes()) + { + if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace) + { + switch (childAttrib.Name.LocalName) + { + case "Action": + action = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (action) + { + case "none": + action = "0"; + break; + case "restartComputer": + action = "2"; + break; + case "restartService": + action = "1"; + break; + case "runCommand": + action = "3"; + break; + default: + // allow everything else to pass through that are hopefully "formatted" Properties. + break; + } + break; + case "Delay": + delay = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + break; + default: + this.Core.UnexpectedAttribute(child, childAttrib); + break; + } + } + } + + if (String.IsNullOrEmpty(action)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action")); + } + + if (String.IsNullOrEmpty(delay)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay")); + } + + if (!String.IsNullOrEmpty(actions)) + { + actions = String.Concat(actions, "[~]"); + } + actions = String.Concat(actions, action); + + if (!String.IsNullOrEmpty(actionsDelays)) + { + actionsDelays = String.Concat(actionsDelays, "[~]"); + } + actionsDelays = String.Concat(actionsDelays, delay); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (!install && !reinstall && !uninstall) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiServiceConfigFailureActionsSymbol(sourceLineNumbers, id) + { + Name = name, + OnInstall = install, + OnReinstall = reinstall, + OnUninstall = uninstall, + ResetPeriod = resetPeriod, + RebootMessage = rebootMessage, + Command = command, + Actions = actions, + DelayActions = actionsDelays, + ComponentRef = componentId, + }); + } + } + + /// + /// Parses a service control element. + /// + /// Element to parse. + /// Identifier of parent component. + private void ParseServiceControlElement(XElement node, string componentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string arguments = null; + Identifier id = null; + string name = null; + var installRemove = false; + var uninstallRemove = false; + var installStart = false; + var uninstallStart = false; + var installStop = false; + var uninstallStop = false; + bool? wait = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Remove": + var removeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (removeValue) + { + case "install": + installRemove = true; + break; + case "uninstall": + uninstallRemove = true; + break; + case "both": + installRemove = true; + uninstallRemove = true; + break; + case "": + break; + } + break; + case "Start": + var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (startValue) + { + case "install": + installStart = true; + break; + case "uninstall": + uninstallStart = true; + break; + case "both": + installStart = true; + uninstallStart = true; + break; + case "": + break; + } + break; + case "Stop": + var stopValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (stopValue) + { + case "install": + installStop = true; + break; + case "uninstall": + uninstallStop = true; + break; + case "both": + installStop = true; + uninstallStop = true; + break; + case "": + break; + } + break; + case "Wait": + wait = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + // get the ServiceControl arguments + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ServiceArgument": + arguments = this.ParseServiceArgument(child, arguments); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ServiceControlSymbol(sourceLineNumbers, id) + { + Name = name, + InstallRemove = installRemove, + UninstallRemove = uninstallRemove, + InstallStart = installStart, + UninstallStart = uninstallStart, + InstallStop = installStop, + UninstallStop = uninstallStop, + Arguments = arguments, + Wait = wait, + ComponentRef = componentId + }); + } + } + + private string ParseServiceArgument(XElement node, string arguments) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string argument = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Value": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + } + + if (argument == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + return (arguments == null) ? argument : String.Concat(arguments, "[~]", argument); + } + + /// + /// Parses a service dependency element. + /// + /// Element to parse. + /// Parsed sevice dependency name. + private string ParseServiceDependencyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string dependency = null; + var group = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Group": + group = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + return group ? String.Concat("+", dependency) : dependency; + } + + /// + /// Parses a service install element. + /// + /// Element to parse. + /// Identifier of parent component. + /// + private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string account = null; + string arguments = null; + string dependencies = null; + string description = null; + string displayName = null; + var eraseDescription = false; + string loadOrderGroup = null; + string name = null; + string password = null; + + var serviceType = ServiceType.OwnProcess; + var startType = ServiceStartType.Demand; + var errorControl = ServiceErrorControl.Normal; + var interactive = false; + var vital = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Account": + account = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Arguments": + arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "EraseDescription": + eraseDescription = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ErrorControl": + var errorControlValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (errorControlValue) + { + case "ignore": + errorControl = ServiceErrorControl.Ignore; + break; + case "normal": + errorControl = ServiceErrorControl.Normal; + break; + case "critical": + errorControl = ServiceErrorControl.Critical; + break; + case "": // error case handled by GetAttributeValue() + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical")); + break; + } + break; + case "Interactive": + interactive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "LoadOrderGroup": + loadOrderGroup = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Password": + password = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Start": + var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (startValue) + { + case "auto": + startType = ServiceStartType.Auto; + break; + case "demand": + startType = ServiceStartType.Demand; + break; + case "disabled": + startType = ServiceStartType.Disabled; + break; + case "boot": + case "system": + this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue)); + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled")); + break; + } + break; + case "Type": + var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (typeValue) + { + case "ownProcess": + serviceType = ServiceType.OwnProcess; + break; + case "shareProcess": + serviceType = ServiceType.ShareProcess; + break; + case "kernelDriver": + case "systemDriver": + this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue)); + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess")); + break; + } + break; + case "Vital": + vital = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else if (null == id) + { + id = this.Core.CreateIdentifierFromFilename(name); + } + + if (0 == startType) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start")); + } + + if (eraseDescription) + { + description = "[~]"; + } + + // get the ServiceInstall dependencies and config + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "ServiceInstall"); + break; + case "ServiceConfig": + this.ParseServiceConfigElement(child, componentId, name); + break; + case "ServiceConfigFailureActions": + this.ParseServiceConfigFailureActionsElement(child, componentId, name); + break; + case "ServiceDependency": + dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "ServiceInstallId", id?.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + if (null != dependencies) + { + dependencies = String.Concat(dependencies, "[~]"); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ServiceInstallSymbol(sourceLineNumbers, id) + { + Name = name, + DisplayName = displayName, + ServiceType = serviceType, + StartType = startType, + ErrorControl = errorControl, + LoadOrderGroup = loadOrderGroup, + Dependencies = dependencies, + StartName = account, + Password = password, + Arguments = arguments, + ComponentRef = componentId, + Description = description, + Interactive = interactive, + Vital = vital + }); + } + } + + /// + /// Parses a SetDirectory element. + /// + /// Element to parse. + private void ParseSetDirectoryElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string actionName = null; + string id = null; + string condition = null; + var executionType = CustomActionExecutionType.Immediate; + var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id); + break; + case "Sequence": + var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sequenceValue) + { + case "execute": + sequences = new[] { SequenceTable.InstallExecuteSequence }; + break; + case "first": + executionType = CustomActionExecutionType.FirstSequence; + break; + case "ui": + sequences = new[] { SequenceTable.InstallUISequence }; + break; + case "both": + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (String.IsNullOrEmpty(actionName)) + { + actionName = String.Concat("Set", id); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName)) + { + ExecutionType = executionType, + SourceType = CustomActionSourceType.Directory, + TargetType = CustomActionTargetType.TextData, + Source = id, + Target = value + }); + + foreach (var sequence in sequences) + { + this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, afterAction: "CostInitialize"); + } + } + } + + /// + /// Parses a SetProperty element. + /// + /// Element to parse. + private void ParseSetPropertyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string actionName = null; + string id = null; + string condition = null; + string afterAction = null; + string beforeAction = null; + var executionType = CustomActionExecutionType.Immediate; + var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both" + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + afterAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Before": + beforeAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Sequence": + var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (sequenceValue) + { + case "execute": + sequences = new[] { SequenceTable.InstallExecuteSequence }; + break; + case "first": + executionType = CustomActionExecutionType.FirstSequence; + break; + case "ui": + sequences = new[] { SequenceTable.InstallUISequence }; + break; + case "both": + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both")); + break; + } + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + else if (String.IsNullOrEmpty(actionName)) + { + actionName = String.Concat("Set", id); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + if (null != beforeAction && null != afterAction) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before")); + } + else if (null == beforeAction && null == afterAction) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id")); + } + + this.Core.ParseForExtensionElements(node); + + // add the row and any references needed + if (!this.Core.EncounteredError) + { + // action that is scheduled to occur before/after itself + if (beforeAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction)); + } + else if (afterAction == actionName) + { + this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction)); + } + + this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName)) + { + ExecutionType = executionType, + SourceType = CustomActionSourceType.Property, + TargetType = CustomActionTargetType.TextData, + Source = id, + Target = value, + }); + + foreach (var sequence in sequences) + { + this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, beforeAction, afterAction); + } + } + } + + /// + /// Parses a SFP catalog element. + /// + /// Element to parse. + /// Parent SFPCatalog. + private void ParseSFPFileElement(XElement node, string parentSFPCatalog) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new FileSFPCatalogSymbol(sourceLineNumbers) + { + FileRef = id, + SFPCatalogRef = parentSFPCatalog + }); + } + } + + /// + /// Parses a SFP catalog element. + /// + /// Element to parse. + /// Parent SFPCatalog. + private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string parentName = null; + string dependency = null; + string name = null; + string sourceFile = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Dependency": + dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + parentSFPCatalog = name; + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "SFPCatalog": + this.ParseSFPCatalogElement(child, ref parentName); + if (null != dependency && parentName == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); + } + dependency = parentName; + break; + case "SFPFile": + this.ParseSFPFileElement(child, name); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == dependency) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new SFPCatalogSymbol(sourceLineNumbers) + { + SFPCatalog = name, + Catalog = sourceFile, + Dependency = dependency + }); + } + } + + /// + /// Parses a shortcut element. + /// + /// Element to parse. + /// Identifer for parent component. + /// Local name of parent element. + /// Default identifier of parent (which is usually the target). + /// Flag to indicate whether the parent element is the keypath of a component or not (will only be true for file parent elements). + private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var advertise = false; + string arguments = null; + string description = null; + string descriptionResourceDll = null; + int? descriptionResourceId = null; + string directoryId = null; + string subdirectory = null; + string displayResourceDll = null; + int? displayResourceId = null; + int? hotkey = null; + string icon = null; + int? iconIndex = null; + string name = null; + string shortName = null; + ShortcutShowType? show = null; + string target = null; + string workingDirectoryId = null; + string workingSubdirectory = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Advertise": + advertise = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Arguments": + arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DescriptionResourceDll": + descriptionResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DescriptionResourceId": + descriptionResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "DisplayResourceDll": + displayResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayResourceId": + displayResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Hotkey": + hotkey = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Icon": + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon); + break; + case "IconIndex": + iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "ShortName": + shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false); + break; + case "Show": + var showValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (showValue) + { + case "normal": + show = ShortcutShowType.Normal; + break; + case "maximized": + show = ShortcutShowType.Maximized; + break; + case "minimized": + show = ShortcutShowType.Minimized; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized")); + break; + } + break; + case "Target": + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "WorkingDirectory": + workingDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, workingDirectoryId); + break; + case "WorkingSubdirectory": + workingSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (advertise && null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes")); + } + + if (null == directoryId) + { + if ("Component" == parentElementLocalName) + { + directoryId = defaultTarget; + } + else + { + this.Core.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component")); + } + } + + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + + if (null != descriptionResourceDll) + { + if (!descriptionResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId")); + } + } + else + { + if (descriptionResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll")); + } + } + + if (null != displayResourceDll) + { + if (!displayResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId")); + } + } + else + { + if (displayResourceId.HasValue) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll")); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + + workingDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, workingDirectoryId, workingSubdirectory, "WorkingDirectory", "WorkingSubdirectory"); + + if ("Component" != parentElementLocalName && null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName)); + } + + if (null == id) + { + id = this.Core.CreateIdentifier("sct", directoryId, LowercaseOrNull(name)); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Icon": + icon = this.ParseIconElement(child); + break; + case "ShortcutProperty": + this.ParseShortcutPropertyElement(child, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (advertise) + { + if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName) + { + this.Core.Write(WarningMessages.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget)); + } + + target = Guid.Empty.ToString("B"); + } + else if (null != target) + { + } + else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName) + { + target = "[" + defaultTarget + "]"; + } + else if ("File" == parentElementLocalName) + { + target = "[#" + defaultTarget + "]"; + } + + this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id) + { + DirectoryRef = directoryId, + Name = name, + ShortName = shortName, + ComponentRef = componentId, + Target = target, + Arguments = arguments, + Description = description, + Hotkey = hotkey, + IconRef = icon, + IconIndex = iconIndex, + Show = show, + WorkingDirectory = workingDirectoryId, + DisplayResourceDll = displayResourceDll, + DisplayResourceId = displayResourceId, + DescriptionResourceDll = descriptionResourceDll, + DescriptionResourceId = descriptionResourceId, + }); + } + } + + /// + /// Parses a shortcut property element. + /// + /// Element to parse. + /// + private void ParseShortcutPropertyElement(XElement node, string shortcutId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string key = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Key": + key = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (String.IsNullOrEmpty(key)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key")); + } + else if (null == id) + { + id = this.Core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant()); + } + + if (String.IsNullOrEmpty(value)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiShortcutPropertySymbol(sourceLineNumbers, id) + { + ShortcutRef = shortcutId, + PropertyKey = key, + PropVariantValue = value + }); + } + } + + /// + /// Parses a typelib element. + /// + /// Element to parse. + /// Identifier of parent component. + /// Identifier of file that acts as typelib server. + /// true if the component is 64-bit. + private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + var advertise = YesNoType.NotSet; + var cost = CompilerConstants.IntegerNotSet; + string description = null; + var flags = 0; + string helpDirectoryId = null; + string helpSubdirectory = null; + var language = CompilerConstants.IntegerNotSet; + var majorVersion = CompilerConstants.IntegerNotSet; + var minorVersion = CompilerConstants.IntegerNotSet; + var resourceId = CompilerConstants.LongNotSet; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Advertise": + advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Control": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 2; + } + break; + case "Cost": + cost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HasDiskImage": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 8; + } + break; + case "HelpDirectory": + helpDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, helpDirectoryId); + break; + case "HelpSubdirectory": + helpSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + case "Hidden": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 4; + } + break; + case "Language": + language = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "MajorVersion": + majorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, UInt16.MaxValue); + break; + case "MinorVersion": + minorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); + break; + case "ResourceId": + resourceId = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, Int32.MinValue, Int32.MaxValue); + break; + case "Restricted": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + flags |= 1; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (CompilerConstants.IntegerNotSet == language) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language")); + language = CompilerConstants.IllegalInteger; + } + + helpDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, helpDirectoryId, helpSubdirectory, "HelpDirectory", "HelpSubdirectory"); + + // build up the typelib version string for the registry if the major or minor version was specified + string registryVersion = null; + if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) + { + if (CompilerConstants.IntegerNotSet != majorVersion) + { + registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat); + } + else + { + registryVersion = "0"; + } + + if (CompilerConstants.IntegerNotSet != minorVersion) + { + registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat)); + } + else + { + registryVersion = String.Concat(registryVersion, ".0"); + } + } + + // if the advertise state has not been set, default to non-advertised + if (YesNoType.NotSet == advertise) + { + advertise = YesNoType.No; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AppId": + this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion); + break; + case "Class": + this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null); + break; + case "Interface": + this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (YesNoType.Yes == advertise) + { + if (CompilerConstants.LongNotSet != resourceId) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId")); + } + + if (0 != flags) + { + if (0x1 == (flags & 0x1)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes")); + } + + if (0x2 == (flags & 0x2)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes")); + } + + if (0x4 == (flags & 0x4)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes")); + } + + if (0x8 == (flags & 0x8)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes")); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new TypeLibSymbol(sourceLineNumbers) + { + LibId = id, + Language = language, + ComponentRef = componentId, + Description = description, + DirectoryRef = helpDirectoryId, + FeatureRef = Guid.Empty.ToString("B") + }); + + if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion) + { + symbol.Version = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0); + } + + if (CompilerConstants.IntegerNotSet != cost) + { + symbol.Cost = cost; + } + } + } + else if (YesNoType.No == advertise) + { + if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no")); + } + + if (null == fileServer) + { + this.Core.Write(ErrorMessages.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File")); + } + + if (null == registryVersion) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no")); + } + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId); + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId] + var path = String.Concat("[#", fileServer, "]"); + if (CompilerConstants.LongNotSet != resourceId) + { + path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat)); + } + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId); + + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId); + + if (null != helpDirectoryId) + { + // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory] + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectoryId, "]"), componentId); + } + } + } + + /// + /// Parses an upgrade element. + /// + /// Element to parse. + private void ParseUpgradeElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + // process the UpgradeVersion children here + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + + switch (child.Name.LocalName) + { + case "Property": + this.ParsePropertyElement(child); + this.Core.Write(WarningMessages.DeprecatedUpgradeProperty(childSourceLineNumbers)); + break; + case "UpgradeVersion": + this.ParseUpgradeVersionElement(child, id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + // No rows created here. All row creation is done in ParseUpgradeVersionElement. + } + + /// + /// Parse upgrade version element. + /// + /// Element to parse. + /// Upgrade code. + private void ParseUpgradeVersionElement(XElement node, string upgradeId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + + string actionProperty = null; + string language = null; + string maximum = null; + string minimum = null; + var excludeLanguages = false; + var ignoreFailures = false; + var includeMax = false; + var includeMin = true; + var migrateFeatures = false; + var onlyDetect = false; + string removeFeatures = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "ExcludeLanguages": + excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IgnoreRemoveFailure": + ignoreFailures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMaximum": + includeMax = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "IncludeMinimum": // this is "yes" by default + includeMin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Language": + language = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Minimum": + minimum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Maximum": + maximum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "MigrateFeatures": + migrateFeatures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "OnlyDetect": + onlyDetect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Property": + actionProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "RemoveFeatures": + removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == actionProperty) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty) + { + this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty)); + } + + if (null == minimum && null == maximum) + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers) + { + UpgradeCode = upgradeId, + VersionMin = minimum, + VersionMax = maximum, + Language = language, + ExcludeLanguages = excludeLanguages, + IgnoreRemoveFailures = ignoreFailures, + VersionMaxInclusive = includeMax, + VersionMinInclusive = includeMin, + MigrateFeatures = migrateFeatures, + OnlyDetect = onlyDetect, + Remove = removeFeatures, + ActionProperty = actionProperty + }); + + // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence + // if at least one row in Upgrade table lacks the OnlyDetect attribute. + if (!onlyDetect) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", "RemoveExistingProducts"); + } + } + } + + /// + /// Parses a verb element. + /// + /// Element to parse. + /// Extension verb is releated to. + /// Optional progId for extension. + /// Identifier for parent component. + /// Flag if verb is advertised. + private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + string argument = null; + string command = null; + var sequence = CompilerConstants.IntegerNotSet; + string targetFile = null; + string targetProperty = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Argument": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Command": + command = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Sequence": + sequence = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "TargetFile": + targetFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, targetFile); + break; + case "TargetProperty": + targetProperty = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null != targetFile && null != targetProperty) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty")); + } + + this.Core.ParseForExtensionElements(node); + + if (YesNoType.Yes == advertise) + { + if (null != targetFile) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile")); + } + + if (null != targetProperty) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty")); + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new VerbSymbol(sourceLineNumbers) + { + ExtensionRef = extension, + Verb = id, + Command = command, + Argument = argument, + }); + + if (CompilerConstants.IntegerNotSet != sequence) + { + symbol.Sequence = sequence; + } + } + } + else if (YesNoType.No == advertise) + { + if (CompilerConstants.IntegerNotSet != sequence) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no")); + } + + if (null == targetFile && null == targetProperty) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no")); + } + + string target = null; + if (null != targetFile) + { + target = String.Concat("\"[#", targetFile, "]\""); + } + else if (null != targetProperty) + { + target = String.Concat("\"[", targetProperty, "]\""); + } + + if (null != argument) + { + target = String.Concat(target, " ", argument); + } + + var prefix = progId ?? String.Concat(".", extension); + + if (null != command) + { + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId); + } + + this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId); + } + } + + /// + /// Parses a WixVariable element. + /// + /// Element to parse. + private void ParseWixVariableElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var overridable = false; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Overridable": + overridable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixVariableSymbol(sourceLineNumbers, id) + { + Value = value, + Overridable = overridable + }); + } + } + + private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var compressionLevel = this.Core.GetAttributeValue(sourceLineNumbers, attribute); + switch (compressionLevel) + { + case "high": + return CompressionLevel.High; + case "low": + return CompressionLevel.Low; + case "medium": + return CompressionLevel.Medium; + case "mszip": + return CompressionLevel.Mszip; + case "none": + return CompressionLevel.None; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalCompressionLevel(sourceLineNumbers, compressionLevel)); + break; + } + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Patch.cs b/src/wix/WixToolset.Core/Compiler_Patch.cs new file mode 100644 index 00000000..c9cae183 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Patch.cs @@ -0,0 +1,657 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses an patch element. + /// + /// The element to parse. + private void ParsePatchElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string patchId = null; + string codepage = null; + ////bool versionMismatches = false; + ////bool productMismatches = false; + var allowRemoval = false; + string classification = null; + string clientPatchId = null; + string description = null; + string displayName = null; + string comments = null; + string manufacturer = null; + var minorUpdateTargetRTM = YesNoType.NotSet; + string moreInfoUrl = null; + var optimizeCA = CompilerConstants.IntegerNotSet; + var optimizedInstallMode = YesNoType.NotSet; + string targetProductName = null; + // string replaceGuids = String.Empty; + var apiPatchingSymbolFlags = 0; + var optimizePatchSizeForLargeFiles = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + patchId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true); + break; + case "Codepage": + codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib); + break; + case "AllowMajorVersionMismatches": + ////versionMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "AllowProductCodeMismatches": + ////productMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "AllowRemoval": + allowRemoval = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + case "Classification": + classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ClientPatchId": + clientPatchId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Comments": + comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Manufacturer": + manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MinorUpdateTargetRTM": + minorUpdateTargetRTM = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "MoreInfoURL": + moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "OptimizedInstallMode": + optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "TargetProductName": + targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ApiPatchingSymbolNoImagehlpFlag": + apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoImagehlp : 0; + break; + case "ApiPatchingSymbolNoFailuresFlag": + apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoFailures : 0; + break; + case "ApiPatchingSymbolUndecoratedTooFlag": + apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolUndecoratedToo : 0; + break; + case "OptimizePatchSizeForLargeFiles": + optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (patchId == null || patchId == "*") + { + // auto-generate at compile time, since this value gets dispersed to several locations + patchId = Common.GenerateGuid(); + } + this.activeName = patchId; + + if (null == this.activeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + if (null == classification) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); + } + if (null == clientPatchId) + { + clientPatchId = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture)); + } + if (null == description) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); + } + if (null == displayName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); + } + if (null == manufacturer) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer")); + } + + this.Core.CreateActiveSection(this.activeName, SectionType.Patch, this.Context.CompilationId); + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "PatchInformation": + this.ParsePatchInformationElement(child); + break; + case "Media": + this.ParseMediaElement(child, patchId); + break; + case "OptimizeCustomActions": + optimizeCA = this.ParseOptimizeCustomActionsElement(child); + break; + case "PatchFamily": + this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Patch, patchId); + break; + case "PatchFamilyRef": + this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.Patch, patchId); + break; + case "PatchFamilyGroup": + this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Patch, patchId); + break; + case "PatchFamilyGroupRef": + this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Patch, patchId); + break; + case "PatchProperty": + this.ParsePatchPropertyElement(child, true); + break; + case "TargetProductCodes": + this.ParseTargetProductCodesElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPatchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, patchId)) + { + Codepage = codepage, + ClientPatchId = clientPatchId, + OptimizePatchSizeForLargeFiles = optimizePatchSizeForLargeFiles, + ApiPatchingSymbolFlags = apiPatchingSymbolFlags, + }); + + if (allowRemoval) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "AllowRemoval", allowRemoval ? "1" : "0"); + } + + if (null != classification) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "Classification", classification); + } + + // always generate the CreationTimeUTC + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", DateTime.UtcNow.ToString("MM-dd-yy HH:mm", CultureInfo.InvariantCulture)); + } + + if (null != description) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "Description", description); + } + + if (null != displayName) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); + } + + if (null != manufacturer) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturer); + } + + if (YesNoType.NotSet != minorUpdateTargetRTM) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", YesNoType.Yes == minorUpdateTargetRTM ? "1" : "0"); + } + + if (null != moreInfoUrl) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); + } + + if (CompilerConstants.IntegerNotSet != optimizeCA) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); + } + + if (YesNoType.NotSet != optimizedInstallMode) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); + } + + if (null != targetProductName) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); + } + + if (null != comments) + { + this.AddMsiPatchMetadata(sourceLineNumbers, null, "Comments", comments); + } + } + // TODO: do something with versionMismatches and productMismatches + } + + /// + /// Parses the OptimizeCustomActions element. + /// + /// Element to parse. + /// The combined integer value for callers to store as appropriate. + private int ParseOptimizeCustomActionsElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var optimizeCA = OptimizeCAFlags.None; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "SkipAssignment": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + optimizeCA |= OptimizeCAFlags.SkipAssignment; + } + break; + case "SkipImmediate": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + optimizeCA |= OptimizeCAFlags.SkipImmediate; + } + break; + case "SkipDeferred": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + optimizeCA |= OptimizeCAFlags.SkipDeferred; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + return (int)optimizeCA; + } + + /// + /// Parses a PatchFamily element. + /// + /// The element to parse. + /// + /// + private void ParsePatchFamilyElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string productCode = null; + string version = null; + var attributes = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "ProductCode": + productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Version": + version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Supersede": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= 0x1; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + if (String.IsNullOrEmpty(version)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version")); + } + else if (!CompilerCore.IsValidProductVersion(version)) + { + this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version)); + } + + // find unexpected child elements + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "All": + this.ParseAllElement(child); + break; + case "BinaryRef": + this.ParsePatchChildRefElement(child, "Binary"); + break; + case "ComponentRef": + this.ParsePatchChildRefElement(child, "Component"); + break; + case "CustomActionRef": + this.ParsePatchChildRefElement(child, "CustomAction"); + break; + case "DirectoryRef": + this.ParsePatchChildRefElement(child, "Directory"); + break; + case "DigitalCertificateRef": + this.ParsePatchChildRefElement(child, "MsiDigitalCertificate"); + break; + case "FeatureRef": + this.ParsePatchChildRefElement(child, "Feature"); + break; + case "IconRef": + this.ParsePatchChildRefElement(child, "Icon"); + break; + case "PropertyRef": + this.ParsePatchChildRefElement(child, "Property"); + break; + case "SoftwareTagRef": + this.ParseTagRefElement(child); + break; + case "UIRef": + this.ParsePatchChildRefElement(child, "WixUI"); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new MsiPatchSequenceSymbol(sourceLineNumbers) + { + PatchFamily = id.Id, + ProductCode = productCode, + Sequence = version, + Attributes = attributes + }); + + if (ComplexReferenceParentType.Unknown != parentType) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, id.Id, ComplexReferenceParentType.Patch == parentType); + } + } + } + + /// + /// Parses a PatchFamilyGroup element. + /// + /// Element to parse. + /// + /// + private void ParsePatchFamilyGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "PatchFamily": + this.ParsePatchFamilyElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); + break; + case "PatchFamilyRef": + this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); + break; + case "PatchFamilyGroupRef": + this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixPatchFamilyGroupSymbol(sourceLineNumbers, id)); + + //Add this PatchFamilyGroup and its parent in WixGroup. + this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PatchFamilyGroup, id.Id); + } + } + + /// + /// Parses a PatchFamilyGroup reference element. + /// + /// Element to parse. + /// The type of parent. + /// Identifier of parent element. + private void ParsePatchFamilyGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) + { + Debug.Assert(ComplexReferenceParentType.PatchFamilyGroup == parentType || ComplexReferenceParentType.Patch == parentType); + + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string id = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixPatchFamilyGroup, id); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamilyGroup, id, true); + } + } + + /// + /// Parses a TargetProductCodes element. + /// + /// The element to parse. + private void ParseTargetProductCodesElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var replace = false; + var targetProductCodes = new List(); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Replace": + replace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "TargetProductCode": + var id = this.ParseTargetProductCodeElement(child); + if (0 == String.CompareOrdinal("*", id)) + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWhenNested(sourceLineNumbers, child.Name.LocalName, "Id", id, node.Name.LocalName)); + } + else + { + targetProductCodes.Add(id); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + // By default, target ProductCodes should be added. + if (!replace) + { + this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers) + { + ProductCode = "*" + }); + } + + foreach (var targetProductCode in targetProductCodes) + { + this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers) + { + ProductCode = targetProductCode + }); + } + } + } + + private void AddMsiPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) + { + this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property)) + { + Company = company, + Property = property, + Value = value + }); + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_PatchCreation.cs b/src/wix/WixToolset.Core/Compiler_PatchCreation.cs new file mode 100644 index 00000000..81ae4121 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_PatchCreation.cs @@ -0,0 +1,1265 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a patch creation element. + /// + /// The element to parse. + private void ParsePatchCreationElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var clean = true; // Default is to clean + var codepage = 0; + string outputPath = null; + var productMismatches = false; + var replaceGuids = String.Empty; + string sourceList = null; + string symbolFlags = null; + var targetProducts = String.Empty; + var versionMismatches = false; + var wholeFiles = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + this.activeName = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "AllowMajorVersionMismatches": + versionMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "AllowProductCodeMismatches": + productMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "CleanWorkingFolder": + clean = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Codepage": + codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib); + break; + case "OutputPath": + outputPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SourceList": + sourceList = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SymbolFlags": + symbolFlags = String.Format(CultureInfo.InvariantCulture, "0x{0:x8}", this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, 0, UInt32.MaxValue)); + break; + case "WholeFilesOnly": + wholeFiles = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == this.activeName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + this.Core.CreateActiveSection(this.activeName, SectionType.PatchCreation, this.Context.CompilationId); + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Family": + this.ParseFamilyElement(child); + break; + case "PatchInformation": + this.ParsePatchInformationElement(child); + break; + case "PatchMetadata": + this.ParsePatchMetadataElement(child); + break; + case "PatchProperty": + this.ParsePatchPropertyElement(child, false); + break; + case "PatchSequence": + this.ParsePatchSequenceElement(child); + break; + case "ReplacePatch": + replaceGuids = String.Concat(replaceGuids, this.ParseReplacePatchElement(child)); + break; + case "TargetProductCode": + var targetProduct = this.ParseTargetProductCodeElement(child); + if (0 < targetProducts.Length) + { + targetProducts = String.Concat(targetProducts, ";"); + } + targetProducts = String.Concat(targetProducts, targetProduct); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + this.AddPrivateProperty(sourceLineNumbers, "PatchGUID", this.activeName); + this.AddPrivateProperty(sourceLineNumbers, "AllowProductCodeMismatches", productMismatches ? "1" : "0"); + this.AddPrivateProperty(sourceLineNumbers, "AllowProductVersionMajorMismatches", versionMismatches ? "1" : "0"); + this.AddPrivateProperty(sourceLineNumbers, "DontRemoveTempFolderWhenFinished", clean ? "0" : "1"); + this.AddPrivateProperty(sourceLineNumbers, "IncludeWholeFilesOnly", wholeFiles ? "1" : "0"); + + if (null != symbolFlags) + { + this.AddPrivateProperty(sourceLineNumbers, "ApiPatchingSymbolFlags", symbolFlags); + } + + if (0 < replaceGuids.Length) + { + this.AddPrivateProperty(sourceLineNumbers, "ListOfPatchGUIDsToReplace", replaceGuids); + } + + if (0 < targetProducts.Length) + { + this.AddPrivateProperty(sourceLineNumbers, "ListOfTargetProductCodes", targetProducts); + } + + if (null != outputPath) + { + this.AddPrivateProperty(sourceLineNumbers, "PatchOutputPath", outputPath); + } + + if (null != sourceList) + { + this.AddPrivateProperty(sourceLineNumbers, "PatchSourceList", sourceList); + } + } + + /// + /// Parses a family element. + /// + /// The element to parse. + private void ParseFamilyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var diskId = CompilerConstants.IntegerNotSet; + string diskPrompt = null; + string mediaSrcProp = null; + string name = null; + var sequenceStart = CompilerConstants.IntegerNotSet; + string volumeLabel = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "DiskId": + diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue); + break; + case "DiskPrompt": + diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MediaSrcProp": + mediaSrcProp = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SequenceStart": + sequenceStart = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue); + break; + case "VolumeLabel": + volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == name) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + else if (0 < name.Length) + { + if (8 < name.Length) // check the length + { + this.Core.Write(ErrorMessages.FamilyNameTooLong(sourceLineNumbers, node.Name.LocalName, "Name", name, name.Length)); + } + else // check for illegal characters + { + foreach (var character in name) + { + if (!Char.IsLetterOrDigit(character) && '_' != character) + { + this.Core.Write(ErrorMessages.IllegalFamilyName(sourceLineNumbers, node.Name.LocalName, "Name", name)); + } + } + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "UpgradeImage": + this.ParseUpgradeImageElement(child, name); + break; + case "ExternalFile": + this.ParseExternalFileElement(child, name); + break; + case "ProtectFile": + this.ParseProtectFileElement(child, name); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ImageFamiliesSymbol(sourceLineNumbers) + { + Family = name, + MediaSrcPropName = mediaSrcProp, + DiskPrompt = diskPrompt, + VolumeLabel = volumeLabel + }); + + if (CompilerConstants.IntegerNotSet != diskId) + { + symbol.MediaDiskId = diskId; + } + + if (CompilerConstants.IntegerNotSet != sequenceStart) + { + symbol.FileSequenceStart = sequenceStart; + } + } + } + + /// + /// Parses an upgrade image element. + /// + /// The element to parse. + /// The family for this element. + private void ParseUpgradeImageElement(XElement node, string family) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string sourceFile = null; + string sourcePatch = null; + var symbols = new List(); + string upgrade = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + upgrade = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (13 < upgrade.Length) + { + this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", upgrade, 13)); + } + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SourcePatch": + sourcePatch = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == upgrade) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "SymbolPath": + symbols.Add(this.ParseSymbolPathElement(child)); + break; + case "TargetImage": + this.ParseTargetImageElement(child, upgrade, family); + break; + case "UpgradeFile": + this.ParseUpgradeFileElement(child, upgrade); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new UpgradedImagesSymbol(sourceLineNumbers) + { + Upgraded = upgrade, + MsiPath = sourceFile, + PatchMsiPath = sourcePatch, + SymbolPaths = String.Join(";", symbols), + Family = family + }); + } + } + + /// + /// Parses an upgrade file element. + /// + /// The element to parse. + /// The upgrade key for this element. + private void ParseUpgradeFileElement(XElement node, string upgrade) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var allowIgnoreOnError = false; + string file = null; + var ignore = false; + var symbols = new List(); + var wholeFile = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AllowIgnoreOnError": + allowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "File": + file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Ignore": + ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "WholeFile": + wholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == file) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "SymbolPath": + symbols.Add(this.ParseSymbolPathElement(child)); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (ignore) + { + this.Core.AddSymbol(new UpgradedFilesToIgnoreSymbol(sourceLineNumbers) + { + Upgraded = upgrade, + FTK = file + }); + } + else + { + this.Core.AddSymbol(new UpgradedFilesOptionalDataSymbol(sourceLineNumbers) + { + Upgraded = upgrade, + FTK = file, + SymbolPaths = String.Join(";", symbols), + AllowIgnoreOnPatchError = allowIgnoreOnError, + IncludeWholeFile = wholeFile + }); + } + } + } + + /// + /// Parses a target image element. + /// + /// The element to parse. + /// The upgrade key for this element. + /// The family key for this element. + private void ParseTargetImageElement(XElement node, string upgrade, string family) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var ignore = false; + var order = CompilerConstants.IntegerNotSet; + string sourceFile = null; + string symbols = null; + string target = null; + string validation = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (target.Length > 13) + { + this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", target, 13)); + } + break; + case "IgnoreMissingFiles": + ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Order": + order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); + break; + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Validation": + validation = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == target) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + if (null == sourceFile) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + } + + if (CompilerConstants.IntegerNotSet == order) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "SymbolPath": + if (null != symbols) + { + symbols = String.Concat(symbols, ";", this.ParseSymbolPathElement(child)); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + case "TargetFile": + this.ParseTargetFileElement(child, target, family); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new TargetImagesSymbol(sourceLineNumbers) + { + Target = target, + MsiPath = sourceFile, + SymbolPaths = symbols, + Upgraded = upgrade, + Order = order, + ProductValidateFlags = validation, + IgnoreMissingSrcFiles = ignore + }); + } + } + + /// + /// Parses an upgrade file element. + /// + /// The element to parse. + /// The upgrade key for this element. + /// The family key for this element. + private void ParseTargetFileElement(XElement node, string target, string family) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string file = null; + string ignoreLengths = null; + string ignoreOffsets = null; + string protectLengths = null; + string protectOffsets = null; + string symbols = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == file) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "IgnoreRange": + this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); + break; + case "ProtectRange": + this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); + break; + case "SymbolPath": + symbols = this.ParseSymbolPathElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new TargetFilesOptionalDataSymbol(sourceLineNumbers) + { + Target = target, + FTK = file, + SymbolPaths = symbols, + IgnoreOffsets = ignoreOffsets, + IgnoreLengths = ignoreLengths, + }); + + if (null != protectOffsets) + { + symbol.RetainOffsets = protectOffsets; + + this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) + { + Family = family, + FTK = file, + RetainOffsets = protectOffsets, + RetainLengths = protectLengths, + }); + } + } + } + + /// + /// Parses an external file element. + /// + /// The element to parse. + /// The family for this element. + private void ParseExternalFileElement(XElement node, string family) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string file = null; + string ignoreLengths = null; + string ignoreOffsets = null; + var order = CompilerConstants.IntegerNotSet; + string protectLengths = null; + string protectOffsets = null; + string source = null; + string symbols = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "File": + file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Order": + order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue); + break; + case "Source": + source = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == file) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); + } + + if (null == source) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Source")); + } + + if (CompilerConstants.IntegerNotSet == order) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "IgnoreRange": + this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); + break; + case "ProtectRange": + this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); + break; + case "SymbolPath": + symbols = this.ParseSymbolPathElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new ExternalFilesSymbol(sourceLineNumbers) + { + Family = family, + FTK = file, + FilePath = source, + SymbolPaths = symbols, + IgnoreOffsets = ignoreOffsets, + IgnoreLengths = ignoreLengths, + }); + + if (null != protectOffsets) + { + symbol.RetainOffsets = protectOffsets; + } + + if (CompilerConstants.IntegerNotSet != order) + { + symbol.Order = order; + } + + if (null != protectOffsets) + { + this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) + { + Family = family, + FTK = file, + RetainOffsets = protectOffsets, + RetainLengths = protectLengths, + }); + } + } + } + + /// + /// Parses a protect file element. + /// + /// The element to parse. + /// The family for this element. + private void ParseProtectFileElement(XElement node, string family) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string file = null; + string protectLengths = null; + string protectOffsets = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "File": + file = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == file) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "ProtectRange": + this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == protectOffsets || null == protectLengths) + { + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "ProtectRange")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers) + { + Family = family, + FTK = file, + RetainOffsets = protectOffsets, + RetainLengths = protectLengths + }); + } + } + + /// + /// Parses a range element (ProtectRange, IgnoreRange, etc). + /// + /// The element to parse. + /// Reference to the offsets string. + /// Reference to the lengths string. + private void ParseRangeElement(XElement node, ref string offsets, ref string lengths) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string length = null; + string offset = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Length": + length = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Offset": + offset = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == length) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Length")); + } + + if (null == offset) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset")); + } + + this.Core.ParseForExtensionElements(node); + + if (null != lengths) + { + lengths = String.Concat(lengths, ",", length); + } + else + { + lengths = length; + } + + if (null != offsets) + { + offsets = String.Concat(offsets, ",", offset); + } + else + { + offsets = offset; + } + } + + /// + /// Parses a patch metadata element. + /// + /// Element to parse. + private void ParsePatchMetadataElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var allowRemoval = YesNoType.NotSet; + string classification = null; + string creationTimeUtc = null; + string description = null; + string displayName = null; + string manufacturerName = null; + string minorUpdateTargetRTM = null; + string moreInfoUrl = null; + var optimizeCA = CompilerConstants.IntegerNotSet; + var optimizedInstallMode = YesNoType.NotSet; + string targetProductName = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "AllowRemoval": + allowRemoval = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Classification": + classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "CreationTimeUTC": + creationTimeUtc = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Description": + description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisplayName": + displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ManufacturerName": + manufacturerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MinorUpdateTargetRTM": + minorUpdateTargetRTM = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "MoreInfoURL": + moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "OptimizedInstallMode": + optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "TargetProductName": + targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (YesNoType.NotSet == allowRemoval) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AllowRemoval")); + } + + if (null == classification) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification")); + } + + if (null == description) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description")); + } + + if (null == displayName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName")); + } + + if (null == manufacturerName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ManufacturerName")); + } + + if (null == moreInfoUrl) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "MoreInfoURL")); + } + + if (null == targetProductName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "TargetProductName")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "CustomProperty": + this.ParseCustomPropertyElement(child); + break; + case "OptimizeCustomActions": + optimizeCA = this.ParseOptimizeCustomActionsElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (!this.Core.EncounteredError) + { + if (YesNoType.NotSet != allowRemoval) + { + this.AddPatchMetadata(sourceLineNumbers, null, "AllowRemoval", YesNoType.Yes == allowRemoval ? "1" : "0"); + } + + if (null != classification) + { + this.AddPatchMetadata(sourceLineNumbers, null, "Classification", classification); + } + + if (null != creationTimeUtc) + { + this.AddPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", creationTimeUtc); + } + + if (null != description) + { + this.AddPatchMetadata(sourceLineNumbers, null, "Description", description); + } + + if (null != displayName) + { + this.AddPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName); + } + + if (null != manufacturerName) + { + this.AddPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturerName); + } + + if (null != minorUpdateTargetRTM) + { + this.AddPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", minorUpdateTargetRTM); + } + + if (null != moreInfoUrl) + { + this.AddPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl); + } + + if (CompilerConstants.IntegerNotSet != optimizeCA) + { + this.AddPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture)); + } + + if (YesNoType.NotSet != optimizedInstallMode) + { + this.AddPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0"); + } + + if (null != targetProductName) + { + this.AddPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName); + } + } + } + + /// + /// Parses a custom property element for the PatchMetadata table. + /// + /// Element to parse. + private void ParseCustomPropertyElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string company = null; + string property = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Company": + company = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Property": + property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == company) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company")); + } + + if (null == property) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.AddPatchMetadata(sourceLineNumbers, company, property, value); + } + } + + /// + /// Parses a patch sequence element. + /// + /// The element to parse. + private void ParsePatchSequenceElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string family = null; + string target = null; + string sequence = null; + var attributes = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "PatchFamily": + family = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "ProductCode": + if (null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "TargetImage")); + } + target = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false); + break; + case "Target": + if (null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "TargetImage", "ProductCode")); + } + this.Core.Write(WarningMessages.DeprecatedPatchSequenceTargetAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName)); + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "TargetImage": + if (null != target) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "ProductCode")); + } + target = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.TargetImages, target); + break; + case "Sequence": + sequence = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib); + break; + case "Supersede": + if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) + { + attributes |= 0x1; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == family) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "PatchFamily")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new PatchSequenceSymbol(sourceLineNumbers) + { + PatchFamily = family, + Target = target, + Sequence = sequence, + Supersede = attributes, + }); + } + } + + private void AddPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value) + { + this.Core.AddSymbol(new PatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property)) + { + Company = company, + Property = property, + Value = value, + }); + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler_Tag.cs b/src/wix/WixToolset.Core/Compiler_Tag.cs new file mode 100644 index 00000000..cf55c448 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_Tag.cs @@ -0,0 +1,315 @@ +// 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 +{ + using System; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a Tag element for Software Id Tag registration under a Bundle element. + /// + /// The element to parse. + private void ParseBundleTagElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string name = null; + string regid = null; + string installPath = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "InstallDirectory": + case "Bitness": + this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Package")); + break; + case "InstallPath": + installPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(name)) + { + name = node.Parent?.Attribute("Name")?.Value; + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + } + + if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) + { + this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); + } + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + } + + if (String.IsNullOrEmpty(installPath)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPath")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleTagSymbol(sourceLineNumbers) + { + Filename = String.Concat(name, ".swidtag"), + Regid = regid, + Name = name, + InstallPath = installPath + }); + } + } + + /// + /// Parses a Tag element for Software Id Tag registration under a Package element. + /// + /// The element to parse. + private void ParsePackageTagElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string name = null; + string regid = null; + string feature = null; + string installDirectory = null; + var win64 = this.Context.IsCurrentPlatform64Bit; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Feature": + feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "InstallDirectory": + installDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "InstallPath": + this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bundle")); + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(name)) + { + name = node.Parent?.Attribute("Name")?.Value; + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + } + + if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) + { + this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); + } + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + return; + } + else if (id == null) + { + id = this.CreateTagId(regid); + } + + if (String.IsNullOrEmpty(installDirectory)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallDirectory")); + } + + if (!this.Core.EncounteredError) + { + var fileName = String.Concat(name, ".swidtag"); + + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, installDirectory); + this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) + { + Name = "swidtag", + ParentDirectoryRef = installDirectory, + ComponentGuidGenerationSeed = "4BAD0C8B-3AF0-BFE3-CC83-094749A1C4B1" + }); + + this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) + { + ComponentId = "*", + DirectoryRef = id.Id, + KeyPath = id.Id, + KeyPathType = ComponentKeyPathType.File, + Location = ComponentLocation.LocalOnly, + Win64 = win64 + }); + + this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) + { + ComponentRef = id.Id, + Name = fileName, + DiskId = 1, + Attributes = FileSymbolAttributes.ReadOnly, + }); + + if (!String.IsNullOrEmpty(feature)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); + } + else + { + feature = "WixSwidTag"; + this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, feature)) + { + Title = "ISO/IEC 19770-2", + Level = 1, + InstallDefault = FeatureInstallDefault.Local, + Display = 0, + DisallowAdvertise = true, + DisallowAbsent = true, + }); + } + this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); + + this.Core.EnsureTable(sourceLineNumbers, "SoftwareIdentificationTag"); + this.Core.AddSymbol(new WixProductTagSymbol(sourceLineNumbers, id) + { + FileRef = id.Id, + Regid = regid, + Name = name + }); + } + } + + /// + /// Parses a TagRef element for Software Id Tag registration under a PatchFamily element. + /// + /// The element to parse. + private void ParseTagRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string regid = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + } + + if (!this.Core.EncounteredError) + { + var id = this.CreateTagId(regid); + + this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers, id) + { + Table = SymbolDefinitions.Component.Name, + PrimaryKeys = id.Id + }); + } + } + + private Identifier CreateTagId(string regid) => this.Core.CreateIdentifier("tag", regid, ".product.tag"); + } +} diff --git a/src/wix/WixToolset.Core/Compiler_UI.cs b/src/wix/WixToolset.Core/Compiler_UI.cs new file mode 100644 index 00000000..d712ec91 --- /dev/null +++ b/src/wix/WixToolset.Core/Compiler_UI.cs @@ -0,0 +1,1808 @@ +// 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 +{ + using System; + using System.Collections; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + // NameToBit arrays + private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; + private static readonly string[] HyperlinkControlAttributes = { "Transparent" }; + private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; + private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; + private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; + private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; + private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; + private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; + private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; + private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; + private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; + private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; + private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; + + /// + /// Parses UI elements. + /// + /// Element to parse. + private void ParseUIElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var embeddedUICount = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "BillboardAction": + this.ParseBillboardActionElement(child); + break; + case "ComboBox": + this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem"); + break; + case "Dialog": + this.ParseDialogElement(child); + break; + case "DialogRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Dialog); + break; + case "EmbeddedUI": + if (0 < embeddedUICount) // there can be only one embedded UI + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName)); + } + this.ParseEmbeddedUIElement(child); + ++embeddedUICount; + break; + case "Error": + this.ParseErrorElement(child); + break; + case "ListBox": + this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem"); + break; + case "ListView": + this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem"); + break; + case "ProgressText": + this.ParseActionTextElement(child); + break; + case "Publish": + var order = 0; + this.ParsePublishElement(child, null, null, ref order); + break; + case "RadioButtonGroup": + var radioButtonType = this.ParseRadioButtonGroupElement(child, null, RadioButtonType.NotSet); + if (RadioButtonType.Bitmap == radioButtonType || RadioButtonType.Icon == radioButtonType) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.RadioButtonBitmapAndIconDisallowed(childSourceLineNumbers)); + } + break; + case "TextStyle": + this.ParseTextStyleElement(child); + break; + case "UIText": + this.ParseUITextElement(child); + break; + + // the following are available indentically under the UI and Product elements for document organization use only + case "AdminUISequence": + this.ParseSequenceElement(child, SequenceTable.AdminUISequence); + break; + case "InstallUISequence": + this.ParseSequenceElement(child, SequenceTable.InstallUISequence); + break; + case "Binary": + this.ParseBinaryElement(child); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "PropertyRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.Property); + break; + case "UIRef": + this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI); + break; + + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null != id && !this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixUISymbol(sourceLineNumbers, id)); + } + } + + /// + /// Parses a list item element. + /// + /// Element to parse. + /// Type of symbol to create. + /// Identifier of property referred to by list item. + /// Relative order of list items. + private void ParseListItemElement(XElement node, SymbolDefinitionType symbolType, string property, ref int order) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string icon = null; + string text = null; + string value = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Icon": + if (SymbolDefinitionType.ListView == symbolType) + { + icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, icon); + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeExceptOnElement(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ListView")); + } + break; + case "Text": + text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + switch (symbolType) + { + case SymbolDefinitionType.ComboBox: + this.Core.AddSymbol(new ComboBoxSymbol(sourceLineNumbers) + { + Property = property, + Order = ++order, + Value = value, + Text = text, + }); + break; + case SymbolDefinitionType.ListBox: + this.Core.AddSymbol(new ListBoxSymbol(sourceLineNumbers) + { + Property = property, + Order = ++order, + Value = value, + Text = text, + }); + break; + case SymbolDefinitionType.ListView: + var symbol = this.Core.AddSymbol(new ListViewSymbol(sourceLineNumbers) + { + Property = property, + Order = ++order, + Value = value, + Text = text, + }); + + if (null != icon) + { + symbol.BinaryRef = icon; + } + break; + default: + throw new ArgumentOutOfRangeException(nameof(symbolType)); + } + } + } + + /// + /// Parses a radio button element. + /// + /// Element to parse. + /// Identifier of property referred to by radio button. + /// Relative order of radio buttons. + /// Type of this radio button. + private RadioButtonType ParseRadioButtonElement(XElement node, string property, ref int order) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var type = RadioButtonType.NotSet; + string value = null; + string x = null; + string y = null; + string width = null; + string height = null; + string text = null; + string tooltip = null; + string help = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Bitmap": + if (RadioButtonType.NotSet != type) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Icon", "Text")); + } + text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); + type = RadioButtonType.Bitmap; + break; + case "Height": + height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Help": + help = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Icon": + if (RadioButtonType.NotSet != type) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Text")); + } + text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); + type = RadioButtonType.Icon; + break; + case "Text": + if (RadioButtonType.NotSet != type) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Icon")); + } + text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + type = RadioButtonType.Text; + break; + case "ToolTip": + tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Value": + value = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Width": + width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "X": + x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Y": + y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == value) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value")); + } + + if (null == x) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); + } + + if (null == y) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); + } + + if (null == width) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); + } + + if (null == height) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + var symbol = this.Core.AddSymbol(new RadioButtonSymbol(sourceLineNumbers) + { + Property = property, + Order = ++order, + Value = value, + Text = text, + Help = (null != tooltip || null != help) ? String.Concat(tooltip, "|", help) : null + }); + + symbol.Set((int)RadioButtonSymbolFields.X, x); + symbol.Set((int)RadioButtonSymbolFields.Y, y); + symbol.Set((int)RadioButtonSymbolFields.Width, width); + symbol.Set((int)RadioButtonSymbolFields.Height, height); + } + + return type; + } + + /// + /// Parses a billboard element. + /// + /// Element to parse. + private void ParseBillboardActionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string action = null; + var order = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + action = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", action); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == action) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Billboard": + order = order + 1; + this.ParseBillboardElement(child, action, order); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + } + + /// + /// Parses a billboard element. + /// + /// Element to parse. + /// Action for the billboard. + /// Order of the billboard. + private void ParseBillboardElement(XElement node, string action, int order) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string feature = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Feature": + feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("bil", action, order.ToString(), feature); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Control": + // These are all thrown away. + ControlSymbol lastTabSymbol = null; + string firstControl = null; + string defaultControl = null; + string cancelControl = null; + + this.ParseControlElement(child, id.Id, SymbolDefinitionType.BBControl, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new BillboardSymbol(sourceLineNumbers, id) + { + FeatureRef = feature, + Action = action, + Ordering = order + }); + } + } + + /// + /// Parses a control group element. + /// + /// Element to parse. + /// Symbol type referred to by control group. + /// Expected child elements. + private void ParseControlGroupElement(XElement node, SymbolDefinitionType symbolType, string childTag) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var order = 0; + string property = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Property": + property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == property) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + if (childTag != child.Name.LocalName) + { + this.Core.UnexpectedElement(node, child); + } + + switch (child.Name.LocalName) + { + case "ListItem": + this.ParseListItemElement(child, symbolType, property, ref order); + break; + case "Property": + this.ParsePropertyElement(child); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + } + + /// + /// Parses a radio button control group element. + /// + /// Element to parse. + /// Property associated with this radio button group. + /// Specifies the current type of radio buttons in the group. + /// The current type of radio buttons in the group. + private RadioButtonType ParseRadioButtonGroupElement(XElement node, string property, RadioButtonType groupType) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var order = 0; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Property": + property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == property) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property")); + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "RadioButton": + var type = this.ParseRadioButtonElement(child, property, ref order); + if (RadioButtonType.NotSet == groupType) + { + groupType = type; + } + else if (groupType != type) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + this.Core.Write(ErrorMessages.RadioButtonTypeInconsistent(childSourceLineNumbers)); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + + return groupType; + } + + /// + /// Parses an action text element. + /// + /// Element to parse. + private void ParseActionTextElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string action = null; + string message = null; + string template = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Action": + action = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Message": + message = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Template": + template = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == action) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ActionTextSymbol(sourceLineNumbers) + { + Action = action, + Description = message, + Template = template, + }); + } + } + + /// + /// Parses an ui text element. + /// + /// Element to parse. + private void ParseUITextElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string text = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Value": + text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + id = this.Core.CreateIdentifier("txt", text); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new UITextSymbol(sourceLineNumbers, id) + { + Text = text, + }); + } + } + + /// + /// Parses a text style element. + /// + /// Element to parse. + private void ParseTextStyleElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + int? red = null; + int? green = null; + int? blue = null; + var bold = false; + var italic = false; + var strike = false; + var underline = false; + string faceName = null; + var size = "0"; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + + // RGB Values + case "Red": + var redColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); + if (CompilerConstants.IllegalInteger != redColor) + { + red = redColor; + } + break; + case "Green": + var greenColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); + if (CompilerConstants.IllegalInteger != greenColor) + { + green = greenColor; + } + break; + case "Blue": + var blueColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue); + if (CompilerConstants.IllegalInteger != blueColor) + { + blue = blueColor; + } + break; + + // Style values + case "Bold": + bold = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Italic": + italic = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Strike": + strike = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Underline": + underline = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + + // Font values + case "FaceName": + faceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Size": + size = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.CreateIdentifier("txs", faceName, size.ToString(), (red ?? 0).ToString(), (green ?? 0).ToString(), (blue ?? 0).ToString(), bold.ToString(), italic.ToString(), strike.ToString(), underline.ToString()); + } + + if (null == faceName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "FaceName")); + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new TextStyleSymbol(sourceLineNumbers, id) + { + FaceName = faceName, + LocalizedSize = size, + Red = red, + Green = green, + Blue = blue, + Bold = bold, + Italic = italic, + Strike = strike, + Underline = underline, + }); + } + } + + /// + /// Parses a dialog element. + /// + /// Element to parse. + private void ParseDialogElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + var hidden = false; + var modal = true; + var minimize = true; + var customPalette = false; + var errorDialog = false; + var keepModeless = false; + var height = 0; + string title = null; + var leftScroll = false; + var rightAligned = false; + var rightToLeft = false; + var systemModal = false; + var trackDiskSpace = false; + var width = 0; + var x = 50; + var y = 50; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Height": + height = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Title": + title = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Width": + width = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "X": + x = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); + break; + case "Y": + y = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100); + break; + case "CustomPalette": + customPalette = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "ErrorDialog": + errorDialog = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Hidden": + hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "KeepModeless": + keepModeless = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "LeftScroll": + leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Modeless": + modal = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "NoMinimize": + minimize = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RightAligned": + rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RightToLeft": + rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "SystemModal": + systemModal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "TrackDiskSpace": + trackDiskSpace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + id = Identifier.Invalid; + } + + ControlSymbol lastTabSymbol = null; + string cancelControl = null; + string defaultControl = null; + string firstControl = null; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Control": + this.ParseControlElement(child, id.Id, SymbolDefinitionType.Control, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null != lastTabSymbol && null != lastTabSymbol.Control) + { + if (firstControl != lastTabSymbol.Control) + { + lastTabSymbol.NextControlRef = firstControl; + } + } + + if (null == firstControl) + { + this.Core.Write(ErrorMessages.NoFirstControlSpecified(sourceLineNumbers, id.Id)); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new DialogSymbol(sourceLineNumbers, id) + { + HCentering = x, + VCentering = y, + Width = width, + Height = height, + CustomPalette = customPalette, + ErrorDialog = errorDialog, + Visible = !hidden, + Modal = modal, + KeepModeless = keepModeless, + LeftScroll = leftScroll, + Minimize = minimize, + RightAligned = rightAligned, + RightToLeft = rightToLeft, + SystemModal = systemModal, + TrackDiskSpace = trackDiskSpace, + Title = title, + FirstControlRef = firstControl, + DefaultControlRef = defaultControl, + CancelControlRef = cancelControl, + }); + } + } + + /// + /// Parses a control element. + /// + /// Element to parse. + /// Identifier for parent dialog. + /// Table control belongs in. + /// Last control in the tab order. + /// Name of the first control in the tab order. + /// Name of the default control. + /// Name of the candle control. + private void ParseControlElement(XElement node, string dialog, SymbolDefinitionType symbolType, ref ControlSymbol lastTabSymbol, ref string firstControl, ref string defaultControl, ref string cancelControl) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier controlId = null; + var bits = new BitArray(32); + string checkBoxPropertyRef = null; + string checkboxValue = null; + string controlType = null; + var disabled = false; + string height = null; + string help = null; + var isCancel = false; + var isDefault = false; + var notTabbable = false; + string property = null; + var publishOrder = 0; + string sourceFile = null; + string text = null; + string tooltip = null; + var radioButtonsType = RadioButtonType.NotSet; + string width = null; + string x = null; + string y = null; + + string defaultCondition = null; + string enableCondition = null; + string disableCondition = null; + string hideCondition = null; + string showCondition = null; + + var hidden = false; + var sunken = false; + var indirect = false; + var integer = false; + var rightToLeft = false; + var rightAligned = false; + var leftScroll = false; + + // The rest of the method relies on the control's Type, so we have to get that first. + var typeAttribute = node.Attribute("Type"); + if (null == typeAttribute) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type")); + } + else + { + controlType = this.Core.GetAttributeValue(sourceLineNumbers, typeAttribute); + } + + string[] specialAttributes; + switch (controlType) + { + case "Billboard": + specialAttributes = null; + notTabbable = true; + disabled = true; + + this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Billboard); + break; + case "Bitmap": + specialAttributes = BitmapControlAttributes; + notTabbable = true; + disabled = true; + break; + case "CheckBox": + specialAttributes = CheckboxControlAttributes; + break; + case "ComboBox": + specialAttributes = ComboboxControlAttributes; + break; + case "DirectoryCombo": + specialAttributes = VolumeControlAttributes; + break; + case "DirectoryList": + specialAttributes = null; + break; + case "Edit": + specialAttributes = EditControlAttributes; + break; + case "GroupBox": + specialAttributes = null; + notTabbable = true; + break; + case "Hyperlink": + specialAttributes = HyperlinkControlAttributes; + break; + case "Icon": + specialAttributes = IconControlAttributes; + notTabbable = true; + disabled = true; + break; + case "Line": + specialAttributes = null; + notTabbable = true; + disabled = true; + break; + case "ListBox": + specialAttributes = ListboxControlAttributes; + break; + case "ListView": + specialAttributes = ListviewControlAttributes; + break; + case "MaskedEdit": + specialAttributes = EditControlAttributes; + break; + case "PathEdit": + specialAttributes = EditControlAttributes; + break; + case "ProgressBar": + specialAttributes = ProgressControlAttributes; + notTabbable = true; + disabled = true; + break; + case "PushButton": + specialAttributes = ButtonControlAttributes; + break; + case "RadioButtonGroup": + specialAttributes = RadioControlAttributes; + break; + case "ScrollableText": + specialAttributes = null; + break; + case "SelectionTree": + specialAttributes = null; + break; + case "Text": + specialAttributes = TextControlAttributes; + notTabbable = true; + break; + case "VolumeCostList": + specialAttributes = VolumeControlAttributes; + notTabbable = true; + break; + case "VolumeSelectCombo": + specialAttributes = VolumeControlAttributes; + break; + default: + specialAttributes = null; + notTabbable = true; + break; + } + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + controlId = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Type": // already processed + break; + case "Cancel": + isCancel = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "CheckBoxPropertyRef": + checkBoxPropertyRef = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "CheckBoxValue": + checkboxValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Default": + isDefault = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "DefaultCondition": + defaultCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "EnableCondition": + enableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "DisableCondition": + disableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "HideCondition": + hideCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ShowCondition": + showCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Height": + height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Help": + help = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "IconSize": + var iconSizeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + if (null != specialAttributes) + { + switch (iconSizeValue) + { + case "16": + this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); + break; + case "32": + this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); + break; + case "48": + this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16); + this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16); + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "16", "32", "48")); + break; + } + } + else + { + this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "Type")); + } + break; + case "Property": + property = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "TabSkip": + notTabbable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Text": + text = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "ToolTip": + tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Width": + width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "X": + x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Y": + y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Disabled": + disabled = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Hidden": + hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Sunken": + sunken = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Indirect": + indirect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "Integer": + integer = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RightToLeft": + rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "RightAligned": + rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + case "LeftScroll": + leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + break; + default: + var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + if (null == specialAttributes || !this.Core.TrySetBitFromName(specialAttributes, attrib.Name.LocalName, attribValue, bits, 16)) + { + this.Core.UnexpectedAttribute(node, attrib); + } + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + var attributes = this.Core.CreateIntegerFromBitArray(bits); + + //if (disabled) + //{ + // attributes |= WindowsInstallerConstants.MsidbControlAttributesEnabled; // bit will be inverted when stored + //} + + if (null == height) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height")); + } + + if (null == width) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width")); + } + + if (null == x) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X")); + } + + if (null == y) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y")); + } + + if (null == controlId) + { + controlId = this.Core.CreateIdentifier("ctl", dialog, x, y, height, width); + } + + if (isCancel) + { + cancelControl = controlId.Id; + } + + if (isDefault) + { + defaultControl = controlId.Id; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "Binary": + this.ParseBinaryElement(child); + break; + case "ComboBox": + this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem"); + break; + case "ListBox": + this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem"); + break; + case "ListView": + this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem"); + break; + case "Property": + this.ParsePropertyElement(child); + break; + case "Publish": + this.ParsePublishElement(child, dialog ?? String.Empty, controlId.Id, ref publishOrder); + break; + case "RadioButtonGroup": + radioButtonsType = this.ParseRadioButtonGroupElement(child, property, radioButtonsType); + break; + case "Subscribe": + this.ParseSubscribeElement(child, dialog, controlId.Id); + break; + case "Text": + foreach (var attrib in child.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "SourceFile": + sourceFile = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Value": + text = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(child, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(child, attrib); + } + } + + this.Core.InnerTextDisallowed(child); + + if (!String.IsNullOrEmpty(text) && null != sourceFile) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "SourceFile", "Value")); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + this.Core.InnerTextDisallowed(node); + + // If the radio buttons have icons, then we need to add the icon attribute. + switch (radioButtonsType) + { + case RadioButtonType.Bitmap: + attributes |= WindowsInstallerConstants.MsidbControlAttributesBitmap; + break; + case RadioButtonType.Icon: + attributes |= WindowsInstallerConstants.MsidbControlAttributesIcon; + break; + case RadioButtonType.Text: + // Text is the default so nothing needs to be added bits + break; + } + + // the logic for creating control rows is a little tricky because of the way tabable controls are set + IntermediateSymbol symbol = null; + if (!this.Core.EncounteredError) + { + if ("CheckBox" == controlType) + { + if (String.IsNullOrEmpty(property) && String.IsNullOrEmpty(checkBoxPropertyRef)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef", true)); + } + else if (!String.IsNullOrEmpty(property) && !String.IsNullOrEmpty(checkBoxPropertyRef)) + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef")); + } + else if (!String.IsNullOrEmpty(property)) + { + this.Core.AddSymbol(new CheckBoxSymbol(sourceLineNumbers) + { + Property = property, + Value = checkboxValue, + }); + } + else + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CheckBox, checkBoxPropertyRef); + } + } + + var id = new Identifier(controlId.Access, dialog, controlId.Id); + + if (SymbolDefinitionType.BBControl == symbolType) + { + var bbSymbol = this.Core.AddSymbol(new BBControlSymbol(sourceLineNumbers, id) + { + BillboardRef = dialog, + BBControl = controlId.Id, + Type = controlType, + Attributes = attributes, + Enabled = !disabled, + Indirect = indirect, + Integer = integer, + LeftScroll = leftScroll, + RightAligned = rightAligned, + RightToLeft = rightToLeft, + Sunken = sunken, + Visible = !hidden, + Text = text, + SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile } + }); + + bbSymbol.Set((int)BBControlSymbolFields.X, x); + bbSymbol.Set((int)BBControlSymbolFields.Y, y); + bbSymbol.Set((int)BBControlSymbolFields.Width, width); + bbSymbol.Set((int)BBControlSymbolFields.Height, height); + + symbol = bbSymbol; + } + else + { + var controlSymbol = this.Core.AddSymbol(new ControlSymbol(sourceLineNumbers, id) + { + DialogRef = dialog, + Control = controlId.Id, + Type = controlType, + Attributes = attributes, + Enabled = !disabled, + Indirect = indirect, + Integer = integer, + LeftScroll = leftScroll, + RightAligned = rightAligned, + RightToLeft = rightToLeft, + Sunken = sunken, + Visible = !hidden, + Property = !String.IsNullOrEmpty(property) ? property : checkBoxPropertyRef, + Text = text, + Help = (null == tooltip && null == help) ? null : String.Concat(tooltip, "|", help), // Separator is required, even if only one is non-null.}; + SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile } + }); + + controlSymbol.Set((int)BBControlSymbolFields.X, x); + controlSymbol.Set((int)BBControlSymbolFields.Y, y); + controlSymbol.Set((int)BBControlSymbolFields.Width, width); + controlSymbol.Set((int)BBControlSymbolFields.Height, height); + + symbol = controlSymbol; + } + + if (!String.IsNullOrEmpty(defaultCondition)) + { + this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = controlId.Id, + Action = "Default", + Condition = defaultCondition, + }); + } + + if (!String.IsNullOrEmpty(enableCondition)) + { + this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = controlId.Id, + Action = "Enable", + Condition = enableCondition, + }); + } + + if (!String.IsNullOrEmpty(disableCondition)) + { + this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = controlId.Id, + Action = "Disable", + Condition = disableCondition, + }); + } + + if (!String.IsNullOrEmpty(hideCondition)) + { + this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = controlId.Id, + Action = "Hide", + Condition = hideCondition, + }); + } + + if (!String.IsNullOrEmpty(showCondition)) + { + this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = controlId.Id, + Action = "Show", + Condition = showCondition, + }); + } + } + + if (!notTabbable) + { + if (symbol is ControlSymbol controlSymbol) + { + if (null != lastTabSymbol) + { + lastTabSymbol.NextControlRef = controlSymbol.Control; + } + lastTabSymbol = controlSymbol; + } + else if (symbol != null) + { + this.Core.Write(ErrorMessages.TabbableControlNotAllowedInBillboard(sourceLineNumbers, node.Name.LocalName, controlType)); + } + + if (null == firstControl) + { + firstControl = controlId.Id; + } + } + + // bitmap and icon controls contain a foreign key into the binary table in the text column; + // add a reference if the identifier of the binary entry is known during compilation + if (("Bitmap" == controlType || "Icon" == controlType) && Common.IsIdentifier(text)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text); + } + } + + /// + /// Parses a publish control event element. + /// + /// Element to parse. + /// Identifier of parent dialog. + /// Identifier of parent control. + /// Relative order of controls. + private void ParsePublishElement(XElement node, string dialog, string control, ref int order) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string argument = null; + string condition = null; + string controlEvent = null; + string property = null; + + // give this control event a unique ordering + order++; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Control": + if (null != control) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); + } + control = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "Dialog": + if (null != dialog) + { + this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName)); + } + dialog = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, dialog); + break; + case "Event": + controlEvent = Compiler.UppercaseFirstChar(this.Core.GetAttributeValue(sourceLineNumbers, attrib)); + break; + case "Order": + order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 2147483647); + break; + case "Property": + property = String.Concat("[", this.Core.GetAttributeValue(sourceLineNumbers, attrib), "]"); + break; + case "Value": + argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + if (null == control) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Control")); + } + + if (null == dialog) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dialog")); + } + + if (null == controlEvent && null == property) // need to specify at least one + { + this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); + } + else if (null != controlEvent && null != property) // cannot specify both + { + this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Event", "Property")); + } + + if (null == argument) + { + if (null != controlEvent) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value", "Event")); + } + else if (null != property) + { + // if this is setting a property to null, put a special value in the argument column + argument = "{}"; + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new ControlEventSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = control, + Event = controlEvent ?? property, + Argument = argument, + Condition = condition, + Ordering = order + }); + } + + if ("DoAction" == controlEvent && null != argument) + { + // if we're not looking at a standard action or a formatted string then create a reference + // to the custom action. + if (!WindowsInstallerStandard.IsStandardAction(argument) && !this.Core.ContainsProperty(argument)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CustomAction, argument); + } + } + + // if we're referring to a dialog but not through a property, add it to the references + if (("NewDialog" == controlEvent || "SpawnDialog" == controlEvent || "SpawnWaitDialog" == controlEvent || "SelectionBrowse" == controlEvent) && Common.IsIdentifier(argument)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, argument); + } + } + + /// + /// Parses a control subscription element. + /// + /// Element to parse. + /// Identifier of dialog. + /// Identifier of control. + private void ParseSubscribeElement(XElement node, string dialog, string control) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string controlAttribute = null; + string eventMapping = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Attribute": + controlAttribute = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); + break; + case "Event": + eventMapping = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib)); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new EventMappingSymbol(sourceLineNumbers) + { + DialogRef = dialog, + ControlRef = control, + Event = eventMapping, + Attribute = controlAttribute, + }); + } + } + } +} diff --git a/src/wix/WixToolset.Core/ComponentKeyPath.cs b/src/wix/WixToolset.Core/ComponentKeyPath.cs new file mode 100644 index 00000000..8e9c5776 --- /dev/null +++ b/src/wix/WixToolset.Core/ComponentKeyPath.cs @@ -0,0 +1,25 @@ +// 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 +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class ComponentKeyPath : IComponentKeyPath + { + /// + /// Identifier of the resource to be a key path. + /// + public string Id { get; set; } + + /// + /// Indicates whether the key path was explicitly set for this resource. + /// + public bool Explicit { get; set; } + + /// + /// Type of resource to be the key path. + /// + public PossibleKeyPathType Type { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/DecompileContext.cs b/src/wix/WixToolset.Core/DecompileContext.cs new file mode 100644 index 00000000..a7ec03fd --- /dev/null +++ b/src/wix/WixToolset.Core/DecompileContext.cs @@ -0,0 +1,49 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class DecompileContext : IDecompileContext + { + internal DecompileContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public string DecompilePath { get; set; } + + public OutputType DecompileType { get; set; } + + public IReadOnlyCollection Extensions { get; set; } + + public string ExtractFolder { get; set; } + + public string CabinetExtractFolder { get; set; } + + public string BaseSourcePath { get; set; } + + public string IntermediateFolder { get; set; } + + public bool IsAdminImage { get; set; } + + public string OutputPath { get; set; } + + public bool SuppressCustomTables { get; set; } + + public bool SuppressDroppingEmptyTables { get; set; } + + public bool SuppressExtractCabinets { get; set; } + + public bool SuppressUI { get; set; } + + public bool TreatProductAsModule { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/DecompileResult.cs b/src/wix/WixToolset.Core/DecompileResult.cs new file mode 100644 index 00000000..fc24cab7 --- /dev/null +++ b/src/wix/WixToolset.Core/DecompileResult.cs @@ -0,0 +1,18 @@ +// 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 +{ + using System.Collections.Generic; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class DecompileResult : IDecompileResult + { + public XDocument Document { get; set; } + + public IReadOnlyCollection ExtractedFilePaths { get; set; } + + public Platform? Platform { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Decompiler.cs b/src/wix/WixToolset.Core/Decompiler.cs new file mode 100644 index 00000000..859f582b --- /dev/null +++ b/src/wix/WixToolset.Core/Decompiler.cs @@ -0,0 +1,68 @@ +// 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 +{ + using System; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Decompiler of the WiX toolset. + /// + internal class Decompiler : IDecompiler + { + internal Decompiler(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IDecompileResult Decompile(IDecompileContext context) + { + // Pre-decompile. + // + foreach (var extension in context.Extensions) + { + extension.PreDecompile(context); + } + + // Decompile. + // + var result = this.BackendDecompile(context); + + if (result != null) + { + // Post-decompile. + // + foreach (var extension in context.Extensions) + { + extension.PostDecompile(result); + } + } + + return result; + } + + private IDecompileResult BackendDecompile(IDecompileContext context) + { + var extensionManager = context.ServiceProvider.GetService(); + + var backendFactories = extensionManager.GetServices(); + + foreach (var factory in backendFactories) + { + if (factory.TryCreateBackend(context.DecompileType.ToString(), context.OutputPath, out var backend)) + { + var result = backend.Decompile(context); + return result; + } + } + + // TODO: messaging that a backend could not be found to decompile the decompile type? + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs new file mode 100644 index 00000000..cfa78623 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs @@ -0,0 +1,176 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class BackendHelper : IBackendHelper + { + private static readonly string[] ReservedFileNames = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; + + public BackendHelper(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + private IMessaging Messaging { get; } + + public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) + { + return new FileFacade(file, assembly); + } + + public IFileFacade CreateFileFacade(FileRow fileRow) + { + return new FileFacade(fileRow); + } + + public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) + { + return new FileFacade(true, fileSymbol); + } + + public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) + { + var sourceFullPath = this.GetValidatedFullPath(sourceLineNumbers, source); + + var destinationFullPath = this.GetValidatedFullPath(sourceLineNumbers, destination); + + return (String.IsNullOrEmpty(sourceFullPath) || String.IsNullOrEmpty(destinationFullPath)) ? null : new FileTransfer + { + Source = sourceFullPath, + Destination = destinationFullPath, + Move = move, + SourceLineNumbers = sourceLineNumbers, + Redundant = String.Equals(sourceFullPath, destinationFullPath, StringComparison.OrdinalIgnoreCase) + }; + } + + public string CreateGuid() + { + return Common.GenerateGuid(); + } + + public string CreateGuid(Guid namespaceGuid, string value) + { + return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); + } + + public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) + { + return new ResolvedDirectory + { + DirectoryParent = directoryParent, + Name = name + }; + } + + public IReadOnlyList ExtractEmbeddedFiles(IEnumerable embeddedFiles) + { + var command = new ExtractEmbeddedFilesCommand(this, embeddedFiles); + command.Execute(); + + return command.TrackedFiles; + } + + public string GenerateIdentifier(string prefix, params string[] args) + { + return Common.GenerateIdentifier(prefix, args); + } + + public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) + { + return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging); + } + + public int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) + { + return Common.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers); + } + + public string GetMsiFileName(string value, bool source, bool longName) + { + return Common.GetName(value, source, longName); + } + + public void ResolveDelayedFields(IEnumerable delayedFields, Dictionary variableCache) + { + var command = new ResolveDelayedFieldsCommand(this.Messaging, delayedFields, variableCache); + command.Execute(); + } + + public string[] SplitMsiFileName(string value) + { + return Common.GetNames(value); + } + + public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) + { + return new TrackedFile(path, type, sourceLineNumbers); + } + + public bool IsValidBinderVariable(string variable) + { + return Common.IsValidBinderVariable(variable); + } + + public bool IsValidFourPartVersion(string version) + { + return Common.IsValidFourPartVersion(version); + } + + public bool IsValidIdentifier(string id) + { + return Common.IsIdentifier(id); + } + + public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) + { + return Common.IsValidLongFilename(filename, allowWildcards, allowRelative); + } + + public bool IsValidShortFilename(string filename, bool allowWildcards) + { + return Common.IsValidShortFilename(filename, allowWildcards); + } + + private string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path) + { + try + { + var result = Path.GetFullPath(path); + + var filename = Path.GetFileName(result); + + foreach (var reservedName in ReservedFileNames) + { + if (reservedName.Equals(filename, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path)); + return null; + } + } + + return result; + } + catch (ArgumentException) + { + this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path)); + } + catch (PathTooLongException) + { + this.Messaging.Write(ErrorMessages.PathTooLong(sourceLineNumbers, path)); + } + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs new file mode 100644 index 00000000..2340ed9e --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs @@ -0,0 +1,233 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class ExtensionManager : IExtensionManager + { + private const string UserWixFolderName = ".wix4"; + private const string MachineWixFolderName = "WixToolset4"; + private const string ExtensionsFolderName = "extensions"; + + private readonly List extensionFactories = new List(); + private readonly Dictionary> loadedExtensionsByType = new Dictionary>(); + + public ExtensionManager(IWixToolsetCoreServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + private IWixToolsetCoreServiceProvider ServiceProvider { get; } + + public void Add(Assembly extensionAssembly) + { + var types = extensionAssembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && typeof(IExtensionFactory).IsAssignableFrom(t)); + var factories = types.Select(this.CreateExtensionFactory).ToList(); + + if (!factories.Any()) + { + var path = Path.GetFullPath(new Uri(extensionAssembly.CodeBase).LocalPath); + throw new WixException(ErrorMessages.InvalidExtension(path, "The extension does not implement IExtensionFactory. All extensions must have at least one implementation of IExtensionFactory.")); + } + + this.extensionFactories.AddRange(factories); + } + + public void Load(string extensionPath) + { + var checkPath = extensionPath; + var checkedPaths = new List { checkPath }; + try + { + if (!TryLoadFromPath(checkPath, out var assembly) && !Path.IsPathRooted(extensionPath)) + { + if (TryParseExtensionReference(extensionPath, out var extensionId, out var extensionVersion)) + { + foreach (var cachePath in this.CacheLocations()) + { + var extensionFolder = Path.Combine(cachePath, extensionId); + + var versionFolder = extensionVersion; + if (String.IsNullOrEmpty(versionFolder) && !TryFindLatestVersionInFolder(extensionFolder, out versionFolder)) + { + checkedPaths.Add(extensionFolder); + continue; + } + + checkPath = Path.Combine(extensionFolder, versionFolder, "tools", extensionId + ".dll"); + checkedPaths.Add(checkPath); + + if (TryLoadFromPath(checkPath, out assembly)) + { + break; + } + } + } + } + + if (assembly == null) + { + throw new WixException(ErrorMessages.CouldNotFindExtensionInPaths(extensionPath, checkedPaths)); + } + + this.Add(assembly); + } + catch (ReflectionTypeLoadException rtle) + { + throw new WixException(ErrorMessages.InvalidExtension(checkPath, String.Join(Environment.NewLine, rtle.LoaderExceptions.Select(le => le.ToString())))); + } + catch (WixException) + { + throw; + } + catch (Exception e) + { + throw new WixException(ErrorMessages.InvalidExtension(checkPath, e.Message), e); + } + } + + public IReadOnlyCollection GetServices() where T : class + { + if (!this.loadedExtensionsByType.TryGetValue(typeof(T), out var extensions)) + { + extensions = new List(); + + foreach (var factory in this.extensionFactories) + { + if (factory.TryCreateExtension(typeof(T), out var obj) && obj is T extension) + { + extensions.Add(extension); + } + } + + this.loadedExtensionsByType.Add(typeof(T), extensions); + } + + return extensions.Cast().ToList(); + } + + private IExtensionFactory CreateExtensionFactory(Type type) + { + var constructor = type.GetConstructor(new[] { typeof(IWixToolsetCoreServiceProvider) }); + if (constructor != null) + { + return (IExtensionFactory)constructor.Invoke(new[] { this.ServiceProvider }); + } + + return (IExtensionFactory)Activator.CreateInstance(type); + } + + private IEnumerable CacheLocations() + { + var path = Path.Combine(Environment.CurrentDirectory, UserWixFolderName, ExtensionsFolderName); + if (Directory.Exists(path)) + { + yield return path; + } + + path = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + path = Path.Combine(path, UserWixFolderName, ExtensionsFolderName); + if (Directory.Exists(path)) + { + yield return path; + } + + if (Environment.Is64BitOperatingSystem) + { + path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), MachineWixFolderName, ExtensionsFolderName); + if (Directory.Exists(path)) + { + yield return path; + } + } + + path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), MachineWixFolderName, ExtensionsFolderName); + if (Directory.Exists(path)) + { + yield return path; + } + + path = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath), ExtensionsFolderName); + if (Directory.Exists(path)) + { + yield return path; + } + } + + private static bool TryParseExtensionReference(string extensionReference, out string extensionId, out string extensionVersion) + { + extensionId = extensionReference ?? String.Empty; + extensionVersion = String.Empty; + + var index = extensionId.LastIndexOf('/'); + if (index > 0) + { + extensionVersion = extensionReference.Substring(index + 1); + extensionId = extensionReference.Substring(0, index); + + if (!NuGet.Versioning.NuGetVersion.TryParse(extensionVersion, out _)) + { + return false; + } + + if (String.IsNullOrEmpty(extensionId)) + { + return false; + } + } + + return true; + } + + private static bool TryFindLatestVersionInFolder(string basePath, out string foundVersionFolder) + { + foundVersionFolder = null; + + try + { + NuGet.Versioning.NuGetVersion version = null; + foreach (var versionPath in Directory.GetDirectories(basePath)) + { + var versionFolder = Path.GetFileName(versionPath); + if (NuGet.Versioning.NuGetVersion.TryParse(versionFolder, out var checkVersion) && + (version == null || version < checkVersion)) + { + foundVersionFolder = versionFolder; + version = checkVersion; + } + } + } + catch (IOException) + { + } + + return !String.IsNullOrEmpty(foundVersionFolder); + } + + private static bool TryLoadFromPath(string extensionPath, out Assembly assembly) + { + try + { + if (File.Exists(extensionPath)) + { + assembly = Assembly.LoadFrom(extensionPath); + return true; + } + } + catch (IOException e) when (e is FileLoadException || e is FileNotFoundException) + { + } + + assembly = null; + return false; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs new file mode 100644 index 00000000..f85d4842 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs @@ -0,0 +1,172 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Data; + + internal class FileFacade : IFileFacade + { + public FileFacade(FileSymbol file, AssemblySymbol assembly) + { + this.FileSymbol = file; + this.AssemblySymbol = assembly; + + this.Identifier = file.Id; + this.ComponentRef = file.ComponentRef; + } + + public FileFacade(bool fromModule, FileSymbol file) + { + this.FromModule = fromModule; + this.FileSymbol = file; + + this.Identifier = file.Id; + this.ComponentRef = file.ComponentRef; + } + + public FileFacade(FileRow row) + { + this.FromTransform = true; + this.FileRow = row; + + this.Identifier = new Identifier(AccessModifier.Section, row.File); + this.ComponentRef = row.Component; + } + + public bool FromModule { get; } + + public bool FromTransform { get; } + + private FileRow FileRow { get; } + + private FileSymbol FileSymbol { get; } + + private AssemblySymbol AssemblySymbol { get; } + + public string Id => this.Identifier.Id; + + public Identifier Identifier { get; } + + public string ComponentRef { get; } + + public int DiskId + { + get => this.FileRow == null ? this.FileSymbol.DiskId ?? 1 : this.FileRow.DiskId; + set + { + if (this.FileRow == null) + { + this.FileSymbol.DiskId = value; + } + else + { + this.FileRow.DiskId = value; + } + } + } + + public string FileName => this.FileRow == null ? this.FileSymbol.Name : this.FileRow.FileName; + + public int FileSize + { + get => this.FileRow == null ? this.FileSymbol.FileSize : this.FileRow.FileSize; + set + { + if (this.FileRow == null) + { + this.FileSymbol.FileSize = value; + } + else + { + this.FileRow.FileSize = value; + } + } + } + + public string Language + { + get => this.FileRow == null ? this.FileSymbol.Language : this.FileRow.Language; + set + { + if (this.FileRow == null) + { + this.FileSymbol.Language = value; + } + else + { + this.FileRow.Language = value; + } + } + } + + public int? PatchGroup => this.FileRow == null ? this.FileSymbol.PatchGroup : null; + + public int Sequence + { + get => this.FileRow == null ? this.FileSymbol.Sequence : this.FileRow.Sequence; + set + { + if (this.FileRow == null) + { + this.FileSymbol.Sequence = value; + } + else + { + this.FileRow.Sequence = value; + } + } + } + + public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers; + + public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source; + + public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; + + public bool Uncompressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + + public string Version + { + get => this.FileRow == null ? this.FileSymbol.Version : this.FileRow.Version; + set + { + if (this.FileRow == null) + { + this.FileSymbol.Version = value; + } + else + { + this.FileRow.Version = value; + } + } + } + + public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblySymbol?.Type : null; + + public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblySymbol?.ApplicationFileRef : throw new NotImplementedException(); + + public string AssemblyManifestFileRef => this.FileRow == null ? this.AssemblySymbol?.ManifestFileRef : throw new NotImplementedException(); + + /// + /// Gets the set of MsiAssemblyName rows created for this file. + /// + /// RowCollection of MsiAssemblyName table. + public List AssemblyNames { get; set; } + + /// + /// Gets or sets the MsiFileHash row for this file. + /// + public MsiFileHashSymbol Hash { get; set; } + + /// + /// Allows direct access to the underlying FileRow as requried for patching. + /// + public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException(); + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs b/src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs new file mode 100644 index 00000000..2cad7cce --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs @@ -0,0 +1,20 @@ +// 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.ExtensibilityServices +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class FileTransfer : IFileTransfer + { + public string Source { get; set; } + + public string Destination { get; set; } + + public bool Move { get; set; } + + public SourceLineNumber SourceLineNumbers { get; set; } + + public bool Redundant { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs b/src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs new file mode 100644 index 00000000..afcd9244 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs @@ -0,0 +1,99 @@ +// 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.ExtensibilityServices +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class Messaging : IMessaging + { + private IMessageListener listener; + private readonly HashSet suppressedWarnings = new HashSet(); + private readonly HashSet warningsAsErrors = new HashSet(); + + public bool EncounteredError { get; private set; } + + public int LastErrorNumber { get; private set; } + + public bool ShowVerboseMessages { get; set; } + + public bool SuppressAllWarnings { get; set; } + + public bool WarningsAsError { get; set; } + + public void ElevateWarningMessage(int warningNumber) => this.warningsAsErrors.Add(warningNumber); + + public void SetListener(IMessageListener listener) => this.listener = listener; + + public void SuppressWarningMessage(int warningNumber) => this.suppressedWarnings.Add(warningNumber); + + public void Write(Message message) + { + var level = this.CalculateMessageLevel(message); + + if (level == MessageLevel.Nothing) + { + return; + } + + if (level == MessageLevel.Error) + { + this.EncounteredError = true; + this.LastErrorNumber = message.Id; + } + + if (this.listener != null) + { + this.listener.Write(message); + } + else if (level == MessageLevel.Error) + { + throw new WixException(message); + } + } + + public void Write(string message, bool verbose = false) + { + if (!verbose || this.ShowVerboseMessages) + { + this.listener?.Write(message); + } + } + + /// + /// Determines the level of this message, when taking into account warning-as-error, + /// warning level, verbosity level and message suppressed by the caller. + /// + /// Event arguments for the message. + /// MessageLevel representing the level of this message. + private MessageLevel CalculateMessageLevel(Message message) + { + var level = message.Level; + + if (level == MessageLevel.Verbose) + { + if (!this.ShowVerboseMessages) + { + level = MessageLevel.Nothing; + } + } + else if (level == MessageLevel.Warning) + { + if (this.SuppressAllWarnings || this.suppressedWarnings.Contains(message.Id)) + { + level = MessageLevel.Nothing; + } + else if (this.WarningsAsError || this.warningsAsErrors.Contains(message.Id)) + { + level = MessageLevel.Error; + } + } + + level = this.listener?.CalculateMessageLevel(this, message, level) ?? level; + + return level; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs new file mode 100644 index 00000000..c1368190 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs @@ -0,0 +1,863 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Xml; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ParseHelper : IParseHelper + { + public ParseHelper(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = serviceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private ISymbolDefinitionCreator Creator { get; set; } + + public bool ContainsProperty(string possibleProperty) + { + return Common.ContainsProperty(possibleProperty); + } + + public void CreateComplexReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary) + { + + section.AddSymbol(new WixComplexReferenceSymbol(sourceLineNumbers) + { + Parent = parentId, + ParentType = parentType, + ParentLanguage = parentLanguage, + Child = childId, + ChildType = childType, + IsPrimary = isPrimary + }); + + this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId); + } + + public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null) + { + if (null == id) + { + id = this.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName); + } + + var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) + { + ParentDirectoryRef = parentId, + Name = name, + ShortName = shortName, + SourceName = sourceName, + SourceShortName = shortSourceName + }); + + return symbol.Id; + } + + public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax, IDictionary sectionCachedInlinedDirectoryIds) + { + if (String.IsNullOrEmpty(parentId)) + { + throw new ArgumentNullException(nameof(parentId)); + } + + if (String.IsNullOrEmpty(inlineSyntax)) + { + inlineSyntax = this.GetAttributeLongFilename(sourceLineNumbers, attribute, false, true); + } + + if (String.IsNullOrEmpty(inlineSyntax)) + { + return parentId; + } + + inlineSyntax = inlineSyntax.Trim('\\', '/'); + + var cacheKey = String.Concat(parentId, ":", inlineSyntax); + + if (!sectionCachedInlinedDirectoryIds.TryGetValue(cacheKey, out var id)) + { + var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, id: null, parentId, inlineSyntax); + + id = identifier.Id; + } + else + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id); + } + + return id; + } + + public string CreateGuid(Guid namespaceGuid, string value) + { + return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant(); + } + + public Identifier CreateIdentifier(string prefix, params string[] args) + { + var id = Common.GenerateIdentifier(prefix, args); + return new Identifier(AccessModifier.Section, id); + } + + public Identifier CreateIdentifierFromFilename(string filename) + { + var id = Common.GetIdentifierFromName(filename); + return new Identifier(AccessModifier.Section, id); + } + + public string CreateIdentifierValueFromPlatform(string name, Platform currentPlatform, BurnPlatforms supportedPlatforms) + { + string suffix = null; + + switch (currentPlatform) + { + case Platform.X86: + if ((supportedPlatforms & BurnPlatforms.X86) == BurnPlatforms.X86) + { + suffix = "_X86"; + } + break; + case Platform.X64: + if ((supportedPlatforms & BurnPlatforms.X64) == BurnPlatforms.X64) + { + suffix = "_X64"; + } + break; + case Platform.ARM64: + if ((supportedPlatforms & BurnPlatforms.ARM64) == BurnPlatforms.ARM64) + { + suffix = "_A64"; + } + break; + } + + return suffix == null ? null : name + suffix; + } + + public Identifier CreateRegistrySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId, bool escapeLeadingHash) + { + if (RegistryRootType.Unknown == root) + { + throw new ArgumentOutOfRangeException(nameof(root)); + } + + if (null == key) + { + throw new ArgumentNullException(nameof(key)); + } + + if (null == componentId) + { + throw new ArgumentNullException(nameof(componentId)); + } + + // Escape the leading '#' character for string registry values. + if (escapeLeadingHash && null != value && value.StartsWith("#", StringComparison.Ordinal)) + { + value = String.Concat("#", value); + } + + var id = this.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), key.ToLowerInvariant(), (null != name ? name.ToLowerInvariant() : name)); + + var symbol = section.AddSymbol(new RegistrySymbol(sourceLineNumbers, id) + { + Root = root, + Key = key, + Name = name, + Value = value, + ComponentRef = componentId, + }); + + return symbol.Id; + } + + public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey) + { + section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers) + { + Table = symbolName, + PrimaryKeys = primaryKey + }); + } + + public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys) + { + section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers) + { + Table = symbolName, + PrimaryKeys = String.Join("/", primaryKeys) + }); + } + + public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey) + { + this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKey); + } + + public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys) + { + this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKeys); + } + + public void CreateWixGroupSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId) + { + if (null == parentId || ComplexReferenceParentType.Unknown == parentType) + { + return; + } + + if (null == childId) + { + throw new ArgumentNullException(nameof(childId)); + } + + section.AddSymbol(new WixGroupSymbol(sourceLineNumbers) + { + ParentId = parentId, + ParentType = parentType, + ChildId = childId, + ChildType = childType, + }); + } + + public void CreateWixSearchSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId) + { + // TODO: verify variable is not a standard bundle variable + if (variable == null) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable")); + } + + section.AddSymbol(new WixSearchSymbol(sourceLineNumbers, id) + { + Variable = variable, + Condition = condition, + BundleExtensionRef = bundleExtensionId, + }); + + if (after != null) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixSearch, after); + // TODO: We're currently defaulting to "always run after", which we will need to change... + this.CreateWixSearchRelationSymbol(section, sourceLineNumbers, id, after, 2); + } + + if (!String.IsNullOrEmpty(bundleExtensionId)) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixBundleExtension, bundleExtensionId); + } + } + + public void CreateWixSearchRelationSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, int attributes) + { + section.AddSymbol(new WixSearchRelationSymbol(sourceLineNumbers, id) + { + ParentSearchRef = parentId, + Attributes = attributes, + }); + } + + public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, Identifier identifier = null) + { + if (this.Creator == null) + { + this.CreateSymbolDefinitionCreator(); + } + + if (!this.Creator.TryGetSymbolDefinitionByName(symbolName, out var symbolDefinition)) + { + throw new ArgumentException(nameof(symbolName)); + } + + return this.CreateSymbol(section, sourceLineNumbers, symbolDefinition, identifier); + } + + public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, Identifier identifier = null) + { + return section.AddSymbol(symbolDefinition.CreateSymbol(sourceLineNumbers, identifier)); + } + + public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) + { + section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers) + { + Table = tableDefinition.Name, + }); + + // TODO: Check if the given table definition is a custom table. For now we have to assume that it isn't. + //this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableDefinition.Name); + } + + public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName) + { + section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers) + { + Table = tableName, + }); + + if (this.Creator == null) + { + this.CreateSymbolDefinitionCreator(); + } + + // TODO: The tableName may not be the same as the symbolName. For now, we have to assume that it is. + // We don't add custom table definitions to the tableDefinitions collection, + // so if it's not in there, it better be a custom table. If the Id is just wrong, + // instead of a custom table, we get an unresolved reference at link time. + if (!this.Creator.TryGetSymbolDefinitionByName(tableName, out var _)) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableName); + } + } + + public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false) + { + if (null == attribute) + { + throw new ArgumentNullException(nameof(attribute)); + } + + var emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly; + var value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule); + + if (String.IsNullOrEmpty(value)) + { + if (canBeEmpty) + { + return String.Empty; + } + } + else + { + if (generatable && value == "*") + { + return value; + } + + if (Guid.TryParse(value, out var guid)) + { + return guid.ToString("B").ToUpperInvariant(); + } + + if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal)) + { + return value; + } + + if (value.StartsWith("PUT-GUID-", StringComparison.OrdinalIgnoreCase) || + value.StartsWith("{PUT-GUID-", StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + else + { + this.Messaging.Write(ErrorMessages.IllegalGuidValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return CompilerConstants.IllegalGuid; + } + + public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var access = AccessModifier.Global; + var value = Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, EmptyRule.CanBeEmpty); + + var separator = value.IndexOf(' '); + if (separator > 0) + { + var prefix = value.Substring(0, separator); + switch (prefix) + { + case "global": + case "public": + case "package": + access = AccessModifier.Global; + break; + + case "internal": + case "library": + access = AccessModifier.Library; + break; + + case "file": + case "protected": + access = AccessModifier.File; + break; + + case "private": + case "fragment": + case "section": + access = AccessModifier.Section; + break; + + default: + return null; + } + + value = value.Substring(separator + 1).Trim(); + } + + if (!Common.IsIdentifier(value)) + { + this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + return null; + } + else if (72 < value.Length) + { + this.Messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + + return new Identifier(access, value); + } + + public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute); + } + + public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum) + { + return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum); + } + + public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards, bool allowRelative) + { + if (null == attribute) + { + throw new ArgumentNullException("attribute"); + } + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (!String.IsNullOrEmpty(value)) + { + if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value)) + { + if (allowRelative) + { + this.Messaging.Write(ErrorMessages.IllegalRelativeLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + else + { + this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + else if (allowRelative) + { + value = this.GetCanonicalRelativePath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value); + } + else if (CompilerCore.IsAmbiguousFilename(value)) + { + this.Messaging.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return value; + } + + public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum) + { + Debug.Assert(minimum > CompilerConstants.LongNotSet && minimum > CompilerConstants.IllegalLong, "The legal values for this attribute collide with at least one sentinel used during parsing."); + + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (0 < value.Length) + { + try + { + var longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture.NumberFormat); + + if (CompilerConstants.LongNotSet == longValue || CompilerConstants.IllegalLong == longValue) + { + this.Messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, longValue)); + } + else if (minimum > longValue || maximum < longValue) + { + this.Messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, longValue, minimum, maximum)); + longValue = CompilerConstants.IllegalLong; + } + + return longValue; + } + catch (FormatException) + { + this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + catch (OverflowException) + { + this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + } + + return CompilerConstants.IllegalLong; + } + + public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly) + { + return Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, emptyRule); + } + + public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu) + { + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + if (String.IsNullOrEmpty(value)) + { + return null; + } + + switch (value) + { + case "HKCR": + return RegistryRootType.ClassesRoot; + + case "HKCU": + return RegistryRootType.CurrentUser; + + case "HKLM": + return RegistryRootType.LocalMachine; + + case "HKU": + return RegistryRootType.Users; + + case "HKMU": + if (allowHkmu) + { + return RegistryRootType.MachineUser; + } + break; + } + + if (allowHkmu) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKMU", "HKCR", "HKCU", "HKLM", "HKU")); + } + else + { + this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKCR", "HKCU", "HKLM", "HKU")); + } + + return RegistryRootType.Unknown; + } + + public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + if (!String.IsNullOrEmpty(value)) + { + if (Version.TryParse(value, out var version)) + { + return version.ToString(); + } + + // Allow versions to contain binder variables. + if (Common.ContainsValidBinderVariable(value)) + { + return value; + } + + this.Messaging.Write(ErrorMessages.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + } + + return null; + } + + public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + switch (value) + { + case "yes": + case "true": + return YesNoDefaultType.Yes; + + case "no": + case "false": + return YesNoDefaultType.No; + + case "default": + return YesNoDefaultType.Default; + + default: + this.Messaging.Write(ErrorMessages.IllegalYesNoDefaultValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + return YesNoDefaultType.IllegalValue; + } + } + + public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute) + { + var value = this.GetAttributeValue(sourceLineNumbers, attribute); + + switch (value) + { + case "yes": + case "true": + return YesNoType.Yes; + + case "no": + case "false": + return YesNoType.No; + + default: + this.Messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value)); + return YesNoType.IllegalValue; + } + } + + public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) + { + return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging); + } + + public SourceLineNumber GetSourceLineNumbers(XElement element) + { + return Preprocessor.GetSourceLineNumbers(element); + } + + public string GetConditionInnerText(XElement element) + { + var value = Common.GetInnerText(element)?.Trim().Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '); + + // Return null for a non-existant condition. + return String.IsNullOrEmpty(value) ? null : value; + } + + public string GetTrimmedInnerText(XElement element) + { + var value = Common.GetInnerText(element); + return value?.Trim(); + } + + public void InnerTextDisallowed(XElement element) + { + if (element.Nodes().Any(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType)) + { + var innerText = Common.GetInnerText(element); + if (!String.IsNullOrWhiteSpace(innerText)) + { + var sourceLineNumbers = this.GetSourceLineNumbers(element); + this.Messaging.Write(ErrorMessages.IllegalInnerText(sourceLineNumbers, element.Name.LocalName, innerText)); + } + } + } + + public bool IsValidIdentifier(string value) + { + return Common.IsIdentifier(value); + } + + public bool IsValidLocIdentifier(string identifier) + { + return Common.TryParseWixVariable(identifier, 0, out var parsed) && parsed.Index == 0 && parsed.Length == identifier.Length && parsed.Namespace == "loc"; + } + + public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) + { + return Common.IsValidLongFilename(filename, allowWildcards, allowRelative); + } + + public bool IsValidShortFilename(string filename, bool allowWildcards) + { + return Common.IsValidShortFilename(filename, allowWildcards); + } + + public void ParseExtensionAttribute(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement element, XAttribute attribute, IDictionary context = null) + { + // Ignore attributes defined by the W3C because we'll assume they are always right. + if ((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) || + attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal)) + { + return; + } + + if (ParseHelper.TryFindExtension(extensions, attribute.Name.NamespaceName, out var extension)) + { + extension.ParseAttribute(intermediate, section, element, attribute, context); + } + else + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); + this.Messaging.Write(ErrorMessages.UnhandledExtensionAttribute(sourceLineNumbers, element.Name.LocalName, attribute.Name.LocalName, attribute.Name.NamespaceName)); + } + } + + public void ParseExtensionElement(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context = null) + { + if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) + { + extension.ParseElement(intermediate, section, parentElement, element, context); + } + else + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); + this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); + } + } + + public IComponentKeyPath ParsePossibleKeyPathExtensionElement(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) + { + IComponentKeyPath keyPath = null; + + if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension)) + { + keyPath = extension.ParsePossibleKeyPathElement(intermediate, section, parentElement, element, context); + } + else + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); + this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName)); + } + + return keyPath; + } + + public void ParseForExtensionElements(IEnumerable extensions, Intermediate intermediate, IntermediateSection section, XElement element) + { + var checkInnerText = false; + + foreach (var child in element.Nodes()) + { + if (child is XElement childElement) + { + if (element.Name.Namespace == childElement.Name.Namespace) + { + this.UnexpectedElement(element, childElement); + } + else + { + this.ParseExtensionElement(extensions, intermediate, section, element, childElement); + } + } + else + { + checkInnerText = true; + } + } + + if (checkInnerText) + { + this.InnerTextDisallowed(element); + } + } + + public WixActionSymbol ScheduleActionSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition, string beforeAction, string afterAction, bool overridable = false) + { + var actionId = new Identifier(access, sequence, actionName); + + var actionSymbol = section.AddSymbol(new WixActionSymbol(sourceLineNumbers, actionId) + { + SequenceTable = sequence, + Action = actionName, + Condition = condition, + Before = beforeAction, + After = afterAction, + Overridable = overridable, + }); + + if (null != beforeAction) + { + if (WindowsInstallerStandard.IsStandardAction(beforeAction)) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), beforeAction); + } + else + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, beforeAction); + } + } + + if (null != afterAction) + { + if (WindowsInstallerStandard.IsStandardAction(afterAction)) + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), afterAction); + } + else + { + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, afterAction); + } + } + + return actionSymbol; + } + + public void CreateCustomActionReference(SourceLineNumber sourceLineNumbers, IntermediateSection section, string customAction, Platform currentPlatform, CustomActionPlatforms supportedPlatforms) + { + if (!this.Messaging.EncounteredError) + { + var suffix = "_X86"; + + switch (currentPlatform) + { + case Platform.X64: + if ((supportedPlatforms & CustomActionPlatforms.X64) == CustomActionPlatforms.X64) + { + suffix = "_X64"; + } + break; + case Platform.ARM64: + if ((supportedPlatforms & CustomActionPlatforms.ARM64) == CustomActionPlatforms.ARM64) + { + suffix = "_A64"; + } + break; + } + + this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, customAction + suffix); + } + } + + public void UnexpectedAttribute(XElement element, XAttribute attribute) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element); + Common.UnexpectedAttribute(this.Messaging, sourceLineNumbers, attribute); + } + + public void UnexpectedElement(XElement parentElement, XElement childElement) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(childElement); + this.Messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, parentElement.Name.LocalName, childElement.Name.LocalName)); + } + + private void CreateSymbolDefinitionCreator() + { + this.Creator = this.ServiceProvider.GetService(); + } + + private static bool TryFindExtension(IEnumerable extensions, XNamespace ns, out ICompilerExtension extension) + { + extension = null; + + foreach (var ext in extensions) + { + if (ext.Namespace == ns) + { + extension = ext; + break; + } + } + + return extension != null; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs b/src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs new file mode 100644 index 00000000..72be2bcb --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs @@ -0,0 +1,118 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class PathResolver : IPathResolver + { + public string GetCanonicalDirectoryPath(Dictionary directories, Dictionary componentIdGenSeeds, string directory, Platform platform) + { + if (!directories.TryGetValue(directory, out var resolvedDirectory)) + { + throw new WixException(ErrorMessages.ExpectedDirectory(directory)); + } + + if (null == resolvedDirectory.Path) + { + if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(directory)) + { + resolvedDirectory.Path = componentIdGenSeeds[directory]; + } + else if (WindowsInstallerStandard.IsStandardDirectory(directory)) + { + resolvedDirectory.Path = WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directory, platform); + } + else + { + var name = resolvedDirectory.Name?.ToLowerInvariant(); + + if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) + { + resolvedDirectory.Path = name; + } + else + { + var parentPath = this.GetCanonicalDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, platform); + + if (null != resolvedDirectory.Name) + { + resolvedDirectory.Path = Path.Combine(parentPath, name); + } + else + { + resolvedDirectory.Path = parentPath; + } + } + } + } + + return resolvedDirectory.Path; + } + + public string GetDirectoryPath(Dictionary directories, string directory) + { + if (!directories.TryGetValue(directory, out var resolvedDirectory)) + { + throw new WixException(ErrorMessages.ExpectedDirectory(directory)); + } + + if (null == resolvedDirectory.Path) + { + var name = resolvedDirectory.Name; + + if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent)) + { + resolvedDirectory.Path = name; + } + else + { + var parentPath = this.GetDirectoryPath(directories, resolvedDirectory.DirectoryParent); + + if (null != resolvedDirectory.Name) + { + resolvedDirectory.Path = Path.Combine(parentPath, name); + } + else + { + resolvedDirectory.Path = parentPath; + } + } + } + + return resolvedDirectory.Path; + } + + public string GetFileSourcePath(Dictionary directories, string directoryId, string fileName, bool compressed, bool useLongName) + { + var fileSourcePath = Common.GetName(fileName, true, useLongName); + + if (compressed) + { + // Use just the file name of the file since all uncompressed files must appear + // in the root of the image in a compressed package. + } + else + { + // Get the relative path of where we want the file to be layed out as specified + // in the Directory table. + var directoryPath = this.GetDirectoryPath(directories, directoryId); + fileSourcePath = Path.Combine(directoryPath, fileSourcePath); + } + + // Strip off "SourceDir" if it's still on there. + if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal)) + { + fileSourcePath = fileSourcePath.Substring(10); + } + + return fileSourcePath; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs b/src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs new file mode 100644 index 00000000..b0c87bcf --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs @@ -0,0 +1,499 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class PreprocessHelper : IPreprocessHelper + { + private static readonly char[] VariableSplitter = new char[] { '.' }; + private static readonly char[] ArgumentSplitter = new char[] { ',' }; + + public PreprocessHelper(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = this.ServiceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private Dictionary ExtensionsByPrefix { get; set; } + + public void AddVariable(IPreprocessContext context, string name, string value) + { + this.AddVariable(context, name, value, true); + } + + public void AddVariable(IPreprocessContext context, string name, string value, bool showWarning) + { + var currentValue = this.GetVariableValue(context, "var", name); + + if (null == currentValue) + { + context.Variables.Add(name, value); + } + else + { + if (showWarning && value != currentValue) + { + this.Messaging.Write(WarningMessages.VariableDeclarationCollision(context.CurrentSourceLineNumber, name, value, currentValue)); + } + + context.Variables[name] = value; + } + } + + public string EvaluateFunction(IPreprocessContext context, string function) + { + var prefixParts = function.Split(VariableSplitter, 2); + + // Check to make sure there are 2 parts and neither is an empty string. + if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length) + { + throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); + } + + var prefix = prefixParts[0]; + var functionParts = prefixParts[1].Split(new char[] { '(' }, 2); + + // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren. + if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal)) + { + throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function)); + } + + var functionName = functionParts[0]; + + // Remove the trailing closing paren. + var allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1); + + // Parse the arguments and preprocess them. + var args = allArgs.Split(ArgumentSplitter); + for (var i = 0; i < args.Length; i++) + { + args[i] = this.PreprocessString(context, args[i].Trim()); + } + + var result = this.EvaluateFunction(context, prefix, functionName, args); + + // If the function didn't evaluate, try to evaluate the original value as a variable to support + // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve. + if (result == null) + { + result = this.GetVariableValue(context, function, true); + } + + return result; + } + + public string EvaluateFunction(IPreprocessContext context, string prefix, string function, string[] args) + { + if (String.IsNullOrEmpty(prefix)) + { + throw new ArgumentNullException("prefix"); + } + + if (String.IsNullOrEmpty(function)) + { + throw new ArgumentNullException("function"); + } + + switch (prefix) + { + case "fun": + switch (function) + { + case "AutoVersion": + // Make sure the base version is specified + if (args.Length == 0 || String.IsNullOrEmpty(args[0])) + { + throw new WixException(ErrorMessages.InvalidPreprocessorFunctionAutoVersion(context.CurrentSourceLineNumber)); + } + + // Build = days since 1/1/2000; Revision = seconds since midnight / 2 + var now = DateTime.UtcNow; + var build = now - new DateTime(2000, 1, 1); + var revision = now - new DateTime(now.Year, now.Month, now.Day); + + return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2)); + + default: + return null; + } + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + try + { + return extension.EvaluateFunction(prefix, function, args); + } + catch (Exception e) + { + throw new WixException(ErrorMessages.PreprocessorExtensionEvaluateFunctionFailed(context.CurrentSourceLineNumber, prefix, function, String.Join(",", args), e.Message)); + } + } + else + { + return null; + } + } + } + + public string GetVariableValue(IPreprocessContext context, string variable, bool allowMissingPrefix) + { + // Strip the "$(" off the front and the ")" off the back. + if (variable.StartsWith("$(", StringComparison.Ordinal)) + { + variable = variable.Substring(2, variable.Length - 3); + } + + var parts = variable.Split(VariableSplitter, 2); + + if (1 == parts.Length) // missing prefix + { + if (allowMissingPrefix) + { + return this.GetVariableValue(context, "var", parts[0]); + } + else + { + throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); + } + } + else + { + // check for empty variable name + if (0 < parts[1].Length) + { + string result = this.GetVariableValue(context, parts[0], parts[1]); + + // If we didn't find it and we allow missing prefixes and the variable contains a dot, perhaps the dot isn't intended to indicate a prefix + if (null == result && allowMissingPrefix && variable.Contains(".")) + { + result = this.GetVariableValue(context, "var", variable); + } + + return result; + } + else + { + throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable)); + } + } + } + + public string GetVariableValue(IPreprocessContext context, string prefix, string name) + { + if (String.IsNullOrEmpty(prefix)) + { + throw new ArgumentNullException("prefix"); + } + + if (String.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + switch (prefix) + { + case "env": + return Environment.GetEnvironmentVariable(name); + + case "sys": + switch (name) + { + case "CURRENTDIR": + return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar); + + case "SOURCEFILEDIR": + return String.Concat(Path.GetDirectoryName(context.CurrentSourceLineNumber.FileName), Path.DirectorySeparatorChar); + + case "SOURCEFILEPATH": + return context.CurrentSourceLineNumber.FileName; + + case "PLATFORM": + this.Messaging.Write(WarningMessages.DeprecatedPreProcVariable(context.CurrentSourceLineNumber, "$(sys.PLATFORM)", "$(sys.BUILDARCH)")); + + goto case "BUILDARCH"; + + case "BUILDARCH": + switch (context.Platform) + { + case Platform.X86: + return "x86"; + + case Platform.X64: + return "x64"; + + case Platform.ARM64: + return "arm64"; + + default: + throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", context.Platform.ToString()); + } + + case "WIXMAJORVERSION": + return ThisAssembly.AssemblyFileVersion.Split('.')[0]; + + case "WIXVERSION": + return ThisAssembly.AssemblyFileVersion; + + default: + return null; + } + + case "var": + return context.Variables.TryGetValue(name, out var result) ? result : null; + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + try + { + return extension.GetVariableValue(prefix, name); + } + catch (Exception e) + { + throw new WixException(ErrorMessages.PreprocessorExtensionGetVariableValueFailed(context.CurrentSourceLineNumber, prefix, name, e.Message)); + } + } + else + { + return null; + } + } + } + + public void PreprocessPragma(IPreprocessContext context, string pragmaName, string args, XContainer parent) + { + var prefixParts = pragmaName.Split(VariableSplitter, 2); + + // Check to make sure there are 2 parts and neither is an empty string. + if (2 != prefixParts.Length) + { + throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); + } + + var prefix = prefixParts[0]; + var pragma = prefixParts[1]; + + if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma)) + { + throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName)); + } + + switch (prefix) + { + case "wix": + switch (pragma) + { + // Add any core defined pragmas here + default: + this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); + break; + } + break; + + default: + var extensionsByPrefix = this.GetExtensionsByPrefix(); + if (extensionsByPrefix.TryGetValue(prefix, out var extension)) + { + if (!extension.ProcessPragma(prefix, pragma, args, parent)) + { + this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName)); + } + } + break; + } + } + + public string PreprocessString(IPreprocessContext context, string value) + { + var sb = new StringBuilder(); + var currentPosition = 0; + var end = 0; + + while (-1 != (currentPosition = value.IndexOf('$', end))) + { + if (end < currentPosition) + { + sb.Append(value, end, currentPosition - end); + } + + end = currentPosition + 1; + + var remainder = value.Substring(end); + if (remainder.StartsWith("$", StringComparison.Ordinal)) + { + sb.Append("$"); + end++; + } + else if (remainder.StartsWith("(loc.", StringComparison.Ordinal)) + { + currentPosition = remainder.IndexOf(')'); + if (-1 == currentPosition) + { + this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); + break; + } + + sb.Append("$"); // just put the resource reference back as was + sb.Append(remainder, 0, currentPosition + 1); + + end += currentPosition + 1; + } + else if (remainder.StartsWith("(", StringComparison.Ordinal)) + { + var openParenCount = 1; + var closingParenCount = 0; + var isFunction = false; + var foundClosingParen = false; + + // find the closing paren + int closingParenPosition; + for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++) + { + switch (remainder[closingParenPosition]) + { + case '(': + openParenCount++; + isFunction = true; + break; + + case ')': + closingParenCount++; + break; + } + + if (openParenCount == closingParenCount) + { + foundClosingParen = true; + break; + } + } + + // Environment variables may contain parens so if it looks + // like a function, check to see if the environment variable + // prefix was explicitly provided. + if (isFunction && remainder.StartsWith("(env.", StringComparison.Ordinal)) + { + isFunction = false; + } + + // move the currentPosition to the closing paren + currentPosition += closingParenPosition; + + if (!foundClosingParen) + { + if (isFunction) + { + this.Messaging.Write(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, remainder)); + break; + } + else + { + this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder)); + break; + } + } + + var subString = remainder.Substring(1, closingParenPosition - 1); + string result = null; + if (isFunction) + { + result = this.EvaluateFunction(context, subString); + } + else + { + result = this.GetVariableValue(context, subString, true); + } + + if (null == result) + { + if (isFunction) + { + this.Messaging.Write(ErrorMessages.UndefinedPreprocessorFunction(context.CurrentSourceLineNumber, subString)); + break; + } + else + { + this.Messaging.Write(ErrorMessages.UndefinedPreprocessorVariable(context.CurrentSourceLineNumber, subString)); + break; + } + } + else + { + if (!isFunction) + { + //this.OnResolvedVariable(new ResolvedVariableEventArgs(context.CurrentSourceLineNumber, subString, result)); + } + } + + sb.Append(result); + end += closingParenPosition + 1; + } + else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing + { + sb.Append('$'); + } + } + + if (end < value.Length) + { + sb.Append(value.Substring(end)); + } + + return sb.ToString(); + } + + public void RemoveVariable(IPreprocessContext context, string name) + { + if (!context.Variables.Remove(name)) + { + this.Messaging.Write(ErrorMessages.CannotReundefineVariable(context.CurrentSourceLineNumber, name)); + } + } + + private Dictionary GetExtensionsByPrefix() + { + if (this.ExtensionsByPrefix == null) + { + this.ExtensionsByPrefix = new Dictionary(); + + var extensionManager = this.ServiceProvider.GetService(); + + var extensions = extensionManager.GetServices(); + + foreach (var extension in extensions) + { + if (null != extension.Prefixes) + { + foreach (string prefix in extension.Prefixes) + { + if (!this.ExtensionsByPrefix.ContainsKey(prefix)) + { + this.ExtensionsByPrefix.Add(prefix, extension); + } + } + } + } + } + + return this.ExtensionsByPrefix; + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs new file mode 100644 index 00000000..cc8acfdd --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs @@ -0,0 +1,15 @@ +// 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.ExtensibilityServices +{ + using WixToolset.Extensibility.Data; + + internal class ResolvedDirectory : IResolvedDirectory + { + public string DirectoryParent { get; set; } + + public string Name { get; set; } + + public string Path { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs b/src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs new file mode 100644 index 00000000..a2486130 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs @@ -0,0 +1,70 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class SymbolDefinitionCreator : ISymbolDefinitionCreator + { + public SymbolDefinitionCreator(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + private IServiceProvider ServiceProvider { get; } + + private IEnumerable ExtensionData { get; set; } + + private Dictionary CustomDefinitionByName { get; } = new Dictionary(); + + public void AddCustomSymbolDefinition(IntermediateSymbolDefinition definition) + { + if (!this.CustomDefinitionByName.TryGetValue(definition.Name, out var existing) || definition.Revision > existing.Revision) + { + this.CustomDefinitionByName[definition.Name] = definition; + } + } + + public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition) + { + // First, look in the built-ins. + symbolDefinition = SymbolDefinitions.ByName(name); + + if (symbolDefinition == null) + { + if (this.ExtensionData == null) + { + this.LoadExtensionData(); + } + + // Second, look in the extensions. + foreach (var data in this.ExtensionData) + { + if (data.TryGetSymbolDefinitionByName(name, out symbolDefinition)) + { + break; + } + } + + // Finally, look in the custom symbol definitions provided during an intermediate load. + if (symbolDefinition == null) + { + this.CustomDefinitionByName.TryGetValue(name, out symbolDefinition); + } + } + + return symbolDefinition != null; + } + + private void LoadExtensionData() + { + var extensionManager = this.ServiceProvider.GetService(); + + this.ExtensionData = extensionManager.GetServices(); + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs b/src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs new file mode 100644 index 00000000..028cddbf --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs @@ -0,0 +1,26 @@ +// 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.ExtensibilityServices +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class TrackedFile : ITrackedFile + { + public TrackedFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers) + { + this.Path = path; + this.Type = type; + this.SourceLineNumbers = sourceLineNumbers; + this.Clean = (type == TrackedFileType.Intermediate || type == TrackedFileType.Final); + } + + public bool Clean { get; set; } + + public string Path { get; set; } + + public SourceLineNumber SourceLineNumbers { get; set; } + + public TrackedFileType Type { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs b/src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs new file mode 100644 index 00000000..ad9eea26 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs @@ -0,0 +1,81 @@ +// 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.ExtensibilityServices +{ + using System; + using System.Net; + using System.Security.Cryptography; + using System.Text; + + /// + /// Implementation of RFC 4122 - A Universally Unique Identifier (UUID) URN Namespace. + /// + internal static class Uuid + { + /// + /// Creates a version 3 name-based UUID. + /// + /// The namespace UUID. + /// The value. + /// The UUID for the given namespace and value. + public static Guid NewUuid(Guid namespaceGuid, string value) + { + byte[] namespaceBytes = namespaceGuid.ToByteArray(); + short uuidVersion = (short)0x5000; + + // get the fields of the guid which are in host byte ordering + int timeLow = BitConverter.ToInt32(namespaceBytes, 0); + short timeMid = BitConverter.ToInt16(namespaceBytes, 4); + short timeHiAndVersion = BitConverter.ToInt16(namespaceBytes, 6); + + // convert to network byte ordering + timeLow = IPAddress.HostToNetworkOrder(timeLow); + timeMid = IPAddress.HostToNetworkOrder(timeMid); + timeHiAndVersion = IPAddress.HostToNetworkOrder(timeHiAndVersion); + + // get the bytes from the value + byte[] valueBytes = Encoding.Unicode.GetBytes(value); + + // fill-in the hash input buffer + byte[] buffer = new byte[namespaceBytes.Length + valueBytes.Length]; + Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, buffer, 0, 4); + Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, buffer, 4, 2); + Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, buffer, 6, 2); + Buffer.BlockCopy(namespaceBytes, 8, buffer, 8, 8); + Buffer.BlockCopy(valueBytes, 0, buffer, 16, valueBytes.Length); + + // perform the appropriate hash of the namespace and value + byte[] hash; + using (SHA1 sha1 = SHA1.Create()) + { + hash = sha1.ComputeHash(buffer); + } + + // get the fields of the hash which are in network byte ordering + timeLow = BitConverter.ToInt32(hash, 0); + timeMid = BitConverter.ToInt16(hash, 4); + timeHiAndVersion = BitConverter.ToInt16(hash, 6); + + // convert to network byte ordering + timeLow = IPAddress.NetworkToHostOrder(timeLow); + timeMid = IPAddress.NetworkToHostOrder(timeMid); + timeHiAndVersion = IPAddress.NetworkToHostOrder(timeHiAndVersion); + + // set the version and variant bits + timeHiAndVersion &= 0x0FFF; + timeHiAndVersion += uuidVersion; + hash[8] &= 0x3F; + hash[8] |= 0x80; + + // put back the converted values into a 128-bit value + byte[] guidBits = new byte[16]; + Buffer.BlockCopy(hash, 0, guidBits, 0, 16); + + Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, guidBits, 0, 4); + Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, guidBits, 4, 2); + Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, guidBits, 6, 2); + + return new Guid(guidBits); + } + } +} diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs b/src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs new file mode 100644 index 00000000..56300400 --- /dev/null +++ b/src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs @@ -0,0 +1,124 @@ +// 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. + +using System.Resources; + +[assembly: NeutralResourcesLanguage("en-US")] + +namespace WixToolset.Core.ExtensibilityServices +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Reflection; + using WixToolset.Extensibility.Services; + + /// + /// Branding strings. + /// + internal class WixBranding : IWixBranding + { + /// + /// News URL for the distribution. + /// + public static string NewsUrl = "http://wixtoolset.org/news/"; + + /// + /// Short product name for the distribution. + /// + public static string ShortProduct = "WiX Toolset"; + + /// + /// Support URL for the distribution. + /// + public static string SupportUrl = "http://wixtoolset.org/"; + + /// + /// Telemetry URL format for the distribution. + /// + public static string TelemetryUrlFormat = "http://wixtoolset.org/telemetry/v{0}/?r={1}"; + + /// + /// VS Extensions Landing page Url for the distribution. + /// + public static string VSExtensionsLandingUrl = "http://wixtoolset.org/releases/"; + + public string GetCreatingApplication() + { + return this.ReplacePlaceholders("[AssemblyProduct] ([FileVersion])"); + } + + public string ReplacePlaceholders(string original, Assembly assembly = null) + { + if (assembly == null) + { + assembly = typeof(WixBranding).Assembly; + } + + var commonVersionPath = Path.Combine(Path.GetDirectoryName(typeof(WixBranding).Assembly.Location), "wixver.dll"); + if (File.Exists(commonVersionPath)) + { + var commonFileVersion = FileVersionInfo.GetVersionInfo(commonVersionPath); + + original = original.Replace("[FileCopyright]", commonFileVersion.LegalCopyright); + original = original.Replace("[FileVersion]", commonFileVersion.FileVersion); + } + + var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location); + + original = original.Replace("[FileComments]", fileVersion.Comments); + original = original.Replace("[FileCopyright]", fileVersion.LegalCopyright); + original = original.Replace("[FileProductName]", fileVersion.ProductName); + original = original.Replace("[FileVersion]", fileVersion.FileVersion); + + if (original.Contains("[FileVersionMajorMinor]")) + { + var version = new Version(fileVersion.FileVersion); + original = original.Replace("[FileVersionMajorMinor]", String.Concat(version.Major, ".", version.Minor)); + } + + if (TryGetAttribute(assembly, out AssemblyCompanyAttribute company)) + { + original = original.Replace("[AssemblyCompany]", company.Company); + } + + if (TryGetAttribute(assembly, out AssemblyCopyrightAttribute copyright)) + { + original = original.Replace("[AssemblyCopyright]", copyright.Copyright); + } + + if (TryGetAttribute(assembly, out AssemblyDescriptionAttribute description)) + { + original = original.Replace("[AssemblyDescription]", description.Description); + } + + if (TryGetAttribute(assembly, out AssemblyProductAttribute product)) + { + original = original.Replace("[AssemblyProduct]", product.Product); + } + + if (TryGetAttribute(assembly, out AssemblyTitleAttribute title)) + { + original = original.Replace("[AssemblyTitle]", title.Title); + } + + original = original.Replace("[NewsUrl]", NewsUrl); + original = original.Replace("[ShortProduct]", ShortProduct); + original = original.Replace("[SupportUrl]", SupportUrl); + + return original; + } + + private static bool TryGetAttribute(Assembly assembly, out T attribute) where T : Attribute + { + attribute = null; + + var customAttributes = assembly.GetCustomAttributes(typeof(T), false); + if (null != customAttributes && 0 < customAttributes.Length) + { + attribute = customAttributes[0] as T; + } + + return null != attribute; + } + } +} diff --git a/src/wix/WixToolset.Core/IBinder.cs b/src/wix/WixToolset.Core/IBinder.cs new file mode 100644 index 00000000..a1b66f42 --- /dev/null +++ b/src/wix/WixToolset.Core/IBinder.cs @@ -0,0 +1,12 @@ +// 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 +{ + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface IBinder + { + IBindResult Bind(IBindContext context); + } +} diff --git a/src/wix/WixToolset.Core/ICompiler.cs b/src/wix/WixToolset.Core/ICompiler.cs new file mode 100644 index 00000000..0aae579a --- /dev/null +++ b/src/wix/WixToolset.Core/ICompiler.cs @@ -0,0 +1,13 @@ +// 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 +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface ICompiler + { + Intermediate Compile(ICompileContext context); + } +} diff --git a/src/wix/WixToolset.Core/IDecompiler.cs b/src/wix/WixToolset.Core/IDecompiler.cs new file mode 100644 index 00000000..74ec26de --- /dev/null +++ b/src/wix/WixToolset.Core/IDecompiler.cs @@ -0,0 +1,12 @@ +// 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 +{ + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface IDecompiler + { + IDecompileResult Decompile(IDecompileContext context); + } +} diff --git a/src/wix/WixToolset.Core/ILayoutCreator.cs b/src/wix/WixToolset.Core/ILayoutCreator.cs new file mode 100644 index 00000000..cdff2a78 --- /dev/null +++ b/src/wix/WixToolset.Core/ILayoutCreator.cs @@ -0,0 +1,12 @@ +// 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 +{ + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface ILayoutCreator + { + void Layout(ILayoutContext context); + } +} diff --git a/src/wix/WixToolset.Core/ILibrarian.cs b/src/wix/WixToolset.Core/ILibrarian.cs new file mode 100644 index 00000000..0fcedea5 --- /dev/null +++ b/src/wix/WixToolset.Core/ILibrarian.cs @@ -0,0 +1,13 @@ +// 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 +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface ILibrarian + { + Intermediate Combine(ILibraryContext context); + } +} diff --git a/src/wix/WixToolset.Core/ILinker.cs b/src/wix/WixToolset.Core/ILinker.cs new file mode 100644 index 00000000..11cc2c87 --- /dev/null +++ b/src/wix/WixToolset.Core/ILinker.cs @@ -0,0 +1,13 @@ +// 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 +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation + public interface ILinker + { + Intermediate Link(ILinkContext context); + } +} diff --git a/src/wix/WixToolset.Core/ILocalizationParser.cs b/src/wix/WixToolset.Core/ILocalizationParser.cs new file mode 100644 index 00000000..0e70aa0e --- /dev/null +++ b/src/wix/WixToolset.Core/ILocalizationParser.cs @@ -0,0 +1,27 @@ +// 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 +{ + using System.Xml.Linq; + using WixToolset.Data; + + /// + /// Parses localization source files. + /// + public interface ILocalizationParser + { + /// + /// Loads a localization file from a path on disk. + /// + /// Path to localization file saved on disk. + /// Returns the loaded localization file. + Localization ParseLocalization(string path); + + /// + /// Loads a localization file from memory. + /// + /// Document to parse as localization file. + /// Returns the loaded localization file. + Localization ParseLocalization(XDocument document); + } +} diff --git a/src/wix/WixToolset.Core/IPreprocessor.cs b/src/wix/WixToolset.Core/IPreprocessor.cs new file mode 100644 index 00000000..f6ed5fed --- /dev/null +++ b/src/wix/WixToolset.Core/IPreprocessor.cs @@ -0,0 +1,15 @@ +// 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 +{ + using System.Xml; + using WixToolset.Extensibility.Data; + +#pragma warning disable 1591 // TODO: add documentation, move into Extensibility + public interface IPreprocessor + { + IPreprocessResult Preprocess(IPreprocessContext context); + + IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader); + } +} diff --git a/src/wix/WixToolset.Core/IResolver.cs b/src/wix/WixToolset.Core/IResolver.cs new file mode 100644 index 00000000..db25edbe --- /dev/null +++ b/src/wix/WixToolset.Core/IResolver.cs @@ -0,0 +1,19 @@ +// 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 +{ + using WixToolset.Extensibility.Data; + + /// + /// Resolves localization and bind variables. + /// + public interface IResolver + { + /// + /// Resolve localization and bind variables. + /// + /// Resolve context. + /// Resolve result. + IResolveResult Resolve(IResolveContext context); + } +} diff --git a/src/wix/WixToolset.Core/IUnbinder.cs b/src/wix/WixToolset.Core/IUnbinder.cs new file mode 100644 index 00000000..2b4daaa5 --- /dev/null +++ b/src/wix/WixToolset.Core/IUnbinder.cs @@ -0,0 +1,12 @@ +// 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 +{ + using WixToolset.Data; + +#pragma warning disable 1591 // TODO: add documentation, move into Extensibility + public interface IUnbinder + { + Intermediate Unbind(string file, OutputType outputType, string exportBasePath); + } +} diff --git a/src/wix/WixToolset.Core/IncludedFile.cs b/src/wix/WixToolset.Core/IncludedFile.cs new file mode 100644 index 00000000..25d51191 --- /dev/null +++ b/src/wix/WixToolset.Core/IncludedFile.cs @@ -0,0 +1,14 @@ +// 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 +{ + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class IncludedFile : IIncludedFile + { + public string Path { get; set; } + + public SourceLineNumber SourceLineNumbers { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/IncribeContext.cs b/src/wix/WixToolset.Core/IncribeContext.cs new file mode 100644 index 00000000..9d7055ab --- /dev/null +++ b/src/wix/WixToolset.Core/IncribeContext.cs @@ -0,0 +1,26 @@ +// 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 +{ + using System; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class InscribeContext : IInscribeContext + { + public InscribeContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public string IntermediateFolder { get; set; } + + public string InputFilePath { get; set; } + + public string SignedEngineFile { get; set; } + + public string OutputFile { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/LayoutContext.cs b/src/wix/WixToolset.Core/LayoutContext.cs new file mode 100644 index 00000000..4b8c7b99 --- /dev/null +++ b/src/wix/WixToolset.Core/LayoutContext.cs @@ -0,0 +1,40 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Threading; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class LayoutContext : ILayoutContext + { + internal LayoutContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IReadOnlyCollection Extensions { get; set; } + + public IReadOnlyCollection FileSystemExtensions { get; set; } + + public IReadOnlyCollection FileTransfers { get; set; } + + public IReadOnlyCollection TrackedFiles { get; set; } + + public string IntermediateFolder { get; set; } + + public string ContentsFile { get; set; } + + public string OutputsFile { get; set; } + + public string BuiltOutputsFile { get; set; } + + public bool ResetAcls { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/LayoutCreator.cs b/src/wix/WixToolset.Core/LayoutCreator.cs new file mode 100644 index 00000000..0c5aaf63 --- /dev/null +++ b/src/wix/WixToolset.Core/LayoutCreator.cs @@ -0,0 +1,223 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Layout for the WiX toolset. + /// + internal class LayoutCreator : ILayoutCreator + { + internal LayoutCreator(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + private IMessaging Messaging { get; } + + public void Layout(ILayoutContext context) + { + // Pre-layout. + // + foreach (var extension in context.Extensions) + { + extension.PreLayout(context); + } + + try + { + // Final step in binding that transfers (moves/copies) all files generated into the appropriate + // location in the source image. + if (context.FileTransfers?.Any() == true) + { + this.Messaging.Write(VerboseMessages.LayingOutMedia()); + + var command = new TransferFilesCommand(this.Messaging, context.Extensions, context.FileTransfers, context.ResetAcls); + command.Execute(); + } + + if (context.TrackedFiles != null) + { + this.CleanTempFiles(context.IntermediateFolder, context.TrackedFiles); + } + } + finally + { + if (context.TrackedFiles != null) + { + if (!String.IsNullOrEmpty(context.ContentsFile)) + { + this.CreateContentsFile(context.ContentsFile, context.TrackedFiles); + } + + if (!String.IsNullOrEmpty(context.OutputsFile)) + { + this.CreateOutputsFile(context.OutputsFile, context.TrackedFiles); + } + + if (!String.IsNullOrEmpty(context.BuiltOutputsFile)) + { + this.CreateBuiltOutputsFile(context.BuiltOutputsFile, context.TrackedFiles); + } + } + } + + // Post-layout. + foreach (var extension in context.Extensions) + { + extension.PostLayout(); + } + } + + /// + /// Writes the paths to the content files to a text file. + /// + /// Path to write file. + /// Collection of paths to content files that will be written to file. + private void CreateContentsFile(string path, IEnumerable trackedFiles) + { + var uniqueInputFilePaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Input).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); + + if (!uniqueInputFilePaths.Any()) + { + return; + } + + var directory = Path.GetDirectoryName(path); + Directory.CreateDirectory(directory); + + using (var contents = new StreamWriter(path, false)) + { + foreach (var inputPath in uniqueInputFilePaths) + { + contents.WriteLine(inputPath); + } + } + } + + /// + /// Writes the paths to the output files to a text file. + /// + /// Path to write file. + /// Collection of files that were transferred to the output directory. + private void CreateOutputsFile(string path, IEnumerable trackedFiles) + { + var uniqueOutputPaths = new SortedSet(trackedFiles.Where(t => t.Clean).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); + + if (!uniqueOutputPaths.Any()) + { + return; + } + + var directory = Path.GetDirectoryName(path); + Directory.CreateDirectory(directory); + + using (var outputs = new StreamWriter(path, false)) + { + //// Don't list files where the source is the same as the destination since + //// that might be the only place the file exists. The outputs file is often + //// used to delete stuff and losing the original source would be bad. + //var uniqueOutputPaths = new SortedSet(fileTransfers.Where(ft => !ft.Redundant).Select(ft => ft.Destination), StringComparer.OrdinalIgnoreCase); + + foreach (var outputPath in uniqueOutputPaths) + { + outputs.WriteLine(outputPath); + } + } + } + + /// + /// Writes the paths to the built output files to a text file. + /// + /// Path to write file. + /// Collection of files that were transferred to the output directory. + private void CreateBuiltOutputsFile(string path, IEnumerable trackedFiles) + { + var uniqueBuiltPaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Final).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); + + if (!uniqueBuiltPaths.Any()) + { + return; + } + + var directory = Path.GetDirectoryName(path); + Directory.CreateDirectory(directory); + + using (var outputs = new StreamWriter(path, false)) + { + foreach (var builtPath in uniqueBuiltPaths) + { + outputs.WriteLine(builtPath); + } + } + } + + private void CleanTempFiles(string intermediateFolder, IEnumerable trackedFiles) + { + var uniqueTempPaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Temporary).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); + + if (!uniqueTempPaths.Any()) + { + return; + } + + var uniqueFolders = new SortedSet(StringComparer.OrdinalIgnoreCase) + { + intermediateFolder + }; + + // Clean up temp files. + foreach (var tempPath in uniqueTempPaths) + { + try + { + this.SplitUniqueFolders(intermediateFolder, tempPath, uniqueFolders); + + File.Delete(tempPath); + } + catch // delete is best effort. + { + } + } + + // Clean up empty temp folders. + foreach (var folder in uniqueFolders.Reverse()) + { + try + { + Directory.Delete(folder); + } + catch // delete is best effort. + { + } + } + } + + private void SplitUniqueFolders(string intermediateFolder, string tempPath, SortedSet uniqueFolders) + { + if (tempPath.StartsWith(intermediateFolder, StringComparison.OrdinalIgnoreCase)) + { + var folder = Path.GetDirectoryName(tempPath).Substring(intermediateFolder.Length); + + var parts = folder.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); + + folder = intermediateFolder; + + foreach (var part in parts) + { + folder = Path.Combine(folder, part); + + uniqueFolders.Add(folder); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/Librarian.cs b/src/wix/WixToolset.Core/Librarian.cs new file mode 100644 index 00000000..1dd1b44d --- /dev/null +++ b/src/wix/WixToolset.Core/Librarian.cs @@ -0,0 +1,135 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Core.Bind; + using WixToolset.Core.Link; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Core librarian tool. + /// + internal class Librarian : ILibrarian + { + internal Librarian(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = this.ServiceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + /// + /// Create a library by combining several intermediates (objects). + /// + /// Returns the new library. + public Intermediate Combine(ILibraryContext context) + { + if (String.IsNullOrEmpty(context.LibraryId)) + { + context.LibraryId = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('=').Replace('+', '.').Replace('/', '_'); + } + + foreach (var extension in context.Extensions) + { + extension.PreCombine(context); + } + + Intermediate library = null; + try + { + var sections = context.Intermediates.SelectMany(i => i.Sections).ToList(); + + var collate = new CollateLocalizationsCommand(this.Messaging, context.Localizations); + var localizationsByCulture = collate.Execute(); + + if (this.Messaging.EncounteredError) + { + return null; + } + + this.ResolveFilePathsToEmbed(context, sections); + + foreach (var section in sections) + { + section.AssignToLibrary(context.LibraryId); + } + + library = new Intermediate(context.LibraryId, IntermediateLevels.Compiled, sections, localizationsByCulture); + + library.UpdateLevel(IntermediateLevels.Combined); + + this.Validate(library); + } + finally + { + foreach (var extension in context.Extensions) + { + extension.PostCombine(library); + } + } + + return this.Messaging.EncounteredError ? null : library; + } + + private void ResolveFilePathsToEmbed(ILibraryContext context, IEnumerable sections) + { + // Resolve paths to files that are to be embedded in the library. + if (context.BindFiles) + { + var variableResolver = this.ServiceProvider.GetService(); + + var fileResolver = new FileResolver(context.BindPaths, context.Extensions); + + foreach (var symbol in sections.SelectMany(s => s.Symbols)) + { + foreach (var field in symbol.Fields.Where(f => f?.Type == IntermediateFieldType.Path)) + { + var pathField = field.AsPath(); + + if (pathField != null && !String.IsNullOrEmpty(pathField.Path)) + { + var resolution = variableResolver.ResolveVariables(symbol.SourceLineNumbers, pathField.Path); + + var file = fileResolver.Resolve(symbol.SourceLineNumbers, symbol.Definition, resolution.Value); + + if (!String.IsNullOrEmpty(file)) + { + // File was successfully resolved so track the embedded index as the embedded file index. + field.Set(new IntermediateFieldPathValue { Embed = true, Path = file }); + } + else + { + this.Messaging.Write(ErrorMessages.FileNotFound(symbol.SourceLineNumbers, pathField.Path, symbol.Definition.Name)); + } + } + } + } + } + } + + private void Validate(Intermediate library) + { + var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, library.Sections, OutputType.Library); + find.Execute(); + + // TODO: Consider bringing this sort of verification back. + // foreach (Section section in library.Sections) + // { + // ResolveReferencesCommand resolve = new ResolveReferencesCommand(find.EntrySection, find.Symbols); + // resolve.Execute(); + // + // ReportDuplicateResolvedSymbolErrorsCommand reportDupes = new ReportDuplicateResolvedSymbolErrorsCommand(find.SymbolsWithDuplicates, resolve.ResolvedSections); + // reportDupes.Execute(); + // } + } + } +} diff --git a/src/wix/WixToolset.Core/LibraryContext.cs b/src/wix/WixToolset.Core/LibraryContext.cs new file mode 100644 index 00000000..e701cadf --- /dev/null +++ b/src/wix/WixToolset.Core/LibraryContext.cs @@ -0,0 +1,38 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class LibraryContext : ILibraryContext + { + internal LibraryContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IMessaging Messaging { get; set; } + + public bool BindFiles { get; set; } + + public IReadOnlyCollection BindPaths { get; set; } + + public IReadOnlyCollection Extensions { get; set; } + + public string LibraryId { get; set; } + + public IReadOnlyCollection Localizations { get; set; } + + public IReadOnlyCollection Intermediates { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs b/src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs new file mode 100644 index 00000000..d5c69838 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs @@ -0,0 +1,71 @@ +// 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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + internal class CollateLocalizationsCommand + { + public CollateLocalizationsCommand(IMessaging messaging, IEnumerable localizations) + { + this.Messaging = messaging; + this.Localizations = localizations; + } + + private IMessaging Messaging { get; } + + private IEnumerable Localizations { get; } + + public Dictionary Execute() + { + var localizationsByCulture = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var localization in this.Localizations) + { + if (localizationsByCulture.TryGetValue(localization.Culture, out var existingCulture)) + { + var merged = this.Merge(existingCulture, localization); + localizationsByCulture[localization.Culture] = merged; + } + else + { + localizationsByCulture.Add(localization.Culture, localization); + } + } + + return localizationsByCulture; + } + + private Localization Merge(Localization existingLocalization, Localization localization) + { + var variables = existingLocalization.Variables.ToDictionary(v => v.Id); + var controls = existingLocalization.LocalizedControls.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + foreach (var newVariable in localization.Variables) + { + if (!variables.TryGetValue(newVariable.Id, out var existingVariable) || (existingVariable.Overridable && !newVariable.Overridable)) + { + variables[newVariable.Id] = newVariable; + } + else if (!newVariable.Overridable) + { + this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(newVariable.SourceLineNumbers, newVariable.Id)); + } + } + + foreach (var localizedControl in localization.LocalizedControls) + { + if (!controls.ContainsKey(localizedControl.Key)) + { + controls.Add(localizedControl.Key, localizedControl.Value); + } + } + + return new Localization(existingLocalization.Codepage ?? localization.Codepage, existingLocalization.SummaryInformationCodepage ?? localization.SummaryInformationCodepage, existingLocalization.Culture, variables, controls); + } + } +} diff --git a/src/wix/WixToolset.Core/Link/ConnectToFeature.cs b/src/wix/WixToolset.Core/Link/ConnectToFeature.cs new file mode 100644 index 00000000..e9a739a1 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToFeature.cs @@ -0,0 +1,59 @@ +// 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.Link +{ + using System.Collections.Generic; + using WixToolset.Data; + + /// + /// Object that connects things (components/modules) to features. + /// + internal class ConnectToFeature + { + /// + /// Creates a new connect to feature. + /// + /// Section this connect belongs to. + /// Id of the child. + /// Sets the primary feature for the connection. + /// Sets if this is explicit primary. + public ConnectToFeature(IntermediateSection section, string childId, string primaryFeature, bool explicitPrimaryFeature) + { + this.Section = section; + this.ChildId = childId; + + this.PrimaryFeature = primaryFeature; + this.IsExplicitPrimaryFeature = explicitPrimaryFeature; + } + + /// + /// Gets the section. + /// + /// Section. + public IntermediateSection Section { get; } + + /// + /// Gets the child identifier. + /// + /// The child identifier. + public string ChildId { get; } + + /// + /// Gets or sets if the flag for if the primary feature was set explicitly. + /// + /// The flag for if the primary feature was set explicitly. + public bool IsExplicitPrimaryFeature { get; set; } + + /// + /// Gets or sets the primary feature. + /// + /// The primary feature. + public string PrimaryFeature { get; set; } + + /// + /// Gets the features connected to. + /// + /// Features connected to. + public List ConnectFeatures { get; } = new List(); + } +} diff --git a/src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs b/src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs new file mode 100644 index 00000000..b7874527 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs @@ -0,0 +1,92 @@ +// 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.Link +{ + using System; + using System.Collections; + + /// + /// Hash collection of connect to feature objects. + /// + internal class ConnectToFeatureCollection : ICollection + { + private Hashtable collection; + + /// + /// Instantiate a new ConnectToFeatureCollection class. + /// + public ConnectToFeatureCollection() + { + this.collection = new Hashtable(); + } + + /// + /// Gets the number of items in the collection. + /// + /// Number of items in collection. + public int Count + { + get { return this.collection.Count; } + } + + /// + /// Gets if the collection has been synchronized. + /// + /// True if the collection has been synchronized. + public bool IsSynchronized + { + get { return this.collection.IsSynchronized; } + } + + /// + /// Gets the object used to synchronize the collection. + /// + /// Oject used the synchronize the collection. + public object SyncRoot + { + get { return this.collection.SyncRoot; } + } + + /// + /// Gets a feature connection by child id. + /// + /// Identifier of child to locate. + public ConnectToFeature this[string childId] + { + get { return (ConnectToFeature)this.collection[childId]; } + } + + /// + /// Adds a feature connection to the collection. + /// + /// Feature connection to add. + public void Add(ConnectToFeature connection) + { + if (null == connection) + { + throw new ArgumentNullException("connection"); + } + + this.collection.Add(connection.ChildId, connection); + } + + /// + /// Copies the collection into an array. + /// + /// Array to copy the collection into. + /// Index to start copying from. + public void CopyTo(System.Array array, int index) + { + this.collection.CopyTo(array, index); + } + + /// + /// Gets enumerator for the collection. + /// + /// Enumerator for the collection. + public IEnumerator GetEnumerator() + { + return this.collection.Values.GetEnumerator(); + } + } +} diff --git a/src/wix/WixToolset.Core/Link/ConnectToModule.cs b/src/wix/WixToolset.Core/Link/ConnectToModule.cs new file mode 100644 index 00000000..4380e12c --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToModule.cs @@ -0,0 +1,54 @@ +// 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.Link +{ + /// + /// Object that connects things to modules. + /// + internal class ConnectToModule + { + private string childId; + private string module; + private string moduleLanguage; + + /// + /// Creates a new connect to module. + /// + /// Id of the child. + /// Id of the module. + /// Language of the module. + public ConnectToModule(string childId, string module, string moduleLanguage) + { + this.childId = childId; + this.module = module; + this.moduleLanguage = moduleLanguage; + } + + /// + /// Gets the id of the child. + /// + /// Child identifier. + public string ChildId + { + get { return this.childId; } + } + + /// + /// Gets the id of the module. + /// + /// The id of the module. + public string Module + { + get { return this.module; } + } + + /// + /// Gets the language of the module. + /// + /// The language of the module. + public string ModuleLanguage + { + get { return this.moduleLanguage; } + } + } +} diff --git a/src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs b/src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs new file mode 100644 index 00000000..e0f96ffb --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs @@ -0,0 +1,92 @@ +// 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.Link +{ + using System; + using System.Collections; + + /// + /// Hash collection of connect to module objects. + /// + internal class ConnectToModuleCollection : ICollection + { + private Hashtable collection; + + /// + /// Instantiate a new ConnectToModuleCollection class. + /// + public ConnectToModuleCollection() + { + this.collection = new Hashtable(); + } + + /// + /// Gets the number of elements actually contained in the ConnectToModuleCollection. + /// + /// The number of elements actually contained in the ConnectToModuleCollection. + public int Count + { + get { return this.collection.Count; } + } + + /// + /// Gets a value indicating whether access to the ConnectToModuleCollection is synchronized (thread-safe). + /// + /// true if access to the ConnectToModuleCollection is synchronized (thread-safe); otherwise, false. The default is false. + public bool IsSynchronized + { + get { return this.collection.IsSynchronized; } + } + + /// + /// Gets an object that can be used to synchronize access to the ConnectToModuleCollection. + /// + /// An object that can be used to synchronize access to the ConnectToModuleCollection. + public object SyncRoot + { + get { return this.collection.SyncRoot; } + } + + /// + /// Gets a module connection by child id. + /// + /// Identifier of child to locate. + public ConnectToModule this[string childId] + { + get { return (ConnectToModule)this.collection[childId]; } + } + + /// + /// Adds a module connection to the collection. + /// + /// Module connection to add. + public void Add(ConnectToModule connection) + { + if (null == connection) + { + throw new ArgumentNullException("connection"); + } + + this.collection.Add(connection.ChildId, connection); + } + + /// + /// Copies the entire ConnectToModuleCollection to a compatible one-dimensional Array, starting at the specified index of the target array. + /// + /// The one-dimensional Array that is the destination of the elements copied from this ConnectToModuleCollection. The Array must have zero-based indexing. + /// The zero-based index in array at which copying begins. + public void CopyTo(System.Array array, int index) + { + this.collection.Keys.CopyTo(array, index); + } + + /// + /// Returns an enumerator for the entire ConnectToModuleCollection. + /// + /// An IEnumerator for the entire ConnectToModuleCollection. + public IEnumerator GetEnumerator() + { + return this.collection.Keys.GetEnumerator(); + } + } +} diff --git a/src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs b/src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs new file mode 100644 index 00000000..5d6cc831 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs @@ -0,0 +1,119 @@ +// 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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + internal class FindEntrySectionAndLoadSymbolsCommand + { + public FindEntrySectionAndLoadSymbolsCommand(IMessaging messaging, IEnumerable sections, OutputType expectedOutpuType) + { + this.Messaging = messaging; + this.Sections = sections; + this.ExpectedOutputType = expectedOutpuType; + } + + private IMessaging Messaging { get; } + + private IEnumerable Sections { get; } + + private OutputType ExpectedOutputType { get; } + + /// + /// Gets the located entry section after the command is executed. + /// + public IntermediateSection EntrySection { get; private set; } + + /// + /// Gets the collection of loaded symbols. + /// + public IDictionary SymbolsByName { get; private set; } + + /// + /// Gets the collection of possibly conflicting symbols. + /// + public IEnumerable PossibleConflicts { get; private set; } + + /// + /// Gets the collection of redundant symbols that should not be included + /// in the final output. + /// + public ISet RedundantSymbols { get; private set; } + + public void Execute() + { + var symbolsByName = new Dictionary(); + var possibleConflicts = new HashSet(); + var redundantSymbols = new HashSet(); + + if (!Enum.TryParse(this.ExpectedOutputType.ToString(), out SectionType expectedEntrySectionType)) + { + expectedEntrySectionType = SectionType.Unknown; + } + + foreach (var section in this.Sections) + { + // Try to find the one and only entry section. + if (SectionType.Product == section.Type || SectionType.Module == section.Type || SectionType.PatchCreation == section.Type || SectionType.Patch == section.Type || SectionType.Bundle == section.Type) + { + // TODO: remove this? + //if (SectionType.Unknown != expectedEntrySectionType && section.Type != expectedEntrySectionType) + //{ + // string outputExtension = Output.GetExtension(this.ExpectedOutputType); + // this.Messaging.Write(WixWarnings.UnexpectedEntrySection(section.SourceLineNumbers, section.Type.ToString(), expectedEntrySectionType.ToString(), outputExtension)); + //} + + if (null == this.EntrySection) + { + this.EntrySection = section; + } + else + { + this.Messaging.Write(ErrorMessages.MultipleEntrySections(this.EntrySection.Symbols.FirstOrDefault()?.SourceLineNumbers, this.EntrySection.Id, section.Id)); + this.Messaging.Write(ErrorMessages.MultipleEntrySections2(section.Symbols.FirstOrDefault()?.SourceLineNumbers)); + } + } + + // Load all the symbols from the section's tables that create symbols. + foreach (var symbol in section.Symbols.Where(t => t.Id != null)) + { + var symbolWithSection = new SymbolWithSection(section, symbol); + + if (!symbolsByName.TryGetValue(symbolWithSection.Name, out var existingSymbol)) + { + symbolsByName.Add(symbolWithSection.Name, symbolWithSection); + } + else // uh-oh, duplicate symbols. + { + // If the duplicate symbols are both private directories, there is a chance that they + // point to identical symbols. Identical directory symbols are redundant and will not cause + // conflicts. + if (AccessModifier.Section == existingSymbol.Access && AccessModifier.Section == symbolWithSection.Access && + SymbolDefinitionType.Directory == existingSymbol.Symbol.Definition.Type && existingSymbol.Symbol.IsIdentical(symbolWithSection.Symbol)) + { + // Ensure identical symbol's symbol is marked redundant to ensure (should the symbol be + // referenced into the final output) it will not add duplicate primary keys during + // the .IDT importing. + existingSymbol.AddRedundant(symbolWithSection); + redundantSymbols.Add(symbolWithSection.Symbol); + } + else + { + symbolWithSection.AddPossibleConflict(existingSymbol); + existingSymbol.AddPossibleConflict(symbolWithSection); + possibleConflicts.Add(symbolWithSection); + } + } + } + } + + this.SymbolsByName = symbolsByName; + this.PossibleConflicts = possibleConflicts; + this.RedundantSymbols = redundantSymbols; + } + } +} diff --git a/src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs b/src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs new file mode 100644 index 00000000..16593c7d --- /dev/null +++ b/src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs @@ -0,0 +1,194 @@ +// 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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + internal class FlattenAndProcessBundleTablesCommand + { + public FlattenAndProcessBundleTablesCommand(IntermediateSection entrySection, IMessaging messaging) + { + this.EntrySection = entrySection; + this.Messaging = messaging; + } + + private IntermediateSection EntrySection { get; } + + private IMessaging Messaging { get; } + + public void Execute() + { + this.FlattenBundleTables(); + + if (this.Messaging.EncounteredError) + { + return; + } + + this.ProcessBundleComplexReferences(); + } + + /// + /// Flattens the tables used in a Bundle. + /// + private void FlattenBundleTables() + { + // We need to flatten the nested PayloadGroups and PackageGroups under + // UX, Chain, and any Containers. When we're done, the WixGroups table + // will hold Payloads under UX, ChainPackages (references?) under Chain, + // and ContainerPackages/Payloads under any authored Containers. + var groups = new WixGroupingOrdering(this.EntrySection, this.Messaging); + + // Create UX payloads and Package payloads and Container packages + groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, + new[] { ComplexReferenceChildType.ContainerPackage, ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload }); + groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Package, false); + groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Container, false); + groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Layout, false); + + // Create Chain packages... + groups.UseTypes(new[] { ComplexReferenceParentType.PackageGroup }, new[] { ComplexReferenceChildType.Package, ComplexReferenceChildType.PackageGroup }); + groups.FlattenAndRewriteRows(ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, false); + + groups.RemoveUsedGroupRows(); + } + + private void ProcessBundleComplexReferences() + { + var containersById = this.EntrySection.Symbols.OfType().ToDictionary(c => c.Id.Id); + var groups = this.EntrySection.Symbols.OfType().ToList(); + var payloadsById = this.EntrySection.Symbols.OfType().ToDictionary(c => c.Id.Id); + + var containerByPackage = new Dictionary(); + var referencedPackages = new HashSet(); + var payloadsInBA = new HashSet(); + var payloadsInPackageOrLayout = new HashSet(); + + foreach (var groupSymbol in groups) + { + switch (groupSymbol.ChildType) + { + case ComplexReferenceChildType.ContainerPackage: + switch (groupSymbol.ParentType) + { + case ComplexReferenceParentType.Container: + if (containerByPackage.TryGetValue(groupSymbol.ChildId, out var collisionContainer)) + { + this.Messaging.Write(LinkerErrors.PackageInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, groupSymbol.ParentId, collisionContainer.Id.Id)); + } + else + { + containerByPackage.Add(groupSymbol.ChildId, containersById[groupSymbol.ParentId]); + } + break; + } + break; + case ComplexReferenceChildType.Package: + switch (groupSymbol.ParentType) + { + case ComplexReferenceParentType.PackageGroup: + if (groupSymbol.ParentId == BurnConstants.BundleChainPackageGroupId) + { + referencedPackages.Add(groupSymbol.ChildId); + } + break; + } + break; + case ComplexReferenceChildType.Payload: + switch (groupSymbol.ParentType) + { + case ComplexReferenceParentType.Container: + if (groupSymbol.ParentId == BurnConstants.BurnUXContainerName) + { + payloadsInBA.Add(groupSymbol.ChildId); + } + break; + case ComplexReferenceParentType.Layout: + payloadsById[groupSymbol.ChildId].LayoutOnly = true; + payloadsInPackageOrLayout.Add(groupSymbol.ChildId); + break; + case ComplexReferenceParentType.Package: + payloadsInPackageOrLayout.Add(groupSymbol.ChildId); + break; + } + break; + } + } + + foreach (var package in this.EntrySection.Symbols.OfType()) + { + if (!referencedPackages.Contains(package.Id.Id)) + { + this.Messaging.Write(LinkerErrors.UnscheduledChainPackage(package.SourceLineNumbers, package.Id.Id)); + } + } + + foreach (var rollbackBoundary in this.EntrySection.Symbols.OfType()) + { + if (!referencedPackages.Contains(rollbackBoundary.Id.Id)) + { + this.Messaging.Write(LinkerErrors.UnscheduledRollbackBoundary(rollbackBoundary.SourceLineNumbers, rollbackBoundary.Id.Id)); + } + } + + foreach (var payload in payloadsById.Values) + { + var payloadId = payload.Id.Id; + if (payloadsInBA.Contains(payloadId)) + { + if (payloadsInPackageOrLayout.Contains(payloadId)) + { + this.Messaging.Write(LinkerErrors.PayloadSharedWithBA(payload.SourceLineNumbers, payloadId)); + } + } + else if (!payloadsInPackageOrLayout.Contains(payloadId)) + { + this.Messaging.Write(LinkerErrors.OrphanedPayload(payload.SourceLineNumbers, payloadId)); + } + } + + if (this.Messaging.EncounteredError) + { + return; + } + + // Assign authored payloads to authored containers. + // Compressed Payloads not assigned to a container here will get assigned to the default attached container during binding. + foreach (var groupSymbol in groups) + { + if (groupSymbol.ChildType == ComplexReferenceChildType.Payload && groupSymbol.ParentType == ComplexReferenceParentType.Container) + { + var payloadSymbol = payloadsById[groupSymbol.ChildId]; + var containerId = groupSymbol.ParentId; + + if (String.IsNullOrEmpty(payloadSymbol.ContainerRef)) + { + if (payloadSymbol.Compressed == false) + { + this.Messaging.Write(LinkerWarnings.UncompressedPayloadInContainer(payloadSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); + } + + payloadSymbol.Compressed = true; + payloadSymbol.ContainerRef = containerId; + } + else + { + this.Messaging.Write(LinkerWarnings.PayloadInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId, payloadSymbol.ContainerRef)); + } + + if (payloadSymbol.LayoutOnly) + { + this.Messaging.Write(LinkerWarnings.LayoutPayloadInContainer(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId)); + } + } + } + + } + } +} diff --git a/src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs b/src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs new file mode 100644 index 00000000..cbf48abe --- /dev/null +++ b/src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs @@ -0,0 +1,26 @@ +// 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.Link +{ + using WixToolset.Data; + + internal static class IntermediateSymbolExtensions + { + public static bool IsIdentical(this IntermediateSymbol first, IntermediateSymbol second) + { + var identical = (first.Definition.Type == second.Definition.Type && + (first.Definition.Type != SymbolDefinitionType.MustBeFromAnExtension || first.Definition.Name == second.Definition.Name) && + first.Definition.FieldDefinitions.Length == second.Definition.FieldDefinitions.Length); + + for (var i = 0; identical && i < first.Definition.FieldDefinitions.Length; ++i) + { + var firstField = first[i]; + var secondField = second[i]; + + identical = (firstField.AsString() == secondField.AsString()); + } + + return identical; + } + } +} diff --git a/src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs b/src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs new file mode 100644 index 00000000..ace2e19d --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs @@ -0,0 +1,54 @@ +// 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.Link +{ + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + + internal class ReportConflictingSymbolsCommand + { + public ReportConflictingSymbolsCommand(IMessaging messaging, IEnumerable possibleConflicts, IEnumerable resolvedSections) + { + this.Messaging = messaging; + this.PossibleConflicts = possibleConflicts; + this.ResolvedSections = resolvedSections; + } + + private IMessaging Messaging { get; } + + private IEnumerable PossibleConflicts { get; } + + private IEnumerable ResolvedSections { get; } + + public void Execute() + { + // Do a quick check if there are any possibly conflicting symbols that don't come from tables that allow + // overriding. Hopefully the symbols with possible conflicts list is usually very short list (empty should + // be the most common). If we find any matches, we'll do a more costly check to see if the possible conflicting + // symbols are in sections we actually referenced. From the resulting set, show an error for each duplicate + // (aka: conflicting) symbol. + var illegalDuplicates = this.PossibleConflicts.Where(s => s.Symbol.Definition.Type != SymbolDefinitionType.WixAction && s.Symbol.Definition.Type != SymbolDefinitionType.WixVariable).ToList(); + if (0 < illegalDuplicates.Count) + { + var referencedSections = new HashSet(this.ResolvedSections); + + foreach (var referencedDuplicate in illegalDuplicates.Where(s => referencedSections.Contains(s.Section))) + { + var actuallyReferencedDuplicates = referencedDuplicate.PossiblyConflicts.Where(s => referencedSections.Contains(s.Section)).ToList(); + + if (actuallyReferencedDuplicates.Any()) + { + this.Messaging.Write(ErrorMessages.DuplicateSymbol(referencedDuplicate.Symbol.SourceLineNumbers, referencedDuplicate.Name)); + + foreach (var duplicate in actuallyReferencedDuplicates) + { + this.Messaging.Write(ErrorMessages.DuplicateSymbol2(duplicate.Symbol.SourceLineNumbers)); + } + } + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs new file mode 100644 index 00000000..efb90bb8 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs @@ -0,0 +1,183 @@ +// 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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + + /// + /// Resolves all the simple references in a section. + /// + internal class ResolveReferencesCommand + { + private readonly IntermediateSection entrySection; + private readonly IDictionary symbolsWithSections; + private HashSet referencedSymbols; + private HashSet resolvedSections; + + public ResolveReferencesCommand(IMessaging messaging, IntermediateSection entrySection, IDictionary symbolsWithSections) + { + this.Messaging = messaging; + this.entrySection = entrySection; + this.symbolsWithSections = symbolsWithSections; + this.BuildingMergeModule = (SectionType.Module == entrySection.Type); + } + + public IEnumerable ReferencedSymbolWithSections => this.referencedSymbols; + + public IEnumerable ResolvedSections => this.resolvedSections; + + private bool BuildingMergeModule { get; } + + private IMessaging Messaging { get; } + + /// + /// Resolves all the simple references in a section. + /// + public void Execute() + { + this.resolvedSections = new HashSet(); + this.referencedSymbols = new HashSet(); + + this.RecursivelyResolveReferences(this.entrySection); + } + + /// + /// Recursive helper function to resolve all references of passed in section. + /// + /// Section with references to resolve. + /// Note: recursive function. + private void RecursivelyResolveReferences(IntermediateSection section) + { + // If we already resolved this section, move on to the next. + if (!this.resolvedSections.Add(section)) + { + return; + } + + // Process all of the references contained in this section using the collection of + // symbols provided. Then recursively call this method to process the + // located symbol's section. All in all this is a very simple depth-first + // search of the references per-section. + foreach (var wixSimpleReferenceRow in section.Symbols.OfType()) + { + // If we're building a Merge Module, ignore all references to the Media table + // because Merge Modules don't have Media tables. + if (this.BuildingMergeModule && wixSimpleReferenceRow.Table == "Media") + { + continue; + } + + // See if the symbol (and any of its duplicates) are appropriately accessible. + if (this.symbolsWithSections.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbolWithSection)) + { + var accessible = this.DetermineAccessibleSymbols(section, symbolWithSection); + if (accessible.Count == 1) + { + var accessibleSymbol = accessible[0]; + if (this.referencedSymbols.Add(accessibleSymbol) && null != accessibleSymbol.Section) + { + this.RecursivelyResolveReferences(accessibleSymbol.Section); + } + } + else if (accessible.Count == 0) + { + this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbolWithSection.Access)); + } + else // display errors for the duplicate symbols. + { + var accessibleSymbol = accessible[0]; + var referencingSourceLineNumber = wixSimpleReferenceRow.SourceLineNumbers?.ToString(); + + if (String.IsNullOrEmpty(referencingSourceLineNumber)) + { + this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name)); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name, referencingSourceLineNumber)); + } + + foreach (var accessibleDuplicate in accessible.Skip(1)) + { + this.Messaging.Write(ErrorMessages.DuplicateSymbol2(accessibleDuplicate.Symbol.SourceLineNumbers)); + } + } + } + else + { + this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName)); + } + } + } + + /// + /// Determine if the symbol and any of its duplicates are accessbile by referencing section. + /// + /// Section referencing the symbol. + /// Symbol being referenced. + /// List of symbols accessible by referencing section. + private List DetermineAccessibleSymbols(IntermediateSection referencingSection, SymbolWithSection symbolWithSection) + { + var accessibleSymbols = new List(); + + if (this.AccessibleSymbol(referencingSection, symbolWithSection)) + { + accessibleSymbols.Add(symbolWithSection); + } + + foreach (var dupe in symbolWithSection.PossiblyConflicts) + { + // don't count overridable WixActionSymbols + var symbolAction = symbolWithSection.Symbol as WixActionSymbol; + var dupeAction = dupe.Symbol as WixActionSymbol; + if (symbolAction?.Overridable != dupeAction?.Overridable) + { + continue; + } + + if (this.AccessibleSymbol(referencingSection, dupe)) + { + accessibleSymbols.Add(dupe); + } + } + + foreach (var dupe in symbolWithSection.Redundants) + { + if (this.AccessibleSymbol(referencingSection, dupe)) + { + accessibleSymbols.Add(dupe); + } + } + + return accessibleSymbols; + } + + /// + /// Determine if a single symbol is accessible by the referencing section. + /// + /// Section referencing the symbol. + /// Symbol being referenced. + /// True if symbol is accessible. + private bool AccessibleSymbol(IntermediateSection referencingSection, SymbolWithSection symbolWithSection) + { + switch (symbolWithSection.Access) + { + case AccessModifier.Global: + return true; + case AccessModifier.Library: + return symbolWithSection.Section.CompilationId == referencingSection.CompilationId || (null != symbolWithSection.Section.LibraryId && symbolWithSection.Section.LibraryId == referencingSection.LibraryId); + case AccessModifier.File: + return symbolWithSection.Section.CompilationId == referencingSection.CompilationId; + case AccessModifier.Section: + return referencingSection == symbolWithSection.Section; + default: + throw new ArgumentOutOfRangeException(nameof(symbolWithSection.Access)); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Link/SymbolWithSection.cs b/src/wix/WixToolset.Core/Link/SymbolWithSection.cs new file mode 100644 index 00000000..08e01077 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/SymbolWithSection.cs @@ -0,0 +1,92 @@ +// 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.Link +{ + using System; + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + + /// + /// Symbol with section representing a single unique symbol. + /// + internal class SymbolWithSection + { + private HashSet possibleConflicts; + private HashSet redundants; + + /// + /// Creates a symbol for a symbol. + /// + /// + /// Symbol for the symbol + public SymbolWithSection(IntermediateSection section, IntermediateSymbol symbol) + { + this.Symbol = symbol; + this.Section = section; + this.Name = String.Concat(this.Symbol.Definition.Name, ":", this.Symbol.Id.Id); + } + + /// + /// Gets the accessibility of the symbol which is a direct reflection of the accessibility of the row's accessibility. + /// + /// Accessbility of the symbol. + public AccessModifier Access => this.Symbol.Id.Access; + + /// + /// Gets the name of the symbol. + /// + /// Name of the symbol. + public string Name { get; } + + /// + /// Gets the symbol for this symbol. + /// + /// Symbol for this symbol. + public IntermediateSymbol Symbol { get; } + + /// + /// Gets the section for the symbol. + /// + /// Section for the symbol. + public IntermediateSection Section { get; } + + /// + /// Gets any duplicates of this symbol with sections that are possible conflicts. + /// + public IEnumerable PossiblyConflicts => this.possibleConflicts ?? Enumerable.Empty(); + + /// + /// Gets any duplicates of this symbol with sections that are redundant. + /// + public IEnumerable Redundants => this.redundants ?? Enumerable.Empty(); + + /// + /// Adds a duplicate symbol with sections that is a possible conflict. + /// + /// Symbol with section that is a possible conflict of this symbol. + public void AddPossibleConflict(SymbolWithSection symbolWithSection) + { + if (null == this.possibleConflicts) + { + this.possibleConflicts = new HashSet(); + } + + this.possibleConflicts.Add(symbolWithSection); + } + + /// + /// Adds a duplicate symbol that is redundant. + /// + /// Symbol with section that is redundant of this symbol. + public void AddRedundant(SymbolWithSection symbolWithSection) + { + if (null == this.redundants) + { + this.redundants = new HashSet(); + } + + this.redundants.Add(symbolWithSection); + } + } +} diff --git a/src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs b/src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs new file mode 100644 index 00000000..2b1925ad --- /dev/null +++ b/src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs @@ -0,0 +1,75 @@ +// 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.Link +{ + using System; + using WixToolset.Data.Symbols; + + internal static class WixComplexReferenceSymbolExtensions + { + /// + /// Creates a shallow copy of the ComplexReference. + /// + /// A shallow copy of the ComplexReference. + public static WixComplexReferenceSymbol Clone(this WixComplexReferenceSymbol source) + { + var clone = new WixComplexReferenceSymbol(source.SourceLineNumbers, source.Id); + clone.ParentType = source.ParentType; + clone.Parent = source.Parent; + clone.ParentLanguage = source.ParentLanguage; + clone.ChildType = source.ChildType; + clone.Child = source.Child; + clone.IsPrimary = source.IsPrimary; + + return clone; + } + + /// + /// Compares two complex references without considering the primary bit. + /// + /// this + /// Complex reference to compare to. + /// Zero if the objects are equivalent, negative number if the provided object is less, positive if greater. + public static int CompareToWithoutConsideringPrimary(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol other) + { + var comparison = symbol.ChildType - other.ChildType; + if (0 == comparison) + { + comparison = String.Compare(symbol.Child, other.Child, StringComparison.Ordinal); + if (0 == comparison) + { + comparison = symbol.ParentType - other.ParentType; + if (0 == comparison) + { + string thisParentLanguage = null == symbol.ParentLanguage ? String.Empty : symbol.ParentLanguage; + string otherParentLanguage = null == other.ParentLanguage ? String.Empty : other.ParentLanguage; + comparison = String.Compare(thisParentLanguage, otherParentLanguage, StringComparison.Ordinal); + if (0 == comparison) + { + comparison = String.Compare(symbol.Parent, other.Parent, StringComparison.Ordinal); + } + } + } + } + + return comparison; + } + + /// + /// Changes all of the parent references to point to the passed in parent reference. + /// + /// this + /// New parent complex reference. + public static void Reparent(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol parent) + { + symbol.Parent = parent.Parent; + symbol.ParentLanguage = parent.ParentLanguage; + symbol.ParentType = parent.ParentType; + + if (!symbol.IsPrimary) + { + symbol.IsPrimary = parent.IsPrimary; + } + } + } +} diff --git a/src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs new file mode 100644 index 00000000..f9de82a9 --- /dev/null +++ b/src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs @@ -0,0 +1,683 @@ +// 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.Link +{ + using System; + using System.Collections.ObjectModel; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Text; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + using WixToolset.Data.Burn; + + /// + /// Grouping and Ordering class of the WiX toolset. + /// + internal class WixGroupingOrdering + { + private readonly IMessaging Messaging; + private List groupTypes; + private List itemTypes; + private ItemCollection items; + private readonly List symbolsUsed; + private bool loaded; + + /// + /// Creates a WixGroupingOrdering object. + /// + /// Output from which to read the group and order information. + /// Handler for any error messages. + public WixGroupingOrdering(IntermediateSection entrySections, IMessaging messageHandler) + { + this.EntrySection = entrySections; + this.Messaging = messageHandler; + + this.symbolsUsed = new List(); + this.loaded = false; + } + + private IntermediateSection EntrySection { get; } + + /// + /// Switches a WixGroupingOrdering object to operate on a new set of groups/items. + /// + /// Group types to include. + /// Item types to include. + public void UseTypes(IEnumerable groupTypes, IEnumerable itemTypes) + { + this.groupTypes = new List(groupTypes.Select(g => g.ToString())); + this.itemTypes = new List(itemTypes.Select(i => i.ToString())); + + this.items = new ItemCollection(); + this.loaded = false; + } + + /// + /// Finds all nested items under a parent group and creates new WixGroup data for them. + /// + /// The group type for the parent group to flatten. + /// The identifier of the parent group to flatten. + /// Whether to remove used group rows before returning. + public void FlattenAndRewriteRows(ComplexReferenceParentType parentType, string parentId, bool removeUsedRows) + { + var parentTypeString = parentType.ToString(); + Debug.Assert(this.groupTypes.Contains(parentTypeString)); + + this.CreateOrderedList(parentTypeString, parentId, out var orderedItems); + if (this.Messaging.EncounteredError) + { + return; + } + + this.CreateNewGroupRows(parentTypeString, parentId, orderedItems); + + if (removeUsedRows) + { + this.RemoveUsedGroupRows(); + } + } + + /// + /// Finds all items under a parent group type and creates new WixGroup data for them. + /// + /// The type of the parent group to flatten. + /// Whether to remove used group rows before returning. + public void FlattenAndRewriteGroups(ComplexReferenceParentType parentType, bool removeUsedRows) + { + var parentTypeString = parentType.ToString(); + Debug.Assert(this.groupTypes.Contains(parentTypeString)); + + this.LoadFlattenOrderGroups(); + if (this.Messaging.EncounteredError) + { + return; + } + + foreach (Item item in this.items) + { + if (parentTypeString == item.Type) + { + this.CreateOrderedList(item.Type, item.Id, out var orderedItems); + this.CreateNewGroupRows(item.Type, item.Id, orderedItems); + } + } + + if (removeUsedRows) + { + this.RemoveUsedGroupRows(); + } + } + + + /// + /// Creates a flattened and ordered list of items for the given parent group. + /// + /// The group type for the parent group to flatten. + /// The identifier of the parent group to flatten. + /// The returned list of ordered items. + private void CreateOrderedList(string parentType, string parentId, out List orderedItems) + { + orderedItems = null; + + this.LoadFlattenOrderGroups(); + if (this.Messaging.EncounteredError) + { + return; + } + + if (!this.items.TryGetValue(parentType, parentId, out var parentItem)) + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound(parentType, parentId)); + return; + } + + orderedItems = new List(parentItem.ChildItems); + orderedItems.Sort(new Item.AfterItemComparer()); + } + + /// + /// Removes rows from WixGroup that have been used by this object. + /// + public void RemoveUsedGroupRows() + { + foreach (var symbol in this.symbolsUsed) + { + this.EntrySection.RemoveSymbol(symbol); + } + } + + /// + /// Creates new WixGroup rows for a list of items. + /// + /// The group type for the parent group in the new rows. + /// The identifier of the parent group in the new rows. + /// The list of new items. + private void CreateNewGroupRows(string parentType, string parentId, List orderedItems) + { + // TODO: MSIs don't guarantee that rows stay in the same order, and technically, neither + // does WiX (although they do, currently). We probably want to "upgrade" this to a new + // table that includes a sequence number, and then change the code that uses ordered + // groups to read from that table instead. + foreach (var item in orderedItems) + { + this.EntrySection.AddSymbol(new WixGroupSymbol(item.Row.SourceLineNumbers) + { + ParentId = parentId, + ParentType = (ComplexReferenceParentType)Enum.Parse(typeof(ComplexReferenceParentType), parentType), + ChildId = item.Id, + ChildType = (ComplexReferenceChildType)Enum.Parse(typeof(ComplexReferenceChildType), item.Type), + }); + } + } + + // Group/Ordering Flattening Logic + // + // What follows is potentially convoluted logic. Two somewhat orthogonal concepts are in + // play: grouping (parent/child relationships) and ordering (before/after relationships). + // Dealing with just one or the other is straghtforward. Groups can be flattened + // recursively. Ordering can be propagated in either direction. When the ordering also + // participates in the grouping constructions, however, things get trickier. For the + // purposes of this discussion, we're dealing with "items" and "groups", and an instance + // of either of them can be marked as coming "after" some other instance. + // + // For simple item-to-item ordering, the "after" values simply propagate: if A is after B, + // and B is after C, then we can say that A is after *both* B and C. If a group is involved, + // it acts as a proxy for all of its included items and any sub-groups. + + /// + /// Internal workhorse for ensuring that group and ordering information has + /// been loaded and applied. + /// + private void LoadFlattenOrderGroups() + { + if (!this.loaded) + { + this.LoadGroups(); + this.LoadOrdering(); + + // It would be really nice to have a "find circular after dependencies" + // function, but it gets much more complicated because of the way that + // the dependencies are propagated across group boundaries. For now, we + // just live with the dependency loop detection as we flatten the + // dependencies. Group references, however, we can check directly. + this.FindCircularGroupReferences(); + + if (!this.Messaging.EncounteredError) + { + this.FlattenGroups(); + this.FlattenOrdering(); + } + + this.loaded = true; + } + } + + /// + /// Loads data from the WixGroup table. + /// + private void LoadGroups() + { + //Table wixGroupTable = this.output.Tables["WixGroup"]; + //if (null == wixGroupTable || 0 == wixGroupTable.Rows.Count) + //{ + // // TODO: Change message name to make it *not* Bundle specific? + // this.Write(WixErrors.MissingBundleInformation("WixGroup")); + //} + + // Collect all of the groups + foreach (var symbol in this.EntrySection.Symbols.OfType()) + { + var rowParentName = symbol.ParentId; + var rowParentType = symbol.ParentType.ToString(); + var rowChildName = symbol.ChildId; + var rowChildType = symbol.ChildType.ToString(); + + // If this row specifies a parent or child type that's not in our + // lists, we assume it's not a row that we're concerned about. + if (!this.groupTypes.Contains(rowParentType) || + !this.itemTypes.Contains(rowChildType)) + { + continue; + } + + this.symbolsUsed.Add(symbol); + + if (!this.items.TryGetValue(rowParentType, rowParentName, out var parentItem)) + { + parentItem = new Item(symbol, rowParentType, rowParentName); + this.items.Add(parentItem); + } + + if (!this.items.TryGetValue(rowChildType, rowChildName, out var childItem)) + { + childItem = new Item(symbol, rowChildType, rowChildName); + this.items.Add(childItem); + } + + parentItem.ChildItems.Add(childItem); + } + } + + /// + /// Flattens group/item information. + /// + private void FlattenGroups() + { + foreach (Item item in this.items) + { + item.FlattenChildItems(); + } + } + + /// + /// Finds and reports circular references in the group/item data. + /// + private void FindCircularGroupReferences() + { + ItemCollection itemsInKnownLoops = new ItemCollection(); + foreach (Item item in this.items) + { + if (itemsInKnownLoops.Contains(item)) + { + continue; + } + + ItemCollection itemsSeen = new ItemCollection(); + string circularReference; + if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference)) + { + itemsInKnownLoops.Add(itemsSeen); + this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference)); + } + } + } + + /// + /// Recursive worker to find and report circular references in group/item data. + /// + /// The sentinal item being checked. + /// The current item in the recursion. + /// A list of all items already visited (for performance). + /// A list of items in the current circular reference, if one was found; null otherwise. + /// True if a circular reference was found; false otherwise. + private bool FindCircularGroupReference(Item checkItem, Item currentItem, ItemCollection itemsSeen, out string circularReference) + { + circularReference = null; + foreach (Item subitem in currentItem.ChildItems) + { + if (checkItem == subitem) + { + // TODO: Even better would be to include the source lines for each reference! + circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3}", + currentItem.Type, currentItem.Id, subitem.Type, subitem.Id); + return true; + } + + if (!itemsSeen.Contains(subitem)) + { + itemsSeen.Add(subitem); + if (this.FindCircularGroupReference(checkItem, subitem, itemsSeen, out circularReference)) + { + // TODO: Even better would be to include the source lines for each reference! + circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}", + currentItem.Type, currentItem.Id, circularReference); + return true; + } + } + } + + return false; + } + + /// + /// Loads ordering dependency data from the WixOrdering table. + /// + private void LoadOrdering() + { + //Table wixOrderingTable = output.Tables["WixOrdering"]; + //if (null == wixOrderingTable || 0 == wixOrderingTable.Rows.Count) + //{ + // // TODO: Do we need a message here? + // return; + //} + + foreach (var row in this.EntrySection.Symbols.OfType()) + { + var rowItemType = row.ItemType.ToString(); + var rowItemName = row.ItemIdRef; + var rowDependsOnType = row.DependsOnType.ToString(); + var rowDependsOnName = row.DependsOnIdRef; + + // If this row specifies some other (unknown) type in either + // position, we assume it's not a row that we're concerned about. + // For ordering, we allow group and item in either position. + if (!(this.groupTypes.Contains(rowItemType) || this.itemTypes.Contains(rowItemType)) || + !(this.groupTypes.Contains(rowDependsOnType) || this.itemTypes.Contains(rowDependsOnType))) + { + continue; + } + + if (!this.items.TryGetValue(rowItemType, rowItemName, out var item)) + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowItemType, rowItemName)); + } + + if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out var dependsOn)) + { + this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowDependsOnType, rowDependsOnName)); + } + + if (null == item || null == dependsOn) + { + continue; + } + + item.AddAfter(dependsOn, this.Messaging); + } + } + + /// + /// Flattens the ordering dependencies in the groups/items. + /// + private void FlattenOrdering() + { + // Because items don't know about their parent groups (and can, in fact, be + // in more than one group at a time), we need to pre-propagate the 'afters' + // from each parent item to its children before we attempt to flatten the + // ordering. + foreach (Item item in this.items) + { + item.PropagateAfterToChildItems(this.Messaging); + } + + foreach (Item item in this.items) + { + item.FlattenAfters(this.Messaging); + } + } + + /// + /// A variant of KeyedCollection that doesn't throw when an item is re-added. + /// + /// Key type for the collection. + /// Item type for the colelction. + internal abstract class EnhancedKeyCollection : KeyedCollection + { + new public void Add(TItem item) + { + if (!this.Contains(item)) + { + base.Add(item); + } + } + + public void Add(Collection list) + { + foreach (TItem item in list) + { + this.Add(item); + } + } + + public void Remove(Collection list) + { + foreach (TItem item in list) + { + this.Remove(item); + } + } + + public bool TryGetValue(TKey key, out TItem item) + { + // KeyedCollection doesn't implement the TryGetValue() method, but it's + // a useful concept. We can't just always pass this to the enclosed + // Dictionary, however, because it doesn't always exist! If it does, we + // can delegate to it as one would expect. If it doesn't, we have to + // implement everything ourselves in terms of Contains(). + + if (null != this.Dictionary) + { + return this.Dictionary.TryGetValue(key, out item); + } + + if (this.Contains(key)) + { + item = this[key]; + return true; + } + + item = default(TItem); + return false; + } + +#if DEBUG + // This just makes debugging easier... + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + foreach (TItem item in this) + { + sb.AppendFormat("{0}, ", item); + } + sb.Length -= 2; + return sb.ToString(); + } +#endif // DEBUG + } + + /// + /// A specialized EnhancedKeyCollection, typed to Items. + /// + internal class ItemCollection : EnhancedKeyCollection + { + protected override string GetKeyForItem(Item item) + { + return item.Key; + } + + public bool TryGetValue(string type, string id, out Item item) + { + return this.TryGetValue(CreateKeyFromTypeId(type, id), out item); + } + + public static string CreateKeyFromTypeId(string type, string id) + { + return String.Format(CultureInfo.InvariantCulture, "{0}_{1}", type, id); + } + } + + /// + /// An item (or group) in the grouping/ordering engine. + /// + /// Encapsulates nested group membership and also before/after + /// ordering dependencies. + internal class Item + { + private readonly ItemCollection afterItems; + private readonly ItemCollection beforeItems; // for checking for circular references + private bool flattenedAfterItems; + + public Item(IntermediateSymbol row, string type, string id) + { + this.Row = row; + this.Type = type; + this.Id = id; + + this.Key = ItemCollection.CreateKeyFromTypeId(type, id); + + this.afterItems = new ItemCollection(); + this.beforeItems = new ItemCollection(); + this.flattenedAfterItems = false; + } + + public IntermediateSymbol Row { get; private set; } + public string Type { get; private set; } + public string Id { get; private set; } + public string Key { get; private set; } + +#if DEBUG + // Makes debugging easier... + public override string ToString() + { + return this.Key; + } +#endif // DEBUG + + public ItemCollection ChildItems { get; } = new ItemCollection(); + + /// + /// Removes any nested groups under this item and replaces + /// them with their child items. + /// + public void FlattenChildItems() + { + ItemCollection flattenedChildItems = new ItemCollection(); + + foreach (Item childItem in this.ChildItems) + { + if (0 == childItem.ChildItems.Count) + { + flattenedChildItems.Add(childItem); + } + else + { + childItem.FlattenChildItems(); + flattenedChildItems.Add(childItem.ChildItems); + } + } + + this.ChildItems.Clear(); + this.ChildItems.Add(flattenedChildItems); + } + + /// + /// Adds a list of items to the 'after' ordering collection. + /// + /// List of items to add. + /// Message handler in case a circular ordering reference is found. + public void AddAfter(ItemCollection items, IMessaging messageHandler) + { + foreach (Item item in items) + { + this.AddAfter(item, messageHandler); + } + } + + /// + /// Adds an item to the 'after' ordering collection. + /// + /// Item to add. + /// Message handler in case a circular ordering reference is found. + public void AddAfter(Item after, IMessaging messageHandler) + { + if (this.beforeItems.Contains(after)) + { + // We could try to chain this up (the way that group circular dependencies + // are reported), but since we're in the process of flattening, we may already + // have lost some distinction between authored and propagated ordering. + string circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3} -> {0}:{1}", + this.Type, this.Id, after.Type, after.Id); + messageHandler.Write(ErrorMessages.OrderingReferenceLoopDetected(after.Row.SourceLineNumbers, circularReference)); + return; + } + + this.afterItems.Add(after); + after.beforeItems.Add(this); + } + + /// + /// Propagates 'after' dependencies from an item to its child items. + /// + /// Message handler in case a circular ordering reference is found. + /// Because items don't know about their parent groups (and can, in fact, be in more + /// than one group at a time), we need to propagate the 'afters' from each parent item to its children + /// before we attempt to flatten the ordering. + public void PropagateAfterToChildItems(IMessaging messageHandler) + { + if (this.ShouldItemPropagateChildOrdering()) + { + foreach (Item childItem in this.ChildItems) + { + childItem.AddAfter(this.afterItems, messageHandler); + } + } + } + + /// + /// Flattens the ordering dependency for this item. + /// + /// Message handler in case a circular ordering reference is found. + public void FlattenAfters(IMessaging messageHandler) + { + if (this.flattenedAfterItems) + { + return; + } + + this.flattenedAfterItems = true; + + // Ensure that if we're after something (A), and *it's* after something (B), + // that we list ourselved as after both (A) *and* (B). + ItemCollection nestedAfterItems = new ItemCollection(); + + foreach (Item afterItem in this.afterItems) + { + afterItem.FlattenAfters(messageHandler); + nestedAfterItems.Add(afterItem.afterItems); + + if (afterItem.ShouldItemPropagateChildOrdering()) + { + // If we are after a group, it really means + // we are after all of the group's children. + foreach (Item childItem in afterItem.ChildItems) + { + childItem.FlattenAfters(messageHandler); + nestedAfterItems.Add(childItem.afterItems); + nestedAfterItems.Add(childItem); + } + } + } + + this.AddAfter(nestedAfterItems, messageHandler); + } + + // We *don't* propagate ordering information from Packages or + // Containers to their children, because ordering doesn't matter + // for them, and a Payload in two Packages (or Containers) can + // cause a circular reference to occur. + private bool ShouldItemPropagateChildOrdering() + { + if (String.Equals(nameof(ComplexReferenceParentType.Package), this.Type, StringComparison.Ordinal) || + String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal)) + { + return false; + } + return true; + } + + /// + /// Helper IComparer class to make ordering easier. + /// + internal class AfterItemComparer : IComparer + { + public int Compare(Item x, Item y) + { + if (x.afterItems.Contains(y)) + { + return 1; + } + else if (y.afterItems.Contains(x)) + { + return -1; + } + + return String.CompareOrdinal(x.Id, y.Id); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/LinkContext.cs b/src/wix/WixToolset.Core/LinkContext.cs new file mode 100644 index 00000000..b99bb9c4 --- /dev/null +++ b/src/wix/WixToolset.Core/LinkContext.cs @@ -0,0 +1,33 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class LinkContext : ILinkContext + { + internal LinkContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IReadOnlyCollection Extensions { get; set; } + + public IReadOnlyCollection ExtensionData { get; set; } + + public OutputType ExpectedOutputType { get; set; } + + public IReadOnlyCollection Intermediates { get; set; } + + public ISymbolDefinitionCreator SymbolDefinitionCreator { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Linker.cs b/src/wix/WixToolset.Core/Linker.cs new file mode 100644 index 00000000..47671f26 --- /dev/null +++ b/src/wix/WixToolset.Core/Linker.cs @@ -0,0 +1,942 @@ +// 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 +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using WixToolset.Core.Link; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Linker core of the WiX toolset. + /// + internal class Linker : ILinker + { + private static readonly string EmptyGuid = Guid.Empty.ToString("B"); + + private readonly bool sectionIdOnRows; + + /// + /// Creates a linker. + /// + internal Linker(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = this.ServiceProvider.GetService(); + this.sectionIdOnRows = true; // TODO: what is the correct value for this? + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + private ILinkContext Context { get; set; } + + /// + /// Gets or sets the path to output unreferenced symbols to. If null or empty, there is no output. + /// + /// The path to output the xml file. + public string UnreferencedSymbolsFile { get; set; } + + /// + /// Gets or sets the option to show pedantic messages. + /// + /// The option to show pedantic messages. + public bool ShowPedanticMessages { get; set; } + + /// + /// Links a collection of sections into an output. + /// + /// Output intermediate from the linking. + public Intermediate Link(ILinkContext context) + { + this.Context = context; + + if (this.Context.SymbolDefinitionCreator == null) + { + this.Context.SymbolDefinitionCreator = this.ServiceProvider.GetService(); + } + + foreach (var extension in this.Context.Extensions) + { + extension.PreLink(this.Context); + } + + var invalidIntermediates = this.Context.Intermediates.Where(i => !i.HasLevel(Data.IntermediateLevels.Compiled)); + if (invalidIntermediates.Any()) + { + this.Messaging.Write(ErrorMessages.IntermediatesMustBeCompiled(String.Join(", ", invalidIntermediates.Select(i => i.Id)))); + } + + Intermediate intermediate = null; + try + { + var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList(); + var localizations = this.Context.Intermediates.SelectMany(i => i.Localizations).ToList(); + + // Add sections from the extensions with data. + foreach (var data in this.Context.ExtensionData) + { + var library = data.GetLibrary(this.Context.SymbolDefinitionCreator); + + if (library != null) + { + sections.AddRange(library.Sections); + } + } + + //this.activeOutput = null; + + var multipleFeatureComponents = new Hashtable(); + + var wixVariables = new Dictionary(); + + // First find the entry section and while processing all sections load all the symbols from all of the sections. + var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, sections, this.Context.ExpectedOutputType); + find.Execute(); + + // Must have found the entry section by now. + if (null == find.EntrySection) + { + if (this.Context.ExpectedOutputType == OutputType.IntermediatePostLink || this.Context.ExpectedOutputType == OutputType.Unknown) + { + throw new WixException(ErrorMessages.MissingEntrySection()); + } + else + { + throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString())); + } + } + + // Add the missing standard action and directory symbols. + this.LoadStandardSymbols(find.SymbolsByName); + + // Resolve the symbol references to find the set of sections we care about for linking. + // Of course, we start with the entry section (that's how it got its name after all). + var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName); + resolve.Execute(); + + if (this.Messaging.EncounteredError) + { + return null; + } + + // Reset the sections to only those that were resolved then flatten the complex + // references that particpate in groups. + sections = resolve.ResolvedSections.ToList(); + + // TODO: consider filtering "localizations" down to only those localizations from + // intermediates in the sections. + + this.FlattenSectionsComplexReferences(sections); + + if (this.Messaging.EncounteredError) + { + return null; + } + + // The hard part in linking is processing the complex references. + var referencedComponents = new HashSet(); + var componentsToFeatures = new ConnectToFeatureCollection(); + var featuresToFeatures = new ConnectToFeatureCollection(); + var modulesToFeatures = new ConnectToFeatureCollection(); + this.ProcessComplexReferences(find.EntrySection, sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures); + + if (this.Messaging.EncounteredError) + { + return null; + } + + // Display an error message for Components that were not referenced by a Feature. + foreach (var symbolWithSection in resolve.ReferencedSymbolWithSections.Where(s => s.Symbol.Definition.Type == SymbolDefinitionType.Component)) + { + if (!referencedComponents.Contains(symbolWithSection.Name)) + { + this.Messaging.Write(ErrorMessages.OrphanedComponent(symbolWithSection.Symbol.SourceLineNumbers, symbolWithSection.Symbol.Id.Id)); + } + } + + // Report duplicates that would ultimately end up being primary key collisions. + { + var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections); + reportDupes.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return null; + } + + // resolve the feature to feature connects + this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.SymbolsByName); + + // Create a new section to hold the linked content. Start with the entry section's + // metadata. + var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type); + + var sectionCount = 0; + + foreach (var section in sections) + { + sectionCount++; + + var sectionId = section.Id; + if (null == sectionId && this.sectionIdOnRows) + { + sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture); + } + + foreach (var symbol in section.Symbols) + { + if (find.RedundantSymbols.Contains(symbol)) + { + continue; + } + + var copySymbol = true; // by default, copy symbols. + + // handle special tables + switch (symbol.Definition.Type) + { + case SymbolDefinitionType.Class: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)ClassSymbolFields.ComponentRef, (int)ClassSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.Extension: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)ExtensionSymbolFields.ComponentRef, (int)ExtensionSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.Assembly: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)AssemblySymbolFields.ComponentRef, (int)AssemblySymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.PublishComponent: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)PublishComponentSymbolFields.ComponentRef, (int)PublishComponentSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.Shortcut: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)ShortcutSymbolFields.ComponentRef, (int)ShortcutSymbolFields.Target, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.TypeLib: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, (int)TypeLibSymbolFields.ComponentRef, (int)TypeLibSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); + } + break; + + case SymbolDefinitionType.WixMerge: + if (SectionType.Product == resolvedSection.Type) + { + this.ResolveFeatures(symbol, -1, (int)WixMergeSymbolFields.FeatureRef, modulesToFeatures, null); + } + break; + + case SymbolDefinitionType.WixComplexReference: + copySymbol = false; + break; + + case SymbolDefinitionType.WixSimpleReference: + copySymbol = false; + break; + + case SymbolDefinitionType.WixVariable: + this.AddWixVariable(wixVariables, (WixVariableSymbol)symbol); + copySymbol = false; // Do not copy the symbol, it will be added later after all overriding has been handled. + break; + } + + if (copySymbol) + { + resolvedSection.AddSymbol(symbol); + } + } + } + + // Copy the module to feature connections into the output. + foreach (ConnectToFeature connectToFeature in modulesToFeatures) + { + foreach (var feature in connectToFeature.ConnectFeatures) + { + resolvedSection.AddSymbol(new WixFeatureModulesSymbol + { + FeatureRef = feature, + WixMergeRef = connectToFeature.ChildId + }); + } + } + + // Correct the section Id in FeatureComponents table. + if (this.sectionIdOnRows) + { +#if TODO_DO_SYMBOLS_NEED_SECTIONIDS + var componentSectionIds = resolvedSection.Symbols.OfType().ToDictionary(c => c.Id.Id, c => c.SectionId); + + foreach (var featureComponentSymbol in resolvedSection.Symbols.OfType()) + { + if (componentSectionIds.TryGetValue(featureComponentSymbol.ComponentRef, out var componentSectionId)) + { + featureComponentSymbol.SectionId = componentSectionId; + } + } +#endif + } + + // Copy the wix variable rows to the output now that all overriding has been accounted for. + foreach (var symbol in wixVariables.Values) + { + resolvedSection.AddSymbol(symbol); + } + + // Bundles have groups of data that must be flattened in a way different from other types. + if (resolvedSection.Type == SectionType.Bundle) + { + var command = new FlattenAndProcessBundleTablesCommand(resolvedSection, this.Messaging); + command.Execute(); + } + + if (this.Messaging.EncounteredError) + { + return null; + } + + var collate = new CollateLocalizationsCommand(this.Messaging, localizations); + var localizationsByCulture = collate.Execute(); + + intermediate = new Intermediate(resolvedSection.Id, Data.IntermediateLevels.Linked, new[] { resolvedSection }, localizationsByCulture); + } + finally + { + foreach (var extension in this.Context.Extensions) + { + extension.PostLink(intermediate); + } + } + + return this.Messaging.EncounteredError ? null : intermediate; + } + + /// + /// Check for colliding values and collect the wix variable rows. + /// + /// Collection of WixVariableSymbols by id. + /// WixVariableSymbol to add, if not overridden. + private void AddWixVariable(Dictionary wixVariables, WixVariableSymbol symbol) + { + var id = symbol.Id.Id; + + if (wixVariables.TryGetValue(id, out var collidingSymbol)) + { + if (collidingSymbol.Overridable && !symbol.Overridable) + { + wixVariables[id] = symbol; + } + else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable)) + { + this.Messaging.Write(ErrorMessages.WixVariableCollision(symbol.SourceLineNumbers, id)); + } + } + else + { + wixVariables.Add(id, symbol); + } + } + + /// + /// Load the standard action and directory symbols. + /// + /// Collection of symbols. + private void LoadStandardSymbols(IDictionary symbolsByName) + { + foreach (var actionSymbol in WindowsInstallerStandard.StandardActions()) + { + var symbolWithSection = new SymbolWithSection(null, actionSymbol); + + // If the action's symbol has not already been defined (i.e. overriden by the user), add it now. + if (!symbolsByName.ContainsKey(symbolWithSection.Name)) + { + symbolsByName.Add(symbolWithSection.Name, symbolWithSection); + } + } + + foreach (var directorySymbol in WindowsInstallerStandard.StandardDirectories()) + { + var symbolWithSection = new SymbolWithSection(null, directorySymbol); + + // If the directory's symbol has not already been defined (i.e. overriden by the user), add it now. + if (!symbolsByName.ContainsKey(symbolWithSection.Name)) + { + symbolsByName.Add(symbolWithSection.Name, symbolWithSection); + } + } + } + + /// + /// Process the complex references. + /// + /// Active section to add symbols to. + /// Sections that are referenced during the link process. + /// Collection of all components referenced by complex reference. + /// Component to feature complex references. + /// Feature to feature complex references. + /// Module to feature complex references. + private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnumerable sections, ISet referencedComponents, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures) + { + var componentsToModules = new Hashtable(); + + foreach (var section in sections) + { + // Need ToList since we might want to add symbols while processing. + foreach (var wixComplexReferenceRow in section.Symbols.OfType().ToList()) + { + ConnectToFeature connection; + switch (wixComplexReferenceRow.ParentType) + { + case ComplexReferenceParentType.Feature: + switch (wixComplexReferenceRow.ChildType) + { + case ComplexReferenceChildType.Component: + connection = componentsToFeatures[wixComplexReferenceRow.Child]; + if (null == connection) + { + componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); + } + else if (wixComplexReferenceRow.IsPrimary) + { + if (connection.IsExplicitPrimaryFeature) + { + this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), connection.PrimaryFeature ?? resolvedSection.Id)); + continue; + } + else + { + connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects + connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature + connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again + } + } + else + { + connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); + } + + // add a row to the FeatureComponents table + section.AddSymbol(new FeatureComponentsSymbol + { + FeatureRef = wixComplexReferenceRow.Parent, + ComponentRef = wixComplexReferenceRow.Child, + }); + + // index the component for finding orphaned records + var symbolName = String.Concat("Component:", wixComplexReferenceRow.Child); + referencedComponents.Add(symbolName); + + break; + + case ComplexReferenceChildType.Feature: + connection = featuresToFeatures[wixComplexReferenceRow.Child]; + if (null != connection) + { + this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); + continue; + } + + featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); + break; + + case ComplexReferenceChildType.Module: + connection = modulesToFeatures[wixComplexReferenceRow.Child]; + if (null == connection) + { + modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); + } + else if (wixComplexReferenceRow.IsPrimary) + { + if (connection.IsExplicitPrimaryFeature) + { + this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); + continue; + } + else + { + connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects + connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature + connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again + } + } + else + { + connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); + } + break; + + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); + } + break; + + case ComplexReferenceParentType.Module: + switch (wixComplexReferenceRow.ChildType) + { + case ComplexReferenceChildType.Component: + if (componentsToModules.ContainsKey(wixComplexReferenceRow.Child)) + { + this.Messaging.Write(ErrorMessages.ComponentReferencedTwice(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.Child)); + continue; + } + else + { + componentsToModules.Add(wixComplexReferenceRow.Child, wixComplexReferenceRow); // should always be new + + // add a row to the ModuleComponents table + section.AddSymbol(new ModuleComponentsSymbol + { + Component = wixComplexReferenceRow.Child, + ModuleID = wixComplexReferenceRow.Parent, + Language = Convert.ToInt32(wixComplexReferenceRow.ParentLanguage), + }); + } + + // index the component for finding orphaned records + var componentSymbolName = String.Concat("Component:", wixComplexReferenceRow.Child); + referencedComponents.Add(componentSymbolName); + + break; + + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); + } + break; + + case ComplexReferenceParentType.Patch: + switch (wixComplexReferenceRow.ChildType) + { + case ComplexReferenceChildType.PatchFamily: + case ComplexReferenceChildType.PatchFamilyGroup: + break; + + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); + } + break; + + case ComplexReferenceParentType.Product: + switch (wixComplexReferenceRow.ChildType) + { + case ComplexReferenceChildType.Feature: + connection = featuresToFeatures[wixComplexReferenceRow.Child]; + if (null != connection) + { + this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); + continue; + } + + featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary)); + break; + + default: + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); + } + break; + + default: + // Note: Groups have been processed before getting here so they are not handled by any case above. + throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType))); + } + } + } + } + + /// + /// Flattens all complex references in all sections in the collection. + /// + /// Sections that are referenced during the link process. + private void FlattenSectionsComplexReferences(IEnumerable sections) + { + var parentGroups = new Dictionary>(); + var parentGroupsSections = new Dictionary(); + var parentGroupsNeedingProcessing = new Dictionary(); + + // DisplaySectionComplexReferences("--- section's complex references before flattening ---", sections); + + // Step 1: Gather all of the complex references that are going to participate + // in the flatting process. This means complex references that have "grouping + // parents" of Features, Modules, and, of course, Groups. These references + // that participate in a "grouping parent" will be removed from their section + // now and after processing added back in Step 3 below. + foreach (var section in sections) + { + var removeSymbols = new List(); + + foreach (var symbol in section.Symbols) + { + // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature, + // and Module. Non-grouping complex references are simple and + // resolved during normal complex reference resolutions. + if (symbol is WixComplexReferenceSymbol wixComplexReferenceRow && + (ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || + ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || + ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || + ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || + ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || + ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType)) + { + var parentTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent); + + // Group all complex references with a common parent + // together so we can find them quickly while processing in + // Step 2. + if (!parentGroups.TryGetValue(parentTypeAndId, out var childrenComplexRefs)) + { + childrenComplexRefs = new List(); + parentGroups.Add(parentTypeAndId, childrenComplexRefs); + } + + childrenComplexRefs.Add(wixComplexReferenceRow); + removeSymbols.Add(wixComplexReferenceRow); + + // Remember the mapping from set of complex references with a common + // parent to their section. We'll need this to add them back to the + // correct section in Step 3. + if (!parentGroupsSections.TryGetValue(parentTypeAndId, out var parentSection)) + { + parentGroupsSections.Add(parentTypeAndId, section); + } + + // If the child of the complex reference is another group, then in Step 2 + // we're going to have to process this complex reference again to copy + // the child group's references into the parent group. + if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) + { + if (!parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)) + { + parentGroupsNeedingProcessing.Add(parentTypeAndId, section); + } + } + } + } + + foreach (var removeSymbol in removeSymbols) + { + section.RemoveSymbol(removeSymbol); + } + } + + Debug.Assert(parentGroups.Count == parentGroupsSections.Count); + Debug.Assert(parentGroupsNeedingProcessing.Count <= parentGroups.Count); + + // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references middle of flattening ---", sections); + + // Step 2: Loop through the parent groups that have nested groups removing + // them from the hash table as they are processed. At the end of this the + // complex references should all be flattened. + var keys = parentGroupsNeedingProcessing.Keys.ToList(); + + foreach (var key in keys) + { + if (parentGroupsNeedingProcessing.ContainsKey(key)) + { + var loopDetector = new Stack(); + this.FlattenGroup(key, loopDetector, parentGroups, parentGroupsNeedingProcessing); + } + else + { + // the group must have allready been procesed and removed from the hash table + } + } + Debug.Assert(0 == parentGroupsNeedingProcessing.Count); + + // Step 3: Finally, ensure that all of the groups that were removed + // in Step 1 and flattened in Step 2 are added to their appropriate + // section. This is where we will toss out the final no-longer-needed + // groups. + foreach (var parentGroup in parentGroups.Keys) + { + var section = parentGroupsSections[parentGroup]; + + foreach (var wixComplexReferenceRow in parentGroups[parentGroup]) + { + if ((ComplexReferenceParentType.FeatureGroup != wixComplexReferenceRow.ParentType) && + (ComplexReferenceParentType.ComponentGroup != wixComplexReferenceRow.ParentType) && + (ComplexReferenceParentType.PatchFamilyGroup != wixComplexReferenceRow.ParentType)) + { + section.AddSymbol(wixComplexReferenceRow); + } + } + } + + // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references after flattening ---", sections); + } + + private string CombineTypeAndId(ComplexReferenceParentType type, string id) + { + return String.Concat(type.ToString(), ":", id); + } + + private string CombineTypeAndId(ComplexReferenceChildType type, string id) + { + return String.Concat(type.ToString(), ":", id); + } + + /// + /// Recursively processes the group. + /// + /// String combination type and id of group to process next. + /// Stack of groups processed thus far. Used to detect loops. + /// Hash table of complex references grouped by parent id. + /// Hash table of parent groups that still have nested groups that need to be flattened. + private void FlattenGroup(string parentTypeAndId, Stack loopDetector, Dictionary> parentGroups, Dictionary parentGroupsNeedingProcessing) + { + Debug.Assert(parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)); + loopDetector.Push(parentTypeAndId); // push this complex reference parent identfier into the stack for loop verifying + + var allNewChildComplexReferences = new List(); + + var referencesToParent = parentGroups[parentTypeAndId]; + foreach (var wixComplexReferenceRow in referencesToParent) + { + Debug.Assert(ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Patch == wixComplexReferenceRow.ParentType); + Debug.Assert(parentTypeAndId == this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent)); + + // We are only interested processing when the child is a group. + if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) + { + var childTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ChildType, wixComplexReferenceRow.Child); + if (loopDetector.Contains(childTypeAndId)) + { + // Create a comma delimited list of the references that participate in the + // loop for the error message. Start at the bottom of the stack and work the + // way up to present the loop as a directed graph. + var loop = String.Join(" -> ", loopDetector); + + this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(wixComplexReferenceRow?.SourceLineNumbers, loop)); + + // Cleanup the parentGroupsNeedingProcessing and the loopDetector just like the + // exit of this method does at the end because we are exiting early. + loopDetector.Pop(); + parentGroupsNeedingProcessing.Remove(parentTypeAndId); + + return; // bail + } + + // Check to see if the child group still needs to be processed. If so, + // go do that so that we'll get all of that children's (and children's + // children) complex references correctly merged into our parent group. + if (parentGroupsNeedingProcessing.ContainsKey(childTypeAndId)) + { + this.FlattenGroup(childTypeAndId, loopDetector, parentGroups, parentGroupsNeedingProcessing); + } + + // If the child is a parent to anything (i.e. the parent has grandchildren) + // clone each of the children's complex references, repoint them to the parent + // complex reference (because we're moving references up the tree), and finally + // add the cloned child's complex reference to the list of complex references + // that we'll eventually add to the parent group. + if (parentGroups.TryGetValue(childTypeAndId, out var referencesToChild)) + { + foreach (var crefChild in referencesToChild) + { + // Only merge up the non-group items since groups are purged + // after this part of the processing anyway (cloning them would + // be a complete waste of time). + if ((ComplexReferenceChildType.FeatureGroup != crefChild.ChildType) || + (ComplexReferenceChildType.ComponentGroup != crefChild.ChildType) || + (ComplexReferenceChildType.PatchFamilyGroup != crefChild.ChildType)) + { + var crefChildClone = crefChild.Clone(); + Debug.Assert(crefChildClone.Parent == wixComplexReferenceRow.Child); + + crefChildClone.Reparent(wixComplexReferenceRow); + allNewChildComplexReferences.Add(crefChildClone); + } + } + } + } + } + + // Add the children group's complex references to the parent + // group. Clean out any left over groups and quietly remove any + // duplicate complex references that occurred during the merge. + referencesToParent.AddRange(allNewChildComplexReferences); + referencesToParent.Sort(ComplexReferenceComparision); + for (var i = referencesToParent.Count - 1; i >= 0; --i) + { + var wixComplexReferenceRow = referencesToParent[i]; + + if ((ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || + (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) + { + referencesToParent.RemoveAt(i); + } + else if (i > 0) + { + // Since the list is already sorted, we can find duplicates by simply + // looking at the next sibling in the list and tossing out one if they + // match. + var crefCompare = referencesToParent[i - 1]; + if (0 == wixComplexReferenceRow.CompareToWithoutConsideringPrimary(crefCompare)) + { + referencesToParent.RemoveAt(i); + } + } + } + + int ComplexReferenceComparision(WixComplexReferenceSymbol x, WixComplexReferenceSymbol y) + { + var comparison = x.ChildType - y.ChildType; + if (0 == comparison) + { + comparison = String.Compare(x.Child, y.Child, StringComparison.Ordinal); + if (0 == comparison) + { + comparison = x.ParentType - y.ParentType; + if (0 == comparison) + { + comparison = String.Compare(x.ParentLanguage ?? String.Empty, y.ParentLanguage ?? String.Empty, StringComparison.Ordinal); + if (0 == comparison) + { + comparison = String.Compare(x.Parent, y.Parent, StringComparison.Ordinal); + } + } + } + } + + return comparison; + } + + loopDetector.Pop(); // pop this complex reference off the stack since we're done verify the loop here + parentGroupsNeedingProcessing.Remove(parentTypeAndId); // remove the newly processed complex reference + } + + /* + /// + /// Debugging method for displaying the section complex references. + /// + /// The header. + /// The sections to display. + private void DisplaySectionComplexReferences(string header, SectionCollection sections) + { + Console.WriteLine(header); + foreach (Section section in sections) + { + Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; + + foreach (WixComplexReferenceRow cref in wixComplexReferenceTable.Rows) + { + Console.WriteLine("Section: {0} Parent: {1} Type: {2} Child: {3} Primary: {4}", section.Id, cref.ParentId, cref.ParentType, cref.ChildId, cref.IsPrimary); + } + } + } + */ + + /// + /// Resolves the features connected to other features in the active output. + /// + /// Feature to feature complex references. + /// All symbols loaded from the sections. + private void ResolveFeatureToFeatureConnects(ConnectToFeatureCollection featuresToFeatures, IDictionary allSymbols) + { + foreach (ConnectToFeature connection in featuresToFeatures) + { + var wixSimpleReferenceRow = new WixSimpleReferenceSymbol + { + Table = "Feature", + PrimaryKeys = connection.ChildId + }; + + if (allSymbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbol)) + { + var featureSymbol = (FeatureSymbol)symbol.Symbol; + featureSymbol.ParentFeatureRef = connection.PrimaryFeature; + } + } + } + + /// + /// Resolve features for columns that have null guid placeholders. + /// + /// Symbol to resolve. + /// Number of the column containing the connection identifier. + /// Number of the column containing the feature. + /// Connect to feature complex references. + /// Hashtable of known components under multiple features. + private void ResolveFeatures(IntermediateSymbol symbol, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents) + { + var connectionId = connectionColumn < 0 ? symbol.Id.Id : symbol.AsString(connectionColumn); + var featureId = symbol.AsString(featureColumn); + + if (EmptyGuid == featureId) + { + var connection = connectToFeatures[connectionId]; + + if (null == connection) + { + // display an error for the component or merge module as appropriate + if (null != multipleFeatureComponents) + { + this.Messaging.Write(ErrorMessages.ComponentExpectedFeature(symbol.SourceLineNumbers, connectionId, symbol.Definition.Name, symbol.Id.Id)); + } + else + { + this.Messaging.Write(ErrorMessages.MergeModuleExpectedFeature(symbol.SourceLineNumbers, connectionId)); + } + } + else + { + // check for unique, implicit, primary feature parents with multiple possible parent features + if (this.ShowPedanticMessages && + !connection.IsExplicitPrimaryFeature && + 0 < connection.ConnectFeatures.Count) + { + // display a warning for the component or merge module as approrpriate + if (null != multipleFeatureComponents) + { + if (!multipleFeatureComponents.Contains(connectionId)) + { + this.Messaging.Write(WarningMessages.ImplicitComponentPrimaryFeature(connectionId)); + + // remember this component so only one warning is generated for it + multipleFeatureComponents[connectionId] = null; + } + } + else + { + this.Messaging.Write(WarningMessages.ImplicitMergeModulePrimaryFeature(connectionId)); + } + } + + // set the feature + symbol.Set(featureColumn, connection.PrimaryFeature); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/LinkerErrors.cs b/src/wix/WixToolset.Core/LinkerErrors.cs new file mode 100644 index 00000000..7ce8c00e --- /dev/null +++ b/src/wix/WixToolset.Core/LinkerErrors.cs @@ -0,0 +1,48 @@ +// 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 +{ + using WixToolset.Data; + + internal static class LinkerErrors + { + public static Message OrphanedPayload(SourceLineNumber sourceLineNumbers, string payloadId) + { + return Message(sourceLineNumbers, Ids.OrphanedPayload, "Found orphaned Payload '{0}'. Make sure to reference it from a Package, the BootstrapperApplication, or the Bundle or move it into its own Fragment so it only gets linked in when actually used.", payloadId); + } + + public static Message PackageInMultipleContainers(SourceLineNumber sourceLineNumbers, string packageId, string containerId1, string containerId2) + { + return Message(sourceLineNumbers, Ids.PackageInMultipleContainers, "The Package '{0}' is referenced from multiple containers - Container '{1}' and Container '{2}'. This is not currently supported.", packageId, containerId1, containerId2); + } + + public static Message PayloadSharedWithBA(SourceLineNumber sourceLineNumbers, string payloadId) + { + return Message(sourceLineNumbers, Ids.PayloadSharedWithBA, "The Payload '{0}' is shared with the BootstrapperApplication. This is not currently supported.", payloadId); + } + + public static Message UnscheduledChainPackage(SourceLineNumber sourceLineNumbers, string packageId) + { + return Message(sourceLineNumbers, Ids.UnscheduledChainPackage, "Found orphaned Package '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", packageId); + } + + public static Message UnscheduledRollbackBoundary(SourceLineNumber sourceLineNumbers, string rollbackBoundaryId) + { + return Message(sourceLineNumbers, Ids.UnscheduledRollbackBoundary, "Found orphaned RollbackBoundary '{0}'. Make sure to reference it from the Chain or move it into its own Fragment so it only gets linked in when actually used.", rollbackBoundaryId); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + OrphanedPayload = 7000, + PackageInMultipleContainers = 7001, + PayloadSharedWithBA = 7002, + UnscheduledChainPackage = 7003, + UnscheduledRollbackBoundary = 7004, + } // last available is 7099. 7100 is WindowsInstallerBackendWarnings. + } +} diff --git a/src/wix/WixToolset.Core/LinkerWarnings.cs b/src/wix/WixToolset.Core/LinkerWarnings.cs new file mode 100644 index 00000000..968fa4ea --- /dev/null +++ b/src/wix/WixToolset.Core/LinkerWarnings.cs @@ -0,0 +1,36 @@ +// 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 +{ + using WixToolset.Data; + + internal static class LinkerWarnings + { + public static Message LayoutPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) + { + return Message(sourceLineNumbers, Ids.LayoutPayloadInContainer, "The layout-only Payload '{0}' is being added to Container '{1}'. It will not be extracted during layout.", payloadId, containerId); + } + + public static Message PayloadInMultipleContainers(SourceLineNumber sourceLineNumbers, string payloadId, string containerId1, string containerId2) + { + return Message(sourceLineNumbers, Ids.PayloadInMultipleContainers, "The Payload '{0}' can't be added to Container '{1}' because it was already added to Container '{2}'.", payloadId, containerId1, containerId2); + } + + public static Message UncompressedPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId) + { + return Message(sourceLineNumbers, Ids.UncompressedPayloadInContainer, "The Payload '{0}' is being added to Container '{1}', overriding its Compressed value of 'no'.", payloadId, containerId); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args); + } + + public enum Ids + { + LayoutPayloadInContainer = 6900, + PayloadInMultipleContainers = 6901, + UncompressedPayloadInContainer = 6902, + } // last available is 6999. 7000 is LinkerErrors. + } +} diff --git a/src/wix/WixToolset.Core/LocalizationParser.cs b/src/wix/WixToolset.Core/LocalizationParser.cs new file mode 100644 index 00000000..d6113fc6 --- /dev/null +++ b/src/wix/WixToolset.Core/LocalizationParser.cs @@ -0,0 +1,326 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class LocalizationParser : ILocalizationParser + { + public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl"; + private const string XmlElementName = "WixLocalization"; + + internal LocalizationParser(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + private IMessaging Messaging { get; } + + public Localization ParseLocalization(string path) + { + var document = XDocument.Load(path); + return this.ParseLocalization(document); + } + + public Localization ParseLocalization(XDocument document) + { + var root = document.Root; + Localization localization = null; + + var sourceLineNumbers = SourceLineNumber.CreateFromXObject(root); + if (LocalizationParser.XmlElementName == root.Name.LocalName) + { + if (LocalizationParser.WxlNamespace == root.Name.Namespace) + { + localization = ParseWixLocalizationElement(this.Messaging, root); + } + else // invalid or missing namespace + { + if (null == root.Name.Namespace) + { + this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, LocalizationParser.WxlNamespace.NamespaceName)); + } + else + { + this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, root.Name.LocalName, LocalizationParser.WxlNamespace.NamespaceName)); + } + } + } + else + { + this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, root.Name.LocalName, "localization", LocalizationParser.XmlElementName)); + } + + return localization; + } + + /// + /// Adds a WixVariableRow to a dictionary while performing the expected override checks. + /// + /// + /// Dictionary of variable rows. + /// Row to add to the variables dictionary. + private static void AddWixVariable(IMessaging messaging, IDictionary variables, BindVariable wixVariableRow) + { + if (!variables.TryGetValue(wixVariableRow.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable)) + { + variables[wixVariableRow.Id] = wixVariableRow; + } + else if (!wixVariableRow.Overridable) + { + messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(wixVariableRow.SourceLineNumbers, wixVariableRow.Id)); + } + } + + /// + /// Parses the WixLocalization element. + /// + /// + /// Element to parse. + private static Localization ParseWixLocalizationElement(IMessaging messaging, XElement node) + { + var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); + int? codepage = null; + int? summaryInformationCodepage = null; + string culture = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Codepage": + codepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); + break; + case "SummaryInformationCodepage": + summaryInformationCodepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers); + break; + case "Culture": + culture = attrib.Value; + break; + case "Language": + // do nothing; @Language is used for locutil which can't convert Culture to lcid + break; + default: + Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); + break; + } + } + else + { + Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); + } + } + + var variables = new Dictionary(); + var localizedControls = new Dictionary(); + + foreach (var child in node.Elements()) + { + if (LocalizationParser.WxlNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "String": + LocalizationParser.ParseString(messaging, child, variables); + break; + + case "UI": + LocalizationParser.ParseUI(messaging, child, localizedControls); + break; + + default: + messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); + break; + } + } + else + { + messaging.Write(ErrorMessages.UnsupportedExtensionElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString())); + } + } + + return messaging.EncounteredError ? null : new Localization(codepage, summaryInformationCodepage, culture, variables, localizedControls); + } + + /// + /// Parse a localization string into a WixVariableRow. + /// + /// + /// Element to parse. + /// + private static void ParseString(IMessaging messaging, XElement node, IDictionary variables) + { + string id = null; + var overridable = false; + var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); + break; + case "Overridable": + overridable = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); + break; + case "Localizable": + ; // do nothing + break; + default: + messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); + break; + } + } + else + { + messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString())); + } + } + + var value = Common.GetInnerText(node); + + if (null == id) + { + messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "String", "Id")); + } + else if (0 == id.Length) + { + messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, "String", "Id", 0)); + } + + if (!messaging.EncounteredError) + { + var variable = new BindVariable + { + SourceLineNumbers = sourceLineNumbers, + Id = id, + Overridable = overridable, + Value = value, + }; + + LocalizationParser.AddWixVariable(messaging, variables, variable); + } + } + + /// + /// Parse a localized control. + /// + /// + /// Element to parse. + /// Dictionary of localized controls. + private static void ParseUI(IMessaging messaging, XElement node, IDictionary localizedControls) + { + string dialog = null; + string control = null; + var x = CompilerConstants.IntegerNotSet; + var y = CompilerConstants.IntegerNotSet; + var width = CompilerConstants.IntegerNotSet; + var height = CompilerConstants.IntegerNotSet; + var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node); + var rightToLeft = false; + var rightAligned = false; + var leftScroll = false; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Dialog": + dialog = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); + break; + case "Control": + control = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib); + break; + case "X": + x = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Y": + y = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Width": + width = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "Height": + height = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue); + break; + case "RightToLeft": + rightToLeft = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); + break; + case "RightAligned": + rightAligned = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); + break; + case "LeftScroll": + leftScroll = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib); + break; + default: + Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); + break; + } + } + else + { + Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib); + } + } + + var text = Common.GetInnerText(node); + + if (String.IsNullOrEmpty(control) && (rightToLeft || rightAligned || leftScroll)) + { + if (rightToLeft) + { + messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightToLeft", "Control")); + } + + if (rightAligned) + { + messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightAligned", "Control")); + } + + if (leftScroll) + { + messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "LeftScroll", "Control")); + } + } + + if (String.IsNullOrEmpty(control) && String.IsNullOrEmpty(dialog)) + { + messaging.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.ToString(), "Dialog", "Control")); + } + + if (!messaging.EncounteredError) + { + var localizedControl = new LocalizedControl(dialog, control, x, y, width, height, rightToLeft, rightAligned, leftScroll, text); + var key = localizedControl.GetKey(); + if (localizedControls.ContainsKey(key)) + { + if (String.IsNullOrEmpty(localizedControl.Control)) + { + messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog)); + } + else + { + messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog, localizedControl.Control)); + } + } + else + { + localizedControls.Add(key, localizedControl); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/ParsedWixVariable.cs b/src/wix/WixToolset.Core/ParsedWixVariable.cs new file mode 100644 index 00000000..9d308b77 --- /dev/null +++ b/src/wix/WixToolset.Core/ParsedWixVariable.cs @@ -0,0 +1,19 @@ +// 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 +{ + internal class ParsedWixVariable + { + public int Index { get; set; } + + public int Length { get; set; } + + public string Namespace { get; set; } + + public string Name { get; set; } + + public string Scope { get; set; } + + public string DefaultValue { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/IfContext.cs b/src/wix/WixToolset.Core/Preprocess/IfContext.cs new file mode 100644 index 00000000..91173c29 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IfContext.cs @@ -0,0 +1,74 @@ +// 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.Preprocess +{ + /// + /// Context for an if statement in the preprocessor. + /// + internal class IfContext + { + private bool keep; + + /// + /// Creates a default if context object, which are used for if's within an inactive preprocessor block + /// + public IfContext() + { + this.WasEverTrue = true; + this.IfState = IfState.If; + } + + /// + /// Creates an if context object. + /// + /// Flag if context is currently active. + /// Flag if context is currently true. + /// State of context to start in. + public IfContext(bool active, bool keep, IfState state) + { + this.Active = active; + this.keep = keep; + this.WasEverTrue = keep; + this.IfState = IfState.If; + } + + /// + /// Gets and sets if this if context is currently active. + /// + /// true if context is active. + public bool Active { get; set; } + + /// + /// Gets and sets if context is current true. + /// + /// true if context is currently true. + public bool IsTrue + { + get + { + return this.keep; + } + + set + { + this.keep = value; + if (this.keep) + { + this.WasEverTrue = true; + } + } + } + + /// + /// Gets if the context was ever true. + /// + /// True if context was ever true. + public bool WasEverTrue { get; private set; } + + /// + /// Gets the current state of the if context. + /// + /// Current state of context. + public IfState IfState { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs new file mode 100644 index 00000000..6b56638a --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs @@ -0,0 +1,28 @@ +// 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.Preprocess +{ + using System; + using WixToolset.Data; + + internal delegate void IfDefEventHandler(object sender, IfDefEventArgs e); + + internal class IfDefEventArgs : EventArgs + { + public IfDefEventArgs(SourceLineNumber sourceLineNumbers, bool isIfDef, bool isDefined, string variableName) + { + this.SourceLineNumbers = sourceLineNumbers; + this.IsIfDef = isIfDef; + this.IsDefined = isDefined; + this.VariableName = variableName; + } + + public SourceLineNumber SourceLineNumbers { get; } + + public bool IsDefined { get; } + + public bool IsIfDef { get; } + + public string VariableName { get; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/IfState.cs b/src/wix/WixToolset.Core/Preprocess/IfState.cs new file mode 100644 index 00000000..f5bb3e87 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IfState.cs @@ -0,0 +1,22 @@ +// 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.Preprocess +{ + /// + /// Current state of the if context. + /// + internal enum IfState + { + /// Context currently in unknown state. + Unknown, + + /// Context currently inside if statement. + If, + + /// Context currently inside elseif statement.. + ElseIf, + + /// Conext currently inside else statement. + Else, + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs new file mode 100644 index 00000000..3c8ff2e8 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs @@ -0,0 +1,43 @@ +// 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.Preprocess +{ + using System; + using WixToolset.Data; + + /// + /// Included file event handler delegate. + /// + /// Sender of the message. + /// Arguments for the included file event. + internal delegate void IncludedFileEventHandler(object sender, IncludedFileEventArgs e); + + /// + /// Event args for included file event. + /// + internal class IncludedFileEventArgs : EventArgs + { + /// + /// Creates a new IncludedFileEventArgs. + /// + /// Source line numbers for the included file. + /// The full path of the included file. + public IncludedFileEventArgs(SourceLineNumber sourceLineNumbers, string fullName) + { + this.SourceLineNumbers = sourceLineNumbers; + this.FullName = fullName; + } + + /// + /// Gets the full path of the included file. + /// + /// The full path of the included file. + public string FullName { get; } + + /// + /// Gets the source line numbers. + /// + /// The source line numbers. + public SourceLineNumber SourceLineNumbers { get; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs b/src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs new file mode 100644 index 00000000..086a0f1a --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs @@ -0,0 +1,19 @@ +// 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.Preprocess +{ + /// + /// Enumeration for preprocessor operations in if statements. + /// + internal enum PreprocessorOperation + { + /// The and operator. + And, + + /// The or operator. + Or, + + /// The not operator. + Not + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs new file mode 100644 index 00000000..672b4b9f --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs @@ -0,0 +1,43 @@ +// 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.Preprocess +{ + using System; + using System.Xml.Linq; + + /// + /// Preprocessed output stream event handler delegate. + /// + /// Sender of the message. + /// Arguments for the preprocessed stream event. + internal delegate void ProcessedStreamEventHandler(object sender, ProcessedStreamEventArgs e); + + /// + /// Event args for preprocessed stream event. + /// + internal class ProcessedStreamEventArgs : EventArgs + { + /// + /// Creates a new ProcessedStreamEventArgs. + /// + /// Source file that is preprocessed. + /// Preprocessed output document. + public ProcessedStreamEventArgs(string sourceFile, XDocument document) + { + this.SourceFile = sourceFile; + this.Document = document; + } + + /// + /// Gets the full path of the source file. + /// + /// The full path of the source file. + public string SourceFile { get; } + + /// + /// Gets the preprocessed output stream. + /// + /// The the preprocessed output stream. + public XDocument Document { get; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs b/src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs new file mode 100644 index 00000000..6d159ad0 --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs @@ -0,0 +1,25 @@ +// 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.Preprocess +{ + using System; + using WixToolset.Data; + + internal delegate void ResolvedVariableEventHandler(object sender, ResolvedVariableEventArgs e); + + internal class ResolvedVariableEventArgs : EventArgs + { + public ResolvedVariableEventArgs(SourceLineNumber sourceLineNumbers, string variableName, string variableValue) + { + this.SourceLineNumbers = sourceLineNumbers; + this.VariableName = variableName; + this.VariableValue = variableValue; + } + + public SourceLineNumber SourceLineNumbers { get; } + + public string VariableName { get; } + + public string VariableValue { get; } + } +} diff --git a/src/wix/WixToolset.Core/PreprocessContext.cs b/src/wix/WixToolset.Core/PreprocessContext.cs new file mode 100644 index 00000000..986045ff --- /dev/null +++ b/src/wix/WixToolset.Core/PreprocessContext.cs @@ -0,0 +1,35 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + + internal class PreprocessContext : IPreprocessContext + { + internal PreprocessContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IReadOnlyCollection Extensions { get; set; } + + public Platform Platform { get; set; } + + public IReadOnlyCollection IncludeSearchPaths { get; set; } + + public string SourcePath { get; set; } + + public IDictionary Variables { get; set; } + + public SourceLineNumber CurrentSourceLineNumber { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/PreprocessResult.cs b/src/wix/WixToolset.Core/PreprocessResult.cs new file mode 100644 index 00000000..83b29a90 --- /dev/null +++ b/src/wix/WixToolset.Core/PreprocessResult.cs @@ -0,0 +1,15 @@ +// 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 +{ + using System.Collections.Generic; + using System.Xml.Linq; + using WixToolset.Extensibility.Data; + + internal class PreprocessResult : IPreprocessResult + { + public XDocument Document { get; set; } + + public IReadOnlyCollection IncludedFiles { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Preprocessor.cs b/src/wix/WixToolset.Core/Preprocessor.cs new file mode 100644 index 00000000..603c0e5b --- /dev/null +++ b/src/wix/WixToolset.Core/Preprocessor.cs @@ -0,0 +1,1520 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using System.Xml; + using System.Xml.Linq; + using WixToolset.Core.Preprocess; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Preprocessor object + /// + internal class Preprocessor : IPreprocessor + { + private static readonly Regex DefineRegex = new Regex(@"^\s*(?.+?)\s*(=\s*(?.+?)\s*)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); + private static readonly Regex PragmaRegex = new Regex(@"^\s*(?.+?)(?[\s\(].+?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); + + private static readonly XmlReaderSettings DocumentXmlReaderSettings = new XmlReaderSettings() + { + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + XmlResolver = null, + }; + + private static readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings() + { + ConformanceLevel = ConformanceLevel.Fragment, + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + XmlResolver = null, + }; + + internal Preprocessor(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = this.ServiceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + /// + /// Event for ifdef/ifndef directives. + /// + public event IfDefEventHandler IfDef; + + /// + /// Event for included files. + /// + public event IncludedFileEventHandler IncludedFile; + + /// + /// Event for preprocessed stream. + /// + public event ProcessedStreamEventHandler ProcessedStream; + + // + // Event for resolved variables. + // + // TOOD: Remove? + //public event ResolvedVariableEventHandler ResolvedVariable; + + /// + /// Get the source line information for the current element. The precompiler will insert + /// special source line number information for each element that it encounters. + /// + /// Element to get source line information for. + /// + /// The source line number used to author the element being processed or + /// null if the preprocessor did not process the element or the node is + /// not an element. + /// + public static SourceLineNumber GetSourceLineNumbers(XObject node) + { + return SourceLineNumber.GetFromXAnnotation(node); + } + + /// + /// Preprocesses a file. + /// + /// The preprocessing context. + /// XDocument with the postprocessed data. + public IPreprocessResult Preprocess(IPreprocessContext context) + { + var state = new ProcessingState(this.ServiceProvider, context); + + this.PreProcess(state); + + IPreprocessResult result; + using (var reader = XmlReader.Create(state.Context.SourcePath, DocumentXmlReaderSettings)) + { + result = this.Process(state, reader); + } + + this.PostProcess(state, result); + + return result; + } + + /// + /// Preprocesses a file. + /// + /// The preprocessing context. + /// XmlReader to processing the context. + /// XDocument with the postprocessed data. + public IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader) + { + if (String.IsNullOrEmpty(context.SourcePath) && !String.IsNullOrEmpty(reader.BaseURI)) + { + var uri = new Uri(reader.BaseURI); + context.SourcePath = uri.AbsolutePath; + } + + var state = new ProcessingState(this.ServiceProvider, context); + + this.PreProcess(state); + + var result = this.Process(state, reader); + + this.PostProcess(state, result); + + return result; + } + + /// + /// Preprocesses a file. + /// + /// The preprocessing context. + /// XmlReader to processing the context. + /// XDocument with the postprocessed data. + private IPreprocessResult Process(ProcessingState state, XmlReader reader) + { + state.CurrentFileStack.Push(state.Helper.GetVariableValue(state.Context, "sys", "SOURCEFILEDIR")); + + // Process the reader into the output. + IPreprocessResult result = null; + try + { + this.PreprocessReader(state, false, reader, state.Output, 0); + + // Fire event with post-processed document. + this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(state.Context.SourcePath, state.Output)); + + if (!this.Messaging.EncounteredError) + { + result = this.ServiceProvider.GetService(); + result.Document = state.Output; + result.IncludedFiles = state.IncludedFiles; + } + } + catch (XmlException e) + { + this.UpdateCurrentLineNumber(state, reader, 0); + throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message)); + } + + return result; + } + + /// + /// Determins if string is an operator. + /// + /// String to check. + /// true if string is an operator. + private static bool IsOperator(string operation) + { + if (operation == null) + { + return false; + } + + operation = operation.Trim(); + if (0 == operation.Length) + { + return false; + } + + if ("=" == operation || + "!=" == operation || + "<" == operation || + "<=" == operation || + ">" == operation || + ">=" == operation || + "~=" == operation) + { + return true; + } + return false; + } + + /// + /// Determines if expression is currently inside quotes. + /// + /// Expression to evaluate. + /// Index to start searching in expression. + /// true if expression is inside in quotes. + private static bool InsideQuotes(string expression, int index) + { + if (index == -1) + { + return false; + } + + var numQuotes = 0; + var tmpIndex = 0; + while (-1 != (tmpIndex = expression.IndexOf('\"', tmpIndex, index - tmpIndex))) + { + numQuotes++; + tmpIndex++; + } + + // found an even number of quotes before the index, so we're not inside + if (numQuotes % 2 == 0) + { + return false; + } + + // found an odd number of quotes, so we are inside + return true; + } + + /// + /// Tests expression to see if it starts with a keyword. + /// + /// Expression to test. + /// Operation to test for. + /// true if expression starts with a keyword. + private static bool StartsWithKeyword(string expression, PreprocessorOperation operation) + { + expression = expression.ToUpperInvariant(); + switch (operation) + { + case PreprocessorOperation.Not: + if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal)) + { + return true; + } + break; + case PreprocessorOperation.And: + if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal)) + { + return true; + } + break; + case PreprocessorOperation.Or: + if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal)) + { + return true; + } + break; + default: + break; + } + return false; + } + + /// + /// Processes an xml reader into an xml writer. + /// + /// + /// Specifies if reader is from an included file. + /// Reader for the source document. + /// Node where content should be added. + /// Original offset for the line numbers being processed. + private void PreprocessReader(ProcessingState state, bool include, XmlReader reader, XContainer container, int offset) + { + var currentContainer = container; + var containerStack = new Stack(); + + var ifContext = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code + var ifStack = new Stack(); + + // process the reader into the writer + while (reader.Read()) + { + // update information here in case an error occurs before the next read + this.UpdateCurrentLineNumber(state, reader, offset); + + var sourceLineNumbers = state.Context.CurrentSourceLineNumber; + + // check for changes in conditional processing + if (XmlNodeType.ProcessingInstruction == reader.NodeType) + { + var ignore = false; + string name = null; + + switch (reader.LocalName) + { + case "if": + ifStack.Push(ifContext); + if (ifContext.IsTrue) + { + ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(state, reader.Value), IfState.If); + } + else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true + { + ifContext = new IfContext(); + } + ignore = true; + break; + + case "ifdef": + ifStack.Push(ifContext); + name = reader.Value.Trim(); + if (ifContext.IsTrue) + { + ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); + } + else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true + { + ifContext = new IfContext(); + } + ignore = true; + this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name)); + break; + + case "ifndef": + ifStack.Push(ifContext); + name = reader.Value.Trim(); + if (ifContext.IsTrue) + { + ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == state.Helper.GetVariableValue(state.Context, name, true)), IfState.If); + } + else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true + { + ifContext = new IfContext(); + } + ignore = true; + this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name)); + break; + + case "elseif": + if (0 == ifStack.Count) + { + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); + } + + if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) + { + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif")); + } + + ifContext.IfState = IfState.ElseIf; // we're now in an elseif + if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test + { + ifContext.IsTrue = this.EvaluateExpression(state, reader.Value); + } + else if (ifContext.IsTrue) + { + ifContext.IsTrue = false; + } + ignore = true; + break; + + case "else": + if (0 == ifStack.Count) + { + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); + } + + if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState) + { + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else")); + } + + ifContext.IfState = IfState.Else; // we're now in an else + ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now + ignore = true; + break; + + case "endif": + if (0 == ifStack.Count) + { + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif")); + } + + ifContext = ifStack.Pop(); + ignore = true; + break; + } + + if (ignore) // ignore this node since we just handled it above + { + continue; + } + } + + if (!ifContext.Active || !ifContext.IsTrue) // if our context is not true then skip the rest of the processing and just read the next thing + { + continue; + } + + switch (reader.NodeType) + { + case XmlNodeType.XmlDeclaration: + var document = currentContainer as XDocument; + if (null != document) + { + document.Declaration = new XDeclaration(null, null, null); + while (reader.MoveToNextAttribute()) + { + switch (reader.LocalName) + { + case "version": + document.Declaration.Version = reader.Value; + break; + + case "encoding": + document.Declaration.Encoding = reader.Value; + break; + + case "standalone": + document.Declaration.Standalone = reader.Value; + break; + } + } + + } + //else + //{ + // display an error? Can this happen? + //} + break; + + case XmlNodeType.ProcessingInstruction: + switch (reader.LocalName) + { + case "define": + this.PreprocessDefine(state, reader.Value); + break; + + case "error": + this.PreprocessError(state, reader.Value); + break; + + case "warning": + this.PreprocessWarning(state, reader.Value); + break; + + case "undef": + this.PreprocessUndef(state, reader.Value); + break; + + case "include": + this.UpdateCurrentLineNumber(state, reader, offset); + this.PreprocessInclude(state, reader.Value, currentContainer); + break; + + case "foreach": + this.PreprocessForeach(state, reader, currentContainer, offset); + break; + + case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error + throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach")); + + case "pragma": + this.PreprocessPragma(state, reader.Value, currentContainer); + break; + + default: + // unknown processing instructions are currently ignored + break; + } + break; + + case XmlNodeType.Element: + if (0 < state.IncludeNextStack.Count && state.IncludeNextStack.Peek()) + { + if ("Include" != reader.LocalName) + { + this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include")); + } + + state.IncludeNextStack.Pop(); + state.IncludeNextStack.Push(false); + break; + } + + var empty = reader.IsEmptyElement; + var ns = XNamespace.Get(reader.NamespaceURI); + var element = new XElement(ns + reader.LocalName); + currentContainer.Add(element); + + this.UpdateCurrentLineNumber(state, reader, offset); + element.AddAnnotation(sourceLineNumbers); + + while (reader.MoveToNextAttribute()) + { + var value = state.Helper.PreprocessString(state.Context, reader.Value); + + var attribNamespace = XNamespace.Get(reader.NamespaceURI); + attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace; + + element.Add(new XAttribute(attribNamespace + reader.LocalName, value)); + } + + if (!empty) + { + containerStack.Push(currentContainer); + currentContainer = element; + } + break; + + case XmlNodeType.EndElement: + if (0 < reader.Depth || !include) + { + currentContainer = containerStack.Pop(); + } + break; + + case XmlNodeType.Text: + var postprocessedText = state.Helper.PreprocessString(state.Context, reader.Value); + currentContainer.Add(postprocessedText); + break; + + case XmlNodeType.CDATA: + var postprocessedValue = state.Helper.PreprocessString(state.Context, reader.Value); + currentContainer.Add(new XCData(postprocessedValue)); + break; + + default: + break; + } + } + + if (0 != ifStack.Count) + { + throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "if", "endif")); + } + + // TODO: can this actually happen? + if (0 != containerStack.Count) + { + throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "nodes", "nodes")); + } + } + + /// + /// Processes an error processing instruction. + /// + /// + /// Text from source. + private void PreprocessError(ProcessingState state, string errorMessage) + { + // Resolve other variables in the error message. + errorMessage = state.Helper.PreprocessString(state.Context, errorMessage); + + throw new WixException(ErrorMessages.PreprocessorError(state.Context.CurrentSourceLineNumber, errorMessage)); + } + + /// + /// Processes a warning processing instruction. + /// + /// + /// Text from source. + private void PreprocessWarning(ProcessingState state, string warningMessage) + { + // Resolve other variables in the warning message. + warningMessage = state.Helper.PreprocessString(state.Context, warningMessage); + + this.Messaging.Write(WarningMessages.PreprocessorWarning(state.Context.CurrentSourceLineNumber, warningMessage)); + } + + /// + /// Processes a define processing instruction and creates the appropriate parameter. + /// + /// + /// Text from source. + private void PreprocessDefine(ProcessingState state, string originalDefine) + { + var match = DefineRegex.Match(originalDefine); + + if (!match.Success) + { + throw new WixException(ErrorMessages.IllegalDefineStatement(state.Context.CurrentSourceLineNumber, originalDefine)); + } + + var defineName = match.Groups["varName"].Value; + var defineValue = match.Groups["varValue"].Value; + + // strip off the optional quotes + if (1 < defineValue.Length && + ((defineValue.StartsWith("\"", StringComparison.Ordinal) && defineValue.EndsWith("\"", StringComparison.Ordinal)) + || (defineValue.StartsWith("'", StringComparison.Ordinal) && defineValue.EndsWith("'", StringComparison.Ordinal)))) + { + defineValue = defineValue.Substring(1, defineValue.Length - 2); + } + + // resolve other variables in the variable value + defineValue = state.Helper.PreprocessString(state.Context, defineValue); + + if (defineName.StartsWith("var.", StringComparison.Ordinal)) + { + state.Helper.AddVariable(state.Context, defineName.Substring(4), defineValue); + } + else + { + state.Helper.AddVariable(state.Context, defineName, defineValue); + } + } + + /// + /// Processes an undef processing instruction and creates the appropriate parameter. + /// + /// + /// Text from source. + private void PreprocessUndef(ProcessingState state, string originalDefine) + { + var name = state.Helper.PreprocessString(state.Context, originalDefine.Trim()); + + if (name.StartsWith("var.", StringComparison.Ordinal)) + { + state.Helper.RemoveVariable(state.Context, name.Substring(4)); + } + else + { + state.Helper.RemoveVariable(state.Context, name); + } + } + + /// + /// Processes an included file. + /// + /// + /// Path to included file. + /// Parent container for included content. + private void PreprocessInclude(ProcessingState state, string includePath, XContainer parent) + { + var sourceLineNumbers = state.Context.CurrentSourceLineNumber; + + // Preprocess variables in the path. + includePath = state.Helper.PreprocessString(state.Context, includePath); + + var includeFile = this.GetIncludeFile(state, includePath); + + if (null == includeFile) + { + throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, includePath, "include")); + } + + using (var reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings)) + { + this.PushInclude(state, includeFile); + + // process the included reader into the writer + try + { + this.PreprocessReader(state, true, reader, parent, 0); + } + catch (XmlException e) + { + this.UpdateCurrentLineNumber(state, reader, 0); + throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "source", e.Message)); + } + + this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile)); + + var includedFile = this.ServiceProvider.GetService(); + includedFile.Path = includeFile; + includedFile.SourceLineNumbers = sourceLineNumbers; + + state.IncludedFiles.Add(includedFile); + + this.PopInclude(state); + } + } + + /// + /// Preprocess a foreach processing instruction. + /// + /// + /// The xml reader. + /// The container where to output processed data. + /// Offset for the line numbers. + private void PreprocessForeach(ProcessingState state, XmlReader reader, XContainer container, int offset) + { + // Find the "in" token. + var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal); + if (0 > indexOfInToken) + { + throw new WixException(ErrorMessages.IllegalForeach(state.Context.CurrentSourceLineNumber, reader.Value)); + } + + // parse out the variable name + var varName = reader.Value.Substring(0, indexOfInToken).Trim(); + var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim(); + + // preprocess the variable values string because it might be a variable itself + varValuesString = state.Helper.PreprocessString(state.Context, varValuesString); + + var varValues = varValuesString.Split(';'); + + // go through all the empty strings + while (reader.Read() && XmlNodeType.Whitespace == reader.NodeType) + { + } + + // get the offset of this xml fragment (for some reason its always off by 1) + var lineInfoReader = reader as IXmlLineInfo; + if (null != lineInfoReader) + { + offset += lineInfoReader.LineNumber - 1; + } + + var textReader = reader as XmlTextReader; + // dump the xml to a string (maintaining whitespace if possible) + if (null != textReader) + { + textReader.WhitespaceHandling = WhitespaceHandling.All; + } + + var fragmentBuilder = new StringBuilder(); + var nestedForeachCount = 1; + while (nestedForeachCount != 0) + { + if (reader.NodeType == XmlNodeType.ProcessingInstruction) + { + switch (reader.LocalName) + { + case "foreach": + ++nestedForeachCount; + // Output the foreach statement + fragmentBuilder.AppendFormat("", reader.Value); + break; + + case "endforeach": + --nestedForeachCount; + if (0 != nestedForeachCount) + { + fragmentBuilder.Append(""); + } + break; + + default: + fragmentBuilder.AppendFormat("", reader.LocalName, reader.Value); + break; + } + } + else if (reader.NodeType == XmlNodeType.Element) + { + fragmentBuilder.Append(reader.ReadOuterXml()); + continue; + } + else if (reader.NodeType == XmlNodeType.Whitespace) + { + // Or output the whitespace + fragmentBuilder.Append(reader.Value); + } + else if (reader.NodeType == XmlNodeType.None) + { + throw new WixException(ErrorMessages.ExpectedEndforeach(state.Context.CurrentSourceLineNumber)); + } + + reader.Read(); + } + + using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString()))) + { + // process each iteration, updating the variable's value each time + foreach (var varValue in varValues) + { + using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings)) + { + // Always overwrite foreach variables. + state.Helper.AddVariable(state.Context, varName, varValue, false); + + try + { + this.PreprocessReader(state, false, loopReader, container, offset); + } + catch (XmlException e) + { + this.UpdateCurrentLineNumber(state, loopReader, offset); + throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message)); + } + + fragmentStream.Position = 0; // seek back to the beginning for the next loop. + } + } + } + } + + /// + /// Processes a pragma processing instruction + /// + /// + /// Text from source. + /// + private void PreprocessPragma(ProcessingState state, string pragmaText, XContainer parent) + { + var match = PragmaRegex.Match(pragmaText); + + if (!match.Success) + { + throw new WixException(ErrorMessages.InvalidPreprocessorPragma(state.Context.CurrentSourceLineNumber, pragmaText)); + } + + // resolve other variables in the pragma argument(s) + var pragmaArgs = state.Helper.PreprocessString(state.Context, match.Groups["pragmaValue"].Value).Trim(); + + try + { + state.Helper.PreprocessPragma(state.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent); + } + catch (Exception e) + { + throw new WixException(ErrorMessages.PreprocessorExtensionPragmaFailed(state.Context.CurrentSourceLineNumber, pragmaText, e.Message)); + } + } + + /// + /// Gets the next token in an expression. + /// + /// + /// Expression to parse. + /// Expression with token removed. + /// Flag if token is a string literal instead of a variable. + /// Next token. + private string GetNextToken(ProcessingState state, string originalExpression, ref string expression, out bool stringLiteral) + { + stringLiteral = false; + var token = String.Empty; + expression = expression.Trim(); + if (0 == expression.Length) + { + return String.Empty; + } + + if (expression.StartsWith("\"", StringComparison.Ordinal)) + { + stringLiteral = true; + var endingQuotes = expression.IndexOf('\"', 1); + if (-1 == endingQuotes) + { + throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + // cut the quotes off the string + token = state.Helper.PreprocessString(state.Context, expression.Substring(1, endingQuotes - 1)); + + // advance past this string + expression = expression.Substring(endingQuotes + 1).Trim(); + } + else if (expression.StartsWith("$(", StringComparison.Ordinal)) + { + // Find the ending paren of the expression + var endingParen = -1; + var openedCount = 1; + for (var i = 2; i < expression.Length; i++) + { + if ('(' == expression[i]) + { + openedCount++; + } + else if (')' == expression[i]) + { + openedCount--; + } + + if (openedCount == 0) + { + endingParen = i; + break; + } + } + + if (-1 == endingParen) + { + throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + token = expression.Substring(0, endingParen + 1); + + // Advance past this variable + expression = expression.Substring(endingParen + 1).Trim(); + } + else + { + // Cut the token off at the next equal, space, inequality operator, + // or end of string, whichever comes first + var space = expression.IndexOf(" ", StringComparison.Ordinal); + var equals = expression.IndexOf("=", StringComparison.Ordinal); + var lessThan = expression.IndexOf("<", StringComparison.Ordinal); + var lessThanEquals = expression.IndexOf("<=", StringComparison.Ordinal); + var greaterThan = expression.IndexOf(">", StringComparison.Ordinal); + var greaterThanEquals = expression.IndexOf(">=", StringComparison.Ordinal); + var notEquals = expression.IndexOf("!=", StringComparison.Ordinal); + var equalsNoCase = expression.IndexOf("~=", StringComparison.Ordinal); + int closingIndex; + + if (space == -1) + { + space = Int32.MaxValue; + } + + if (equals == -1) + { + equals = Int32.MaxValue; + } + + if (lessThan == -1) + { + lessThan = Int32.MaxValue; + } + + if (lessThanEquals == -1) + { + lessThanEquals = Int32.MaxValue; + } + + if (greaterThan == -1) + { + greaterThan = Int32.MaxValue; + } + + if (greaterThanEquals == -1) + { + greaterThanEquals = Int32.MaxValue; + } + + if (notEquals == -1) + { + notEquals = Int32.MaxValue; + } + + if (equalsNoCase == -1) + { + equalsNoCase = Int32.MaxValue; + } + + closingIndex = Math.Min(space, Math.Min(equals, Math.Min(lessThan, Math.Min(lessThanEquals, Math.Min(greaterThan, Math.Min(greaterThanEquals, Math.Min(equalsNoCase, notEquals))))))); + + if (Int32.MaxValue == closingIndex) + { + closingIndex = expression.Length; + } + + // If the index is 0, we hit an operator, so return it + if (0 == closingIndex) + { + // Length 2 operators + if (closingIndex == lessThanEquals || closingIndex == greaterThanEquals || closingIndex == notEquals || closingIndex == equalsNoCase) + { + closingIndex = 2; + } + else // Length 1 operators + { + closingIndex = 1; + } + } + + // Cut out the new token + token = expression.Substring(0, closingIndex).Trim(); + expression = expression.Substring(closingIndex).Trim(); + } + + return token; + } + + /// + /// Gets the value for a variable. + /// + /// + /// Original expression for error message. + /// Variable to evaluate. + /// Value of variable. + private string EvaluateVariable(ProcessingState state, string originalExpression, string variable) + { + // By default it's a literal and will only be evaluated if it + // matches the variable format + var varValue = variable; + + if (variable.StartsWith("$(", StringComparison.Ordinal)) + { + try + { + varValue = state.Helper.PreprocessString(state.Context, variable); + } + catch (ArgumentNullException) + { + // non-existent variables are expected + varValue = null; + } + } + else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1) + { + // make sure it doesn't contain parenthesis + throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1) + { + // shouldn't contain quotes + throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + return varValue; + } + + /// + /// Gets the left side value, operator, and right side value of an expression. + /// + /// + /// Original expression to evaluate. + /// Expression modified while processing. + /// Left side value from expression. + /// Operation in expression. + /// Right side value from expression. + private void GetNameValuePair(ProcessingState state, string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue) + { + leftValue = this.GetNextToken(state, originalExpression, ref expression, out var stringLiteral); + + // If it wasn't a string literal, evaluate it + if (!stringLiteral) + { + leftValue = this.EvaluateVariable(state, originalExpression, leftValue); + } + + // Get the operation + operation = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral); + if (IsOperator(operation)) + { + if (stringLiteral) + { + throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + rightValue = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral); + + // If it wasn't a string literal, evaluate it + if (!stringLiteral) + { + rightValue = this.EvaluateVariable(state, originalExpression, rightValue); + } + } + else + { + // Prepend the token back on the expression since it wasn't an operator + // and put the quotes back on the literal if necessary + + if (stringLiteral) + { + operation = "\"" + operation + "\""; + } + expression = (operation + " " + expression).Trim(); + + // If no operator, just check for existence + operation = ""; + rightValue = ""; + } + } + + /// + /// Evaluates an expression. + /// + /// + /// Original expression to evaluate. + /// Expression modified while processing. + /// true if expression evaluates to true. + private bool EvaluateAtomicExpression(ProcessingState state, string originalExpression, ref string expression) + { + // Quick test to see if the first token is a variable + var startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal); + this.GetNameValuePair(state, originalExpression, ref expression, out var leftValue, out var operation, out var rightValue); + + var expressionValue = false; + + // If the variables don't exist, they were evaluated to null + if (null == leftValue || null == rightValue) + { + if (operation.Length > 0) + { + throw new WixException(ErrorMessages.ExpectedVariable(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + // false expression + } + else if (operation.Length == 0) + { + // There is no right side of the equation. + // If the variable was evaluated, it exists, so the expression is true + if (startsWithVariable) + { + expressionValue = true; + } + else + { + throw new WixException(ErrorMessages.UnexpectedLiteral(state.Context.CurrentSourceLineNumber, originalExpression)); + } + } + else + { + leftValue = leftValue.Trim(); + rightValue = rightValue.Trim(); + if ("=" == operation) + { + if (leftValue == rightValue) + { + expressionValue = true; + } + } + else if ("!=" == operation) + { + if (leftValue != rightValue) + { + expressionValue = true; + } + } + else if ("~=" == operation) + { + if (String.Equals(leftValue, rightValue, StringComparison.OrdinalIgnoreCase)) + { + expressionValue = true; + } + } + else + { + // Convert the numbers from strings + int rightInt; + int leftInt; + try + { + rightInt = Int32.Parse(rightValue, CultureInfo.InvariantCulture); + leftInt = Int32.Parse(leftValue, CultureInfo.InvariantCulture); + } + catch (FormatException) + { + throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + catch (OverflowException) + { + throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + // Compare the numbers + if ("<" == operation && leftInt < rightInt || + "<=" == operation && leftInt <= rightInt || + ">" == operation && leftInt > rightInt || + ">=" == operation && leftInt >= rightInt) + { + expressionValue = true; + } + } + } + + return expressionValue; + } + + /// + /// Gets a sub-expression in parenthesis. + /// + /// + /// Original expression to evaluate. + /// Expression modified while processing. + /// Index of end of sub-expression. + /// Sub-expression in parenthesis. + private string GetParenthesisExpression(ProcessingState state, string originalExpression, string expression, out int endSubExpression) + { + endSubExpression = 0; + + // if the expression doesn't start with parenthesis, leave it alone + if (!expression.StartsWith("(", StringComparison.Ordinal)) + { + return expression; + } + + // search for the end of the expression with the matching paren + var openParenIndex = 0; + var closeParenIndex = 1; + while (openParenIndex != -1 && openParenIndex < closeParenIndex) + { + closeParenIndex = expression.IndexOf(')', closeParenIndex); + if (closeParenIndex == -1) + { + throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + if (InsideQuotes(expression, closeParenIndex)) + { + // ignore stuff inside quotes (it's a string literal) + } + else + { + // Look to see if there is another open paren before the close paren + // and skip over the open parens while they are in a string literal + do + { + openParenIndex++; + openParenIndex = expression.IndexOf('(', openParenIndex, closeParenIndex - openParenIndex); + } + while (InsideQuotes(expression, openParenIndex)); + } + + // Advance past the closing paren + closeParenIndex++; + } + + endSubExpression = closeParenIndex; + + // Return the expression minus the parenthesis + return expression.Substring(1, closeParenIndex - 2); + } + + /// + /// Updates expression based on operation. + /// + /// + /// State to update. + /// Operation to apply to current value. + /// Previous result. + private void UpdateExpressionValue(ProcessingState state, ref bool currentValue, PreprocessorOperation operation, bool prevResult) + { + switch (operation) + { + case PreprocessorOperation.And: + currentValue = currentValue && prevResult; + break; + case PreprocessorOperation.Or: + currentValue = currentValue || prevResult; + break; + case PreprocessorOperation.Not: + currentValue = !currentValue; + break; + default: + throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(state.Context.CurrentSourceLineNumber, operation.ToString())); + } + } + + /// + /// Evaluate an expression. + /// + /// + /// Expression to evaluate. + /// Boolean result of expression. + private bool EvaluateExpression(ProcessingState state, string expression) + { + var tmpExpression = expression; + return this.EvaluateExpressionRecurse(state, expression, ref tmpExpression, PreprocessorOperation.And, true); + } + + /// + /// Recurse through the expression to evaluate if it is true or false. + /// The expression is evaluated left to right. + /// The expression is case-sensitive (converted to upper case) with the + /// following exceptions: variable names and keywords (and, not, or). + /// Comparisons with = and != are string comparisons. + /// Comparisons with inequality operators must be done on valid integers. + /// + /// The operator precedence is: + /// "" + /// () + /// <, >, <=, >=, =, != + /// Not + /// And, Or + /// + /// Valid expressions include: + /// not $(var.B) or not $(var.C) + /// (($(var.A))and $(var.B) ="2")or Not((($(var.C))) and $(var.A)) + /// (($(var.A)) and $(var.B) = " 3 ") or $(var.C) + /// $(var.A) and $(var.C) = "3" or $(var.C) and $(var.D) = $(env.windir) + /// $(var.A) and $(var.B)>2 or $(var.B) <= 2 + /// $(var.A) != "2" + /// + /// + /// The original expression + /// The expression currently being evaluated + /// The operation to apply to this result + /// The previous result to apply to this result + /// Boolean to indicate if the expression is true or false + private bool EvaluateExpressionRecurse(ProcessingState state, string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult) + { + var expressionValue = false; + expression = expression.Trim(); + if (expression.Length == 0) + { + throw new WixException(ErrorMessages.UnexpectedEmptySubexpression(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + // If the expression starts with parenthesis, evaluate it + if (expression.IndexOf('(') == 0) + { + var subExpression = this.GetParenthesisExpression(state, originalExpression, expression, out var endSubExpressionIndex); + expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref subExpression, PreprocessorOperation.And, true); + + // Now get the rest of the expression that hasn't been evaluated + expression = expression.Substring(endSubExpressionIndex).Trim(); + } + else + { + // Check for NOT + if (StartsWithKeyword(expression, PreprocessorOperation.Not)) + { + expression = expression.Substring(3).Trim(); + if (expression.Length == 0) + { + throw new WixException(ErrorMessages.ExpectedExpressionAfterNot(state.Context.CurrentSourceLineNumber, originalExpression)); + } + + expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Not, true); + } + else // Expect a literal + { + expressionValue = this.EvaluateAtomicExpression(state, originalExpression, ref expression); + + // Expect the literal that was just evaluated to already be cut off + } + } + this.UpdateExpressionValue(state, ref expressionValue, prevResultOperation, prevResult); + + // If there's still an expression left, it must start with AND or OR. + if (expression.Trim().Length > 0) + { + if (StartsWithKeyword(expression, PreprocessorOperation.And)) + { + expression = expression.Substring(3); + return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.And, expressionValue); + } + else if (StartsWithKeyword(expression, PreprocessorOperation.Or)) + { + expression = expression.Substring(2); + return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Or, expressionValue); + } + else + { + throw new WixException(ErrorMessages.InvalidSubExpression(state.Context.CurrentSourceLineNumber, expression, originalExpression)); + } + } + + return expressionValue; + } + + /// + /// Update the current line number with the reader's current state. + /// + /// + /// The xml reader for the preprocessor. + /// This is the artificial offset of the line numbers from the reader. Used for the foreach processing. + private void UpdateCurrentLineNumber(ProcessingState state, XmlReader reader, int offset) + { + var lineInfoReader = reader as IXmlLineInfo; + if (null != lineInfoReader) + { + var newLine = lineInfoReader.LineNumber + offset; + + if (state.Context.CurrentSourceLineNumber.LineNumber != newLine) + { + state.Context.CurrentSourceLineNumber = new SourceLineNumber(state.Context.CurrentSourceLineNumber.FileName, state.Context.CurrentSourceLineNumber.Parent, newLine); + } + } + } + + /// + /// Pushes a file name on the stack of included files. + /// + /// + /// Name to push on to the stack of included files. + private void PushInclude(ProcessingState state, string fileName) + { + if (1023 < state.CurrentFileStack.Count) + { + throw new WixException(ErrorMessages.TooDeeplyIncluded(state.Context.CurrentSourceLineNumber, state.CurrentFileStack.Count)); + } + + var path = Path.GetFullPath(fileName); + + state.CurrentFileStack.Push(path); + state.SourceStack.Push(state.Context.CurrentSourceLineNumber); + state.Context.CurrentSourceLineNumber = new SourceLineNumber(path, state.Context.CurrentSourceLineNumber); + state.IncludeNextStack.Push(true); + } + + /// + /// Pops a file name from the stack of included files. + /// + private void PopInclude(ProcessingState state) + { + state.Context.CurrentSourceLineNumber = state.SourceStack.Pop(); + + state.CurrentFileStack.Pop(); + state.IncludeNextStack.Pop(); + } + + /// + /// Go through search paths, looking for a matching include file. + /// Start the search in the directory of the source file, then go + /// through the search paths in the order given on the command line + /// (leftmost first, ...). + /// + /// + /// User-specified path to the included file (usually just the file name). + /// Returns a FileInfo for the found include file, or null if the file cannot be found. + private string GetIncludeFile(ProcessingState state, string includePath) + { + string finalIncludePath = null; + + includePath = includePath.Trim(); + + // remove quotes (only if they match) + if ((includePath.StartsWith("\"", StringComparison.Ordinal) && includePath.EndsWith("\"", StringComparison.Ordinal)) || + (includePath.StartsWith("'", StringComparison.Ordinal) && includePath.EndsWith("'", StringComparison.Ordinal))) + { + includePath = includePath.Substring(1, includePath.Length - 2); + } + + // check if the include file is a full path + if (Path.IsPathRooted(includePath)) + { + if (File.Exists(includePath)) + { + finalIncludePath = includePath; + } + } + else // relative path + { + // build a string to test the directory containing the source file first + var currentFolder = state.CurrentFileStack.Peek(); + var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder), includePath); + + // test the source file directory + if (File.Exists(includeTestPath)) + { + finalIncludePath = includeTestPath; + } + else if (state.Context.IncludeSearchPaths != null) // test all search paths in the order specified on the command line + { + foreach (var includeSearchPath in state.Context.IncludeSearchPaths) + { + // if the path exists, we have found the final string + includeTestPath = Path.Combine(includeSearchPath, includePath); + if (File.Exists(includeTestPath)) + { + finalIncludePath = includeTestPath; + break; + } + } + } + } + + return finalIncludePath; + } + + private void PreProcess(ProcessingState state) + { + if (state.Context.Extensions == null) + { + return; + } + + foreach (var extension in state.Context.Extensions) + { + if (extension.Prefixes != null) + { + foreach (var prefix in extension.Prefixes) + { + if (!state.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension)) + { + state.ExtensionsByPrefix.Add(prefix, extension); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString())); + } + } + } + + extension.PrePreprocess(state.Context); + } + } + + private void PostProcess(ProcessingState state, IPreprocessResult result) + { + if (state.Context.Extensions == null) + { + return; + } + + foreach (var extension in state.Context.Extensions) + { + extension.PostPreprocess(result); + } + } + + private class ProcessingState + { + public ProcessingState(IServiceProvider serviceProvider, IPreprocessContext context) + { + var path = Path.GetFullPath(context.SourcePath); + + this.Context = context; + this.Context.CurrentSourceLineNumber = new SourceLineNumber(path); + this.Context.Variables = this.Context.Variables == null ? new Dictionary() : new Dictionary(this.Context.Variables); + + this.Helper = serviceProvider.GetService(); + } + + public IPreprocessContext Context { get; } + + public IPreprocessHelper Helper { get; } + + public List IncludedFiles { get; } = new List(); + + public XDocument Output { get; } = new XDocument(); + + public Stack CurrentFileStack { get; } = new Stack(); + + public Dictionary ExtensionsByPrefix { get; } = new Dictionary(); + + public Stack IncludeNextStack { get; } = new Stack(); + + public Stack SourceStack { get; } = new Stack(); + } + } +} diff --git a/src/wix/WixToolset.Core/Properties/AssemblyInfo.cs b/src/wix/WixToolset.Core/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..81274e3f --- /dev/null +++ b/src/wix/WixToolset.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +// 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. + +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] diff --git a/src/wix/WixToolset.Core/ResolveContext.cs b/src/wix/WixToolset.Core/ResolveContext.cs new file mode 100644 index 00000000..638c8079 --- /dev/null +++ b/src/wix/WixToolset.Core/ResolveContext.cs @@ -0,0 +1,42 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Threading; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ResolveContext : IResolveContext + { + internal ResolveContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public IReadOnlyCollection BindPaths { get; set; } + + public IReadOnlyCollection Extensions { get; set; } + + public IReadOnlyCollection ExtensionData { get; set; } + + public IReadOnlyCollection FilterCultures { get; set; } + + public string IntermediateFolder { get; set; } + + public Intermediate IntermediateRepresentation { get; set; } + + public IReadOnlyCollection Localizations { get; set; } + + public IVariableResolver VariableResolver { get; set; } + + public bool AllowUnresolvedVariables { get; set; } + + public CancellationToken CancellationToken { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ResolveFileResult.cs b/src/wix/WixToolset.Core/ResolveFileResult.cs new file mode 100644 index 00000000..f6e201d4 --- /dev/null +++ b/src/wix/WixToolset.Core/ResolveFileResult.cs @@ -0,0 +1,14 @@ +// 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 +{ + using System.Collections.Generic; + using WixToolset.Extensibility.Data; + + internal class ResolveFileResult : IResolveFileResult + { + public string Path { get; set; } + + public IReadOnlyCollection CheckedPaths { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ResolveResult.cs b/src/wix/WixToolset.Core/ResolveResult.cs new file mode 100644 index 00000000..fa8e09b7 --- /dev/null +++ b/src/wix/WixToolset.Core/ResolveResult.cs @@ -0,0 +1,23 @@ +// 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 +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + + internal class ResolveResult : IResolveResult + { + public int? Codepage { get; set; } + + public int? SummaryInformationCodepage { get; set; } + + public int? PackageLcid { get; set; } + + public IReadOnlyCollection DelayedFields { get; set; } + + public IReadOnlyCollection ExpectedEmbeddedFiles { get; set; } + + public Intermediate IntermediateRepresentation { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/ResolvedCabinet.cs b/src/wix/WixToolset.Core/ResolvedCabinet.cs new file mode 100644 index 00000000..be04831f --- /dev/null +++ b/src/wix/WixToolset.Core/ResolvedCabinet.cs @@ -0,0 +1,22 @@ +// 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 +{ + using WixToolset.Extensibility.Data; + + /// + /// Data returned from build file manager ResolveCabinet callback. + /// + internal class ResolvedCabinet : IResolvedCabinet + { + /// + /// Gets or sets the build option for the resolved cabinet. + /// + public CabinetBuildOption BuildOption { get; set; } + + /// + /// Gets or sets the path for the resolved cabinet. + /// + public string Path { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Resolver.cs b/src/wix/WixToolset.Core/Resolver.cs new file mode 100644 index 00000000..e93f8e1b --- /dev/null +++ b/src/wix/WixToolset.Core/Resolver.cs @@ -0,0 +1,304 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Resolver for the WiX toolset. + /// + internal class Resolver : IResolver + { + internal Resolver(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + this.Messaging = serviceProvider.GetService(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + public IResolveResult Resolve(IResolveContext context) + { + foreach (var extension in context.Extensions) + { + extension.PreResolve(context); + } + + ResolveResult resolveResult = null; + try + { + var filteredLocalizations = FilterLocalizations(context); + + var variableResolver = this.CreateVariableResolver(context, filteredLocalizations); + + this.LocalizeUI(variableResolver, context.IntermediateRepresentation); + + resolveResult = this.DoResolve(context, variableResolver); + + var primaryLocalization = filteredLocalizations.FirstOrDefault(); + + if (primaryLocalization != null) + { + this.TryGetCultureInfo(primaryLocalization.Culture, out var cultureInfo); + + resolveResult.Codepage = primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; + + resolveResult.SummaryInformationCodepage = primaryLocalization.SummaryInformationCodepage ?? primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage; + + resolveResult.PackageLcid = cultureInfo?.LCID; + } + } + finally + { + foreach (var extension in context.Extensions) + { + extension.PostResolve(resolveResult); + } + } + + return resolveResult; + } + + private ResolveResult DoResolve(IResolveContext context, IVariableResolver variableResolver) + { + var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch); + + var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); + + IReadOnlyCollection delayedFields; + { + var command = new ResolveFieldsCommand(); + command.Messaging = this.Messaging; + command.BuildingPatch = buildingPatch; + command.VariableResolver = variableResolver; + command.BindPaths = context.BindPaths; + command.Extensions = context.Extensions; + command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; + command.IntermediateFolder = context.IntermediateFolder; + command.Intermediate = context.IntermediateRepresentation; + command.SupportDelayedResolution = true; + command.AllowUnresolvedVariables = context.AllowUnresolvedVariables; + command.Execute(); + + delayedFields = command.DelayedFields; + } + +#if TODO_PATCHING + if (context.IntermediateRepresentation.SubStorages != null) + { + foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages) + { + var command = new ResolveFieldsCommand(); + command.BuildingPatch = buildingPatch; + command.BindVariableResolver = context.WixVariableResolver; + command.BindPaths = context.BindPaths; + command.Extensions = context.Extensions; + command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; + command.IntermediateFolder = context.IntermediateFolder; + command.Intermediate = context.IntermediateRepresentation; + command.SupportDelayedResolution = false; + command.Execute(); + } + } +#endif + + var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); + + context.IntermediateRepresentation.UpdateLevel(IntermediateLevels.Resolved); + + return new ResolveResult + { + ExpectedEmbeddedFiles = expectedEmbeddedFiles, + DelayedFields = delayedFields, + IntermediateRepresentation = context.IntermediateRepresentation + }; + } + + /// + /// Localize dialogs and controls. + /// + private void LocalizeUI(IVariableResolver variableResolver, Intermediate intermediate) + { + foreach (var section in intermediate.Sections) + { + foreach (var symbol in section.Symbols.OfType()) + { + if (variableResolver.TryGetLocalizedControl(symbol.Id.Id, null, out var localizedControl)) + { + if (CompilerConstants.IntegerNotSet != localizedControl.X) + { + symbol.HCentering = localizedControl.X; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Y) + { + symbol.VCentering = localizedControl.Y; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Width) + { + symbol.Width = localizedControl.Width; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Height) + { + symbol.Height = localizedControl.Height; + } + + symbol.RightAligned |= localizedControl.RightAligned; + symbol.RightToLeft |= localizedControl.RightToLeft; + symbol.LeftScroll |= localizedControl.LeftScroll; + + if (!String.IsNullOrEmpty(localizedControl.Text)) + { + symbol.Title = localizedControl.Text; + } + } + } + + foreach (var symbol in section.Symbols.OfType()) + { + if (variableResolver.TryGetLocalizedControl(symbol.DialogRef, symbol.Control, out var localizedControl)) + { + if (CompilerConstants.IntegerNotSet != localizedControl.X) + { + symbol.X = localizedControl.X; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Y) + { + symbol.Y = localizedControl.Y; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Width) + { + symbol.Width = localizedControl.Width; + } + + if (CompilerConstants.IntegerNotSet != localizedControl.Height) + { + symbol.Height = localizedControl.Height; + } + + symbol.RightAligned |= localizedControl.RightAligned; + symbol.RightToLeft |= localizedControl.RightToLeft; + symbol.LeftScroll |= localizedControl.LeftScroll; + + if (!String.IsNullOrEmpty(localizedControl.Text)) + { + symbol.Text = localizedControl.Text; + } + } + } + } + } + + private IVariableResolver CreateVariableResolver(IResolveContext context, IEnumerable filteredLocalizations) + { + var variableResolver = this.ServiceProvider.GetService(); + + foreach (var localization in filteredLocalizations) + { + variableResolver.AddLocalization(localization); + } + + // Gather all the wix variables. + var wixVariableSymbols = context.IntermediateRepresentation.Sections.SelectMany(s => s.Symbols).OfType(); + foreach (var symbol in wixVariableSymbols) + { + variableResolver.AddVariable(symbol.SourceLineNumbers, symbol.Id.Id, symbol.Value, symbol.Overridable); + } + + return variableResolver; + } + + private bool TryGetCultureInfo(string culture, out CultureInfo cultureInfo) + { + cultureInfo = null; + + if (!String.IsNullOrEmpty(culture)) + { + try + { + cultureInfo = new CultureInfo(culture, useUserOverride: false); + } + catch + { + this.Messaging.Write(""); + } + } + + return cultureInfo != null; + } + + private static IEnumerable FilterLocalizations(IResolveContext context) + { + var result = new List(); + var filter = CalculateCultureFilter(context); + + var localizations = context.Localizations.Concat(context.IntermediateRepresentation.Localizations).ToList(); + + AddFilteredLocalizations(result, filter, localizations); + + // Filter localizations provided by extensions with data. + var creator = context.ServiceProvider.GetService(); + + foreach (var data in context.ExtensionData) + { + var library = data.GetLibrary(creator); + + if (library?.Localizations != null && library.Localizations.Any()) + { + var extensionFilter = (!filter.Any() && data.DefaultCulture != null) ? new[] { data.DefaultCulture } : filter; + + AddFilteredLocalizations(result, extensionFilter, library.Localizations); + } + } + + return result; + } + + private static IEnumerable CalculateCultureFilter(IResolveContext context) + { + var filter = context.FilterCultures ?? Array.Empty(); + + // If no filter was specified, look for a language neutral localization file specified + // from the command-line (not embedded in the intermediate). If found, filter on language + // neutral. + if (!filter.Any() && context.Localizations.Any(l => String.IsNullOrEmpty(l.Culture))) + { + filter = new[] { String.Empty }; + } + + return filter; + } + + private static void AddFilteredLocalizations(List result, IEnumerable filter, IEnumerable localizations) + { + // If there is no filter, return all localizations. + if (!filter.Any()) + { + result.AddRange(localizations); + } + else // filter localizations in order specified by the filter + { + foreach (var culture in filter) + { + result.AddRange(localizations.Where(l => culture.Equals(l.Culture, StringComparison.OrdinalIgnoreCase) || String.IsNullOrEmpty(l.Culture))); + } + } + } + } +} diff --git a/src/wix/WixToolset.Core/SourceFile.cs b/src/wix/WixToolset.Core/SourceFile.cs new file mode 100644 index 00000000..d7ea7a50 --- /dev/null +++ b/src/wix/WixToolset.Core/SourceFile.cs @@ -0,0 +1,17 @@ +// 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 +{ + internal class SourceFile + { + public SourceFile(string sourcePath, string outputPath) + { + this.SourcePath = sourcePath; + this.OutputPath = outputPath; + } + + public string OutputPath { get; } + + public string SourcePath { get; } + } +} diff --git a/src/wix/WixToolset.Core/UnbindContext.cs b/src/wix/WixToolset.Core/UnbindContext.cs new file mode 100644 index 00000000..c3817a08 --- /dev/null +++ b/src/wix/WixToolset.Core/UnbindContext.cs @@ -0,0 +1,29 @@ +// 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 +{ + using System; + using WixToolset.Extensibility.Data; + + internal class UnbindContext : IUnbindContext + { + internal UnbindContext(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + public IServiceProvider ServiceProvider { get; } + + public string ExportBasePath { get; set; } + + public string InputFilePath { get; set; } + + public string IntermediateFolder { get; set; } + + public bool IsAdminImage { get; set; } + + public bool SuppressExtractCabinets { get; set; } + + public bool SuppressDemodularization { get; set; } + } +} diff --git a/src/wix/WixToolset.Core/Unbinder.cs b/src/wix/WixToolset.Core/Unbinder.cs new file mode 100644 index 00000000..3ef77083 --- /dev/null +++ b/src/wix/WixToolset.Core/Unbinder.cs @@ -0,0 +1,99 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.IO; + using WixToolset.Data; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + /// + /// Unbinder core of the WiX toolset. + /// + internal sealed class Unbinder : IUnbinder + { + public Unbinder(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + + var extensionManager = this.ServiceProvider.GetService(); + this.BackendFactories = extensionManager.GetServices(); + } + + public IServiceProvider ServiceProvider { get; } + + public IEnumerable BackendFactories { get; } + + /// + /// Gets or sets whether the input msi is an admin image. + /// + /// Set to true if the input msi is part of an admin image. + public bool IsAdminImage { get; set; } + + /// + /// Gets or sets the option to suppress demodularizing values. + /// + /// The option to suppress demodularizing values. + public bool SuppressDemodularization { get; set; } + + /// + /// Gets or sets the option to suppress extracting cabinets. + /// + /// The option to suppress extracting cabinets. + public bool SuppressExtractCabinets { get; set; } + + /// + /// Gets or sets the temporary path for the Binder. If left null, the binder + /// will use %TEMP% environment variable. + /// + /// Path to temp files. + public string TempFilesLocation => Path.GetTempPath(); + + /// + /// Unbind a Windows Installer file. + /// + /// The Windows Installer file. + /// The type of output to create. + /// The path where files should be exported. + /// The output representing the database. + public Intermediate Unbind(string file, OutputType outputType, string exportBasePath) + { + if (!File.Exists(file)) + { + if (OutputType.Transform == outputType) + { + throw new WixException(ErrorMessages.FileNotFound(null, file, "Transform")); + } + else + { + throw new WixException(ErrorMessages.FileNotFound(null, file, "Database")); + } + } + + // if we don't have the temporary files object yet, get one + Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there + + var context = new UnbindContext(this.ServiceProvider); + context.InputFilePath = file; + context.ExportBasePath = exportBasePath; + context.IntermediateFolder = this.TempFilesLocation; + context.IsAdminImage = this.IsAdminImage; + context.SuppressDemodularization = this.SuppressDemodularization; + context.SuppressExtractCabinets = this.SuppressExtractCabinets; + + foreach (var factory in this.BackendFactories) + { + if (factory.TryCreateBackend(outputType.ToString(), file, out var backend)) + { + return backend.Unbind(context); + } + } + + // TODO: Display message that could not find a unbinder for output type? + + return null; + } + } +} diff --git a/src/wix/WixToolset.Core/VariableResolution.cs b/src/wix/WixToolset.Core/VariableResolution.cs new file mode 100644 index 00000000..3b34e294 --- /dev/null +++ b/src/wix/WixToolset.Core/VariableResolution.cs @@ -0,0 +1,29 @@ +// 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 +{ + using WixToolset.Extensibility.Services; + + internal class VariableResolution : IVariableResolution + { + /// + /// Indicates whether the variable should be delay resolved. + /// + public bool DelayedResolve { get; set; } + + /// + /// Indicates whether the value is the default value of the variable. + /// + public bool IsDefault { get; set; } + + /// + /// Indicates whether the value changed. + /// + public bool UpdatedValue { get; set; } + + /// + /// Resolved value. + /// + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/src/wix/WixToolset.Core/VariableResolver.cs b/src/wix/WixToolset.Core/VariableResolver.cs new file mode 100644 index 00000000..437cabb7 --- /dev/null +++ b/src/wix/WixToolset.Core/VariableResolver.cs @@ -0,0 +1,197 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using System.Text; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility.Services; + + /// + /// WiX variable resolver. + /// + internal class VariableResolver : IVariableResolver + { + private readonly Dictionary locVariables; + private readonly Dictionary wixVariables; + private readonly Dictionary localizedControls; + + /// + /// Instantiate a new VariableResolver. + /// + internal VariableResolver(IServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + this.Messaging = serviceProvider.GetService(); + + this.locVariables = new Dictionary(); + this.wixVariables = new Dictionary(); + this.localizedControls = new Dictionary(); + } + + private IServiceProvider ServiceProvider { get; } + + private IMessaging Messaging { get; } + + public int VariableCount => this.wixVariables.Count; + + public void AddLocalization(Localization localization) + { + foreach (var variable in localization.Variables) + { + if (!TryAddWixVariable(this.locVariables, variable)) + { + this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(variable.SourceLineNumbers, variable.Id)); + } + } + + foreach (KeyValuePair localizedControl in localization.LocalizedControls) + { + if (!this.localizedControls.ContainsKey(localizedControl.Key)) + { + this.localizedControls.Add(localizedControl.Key, localizedControl.Value); + } + } + } + + public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable) + { + var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber }; + + if (!TryAddWixVariable(this.wixVariables, bindVariable)) + { + this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name)); + } + } + + public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value) + { + return this.ResolveVariables(sourceLineNumbers, value, errorOnUnknown: true); + } + + public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl) + { + var key = LocalizedControl.GetKey(dialog, control); + return this.localizedControls.TryGetValue(key, out localizedControl); + } + + public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown) + { + var start = 0; + var defaulted = true; + var delayed = false; + var updated = false; + + while (Common.TryParseWixVariable(value, start, out var parsed)) + { + var variableNamespace = parsed.Namespace; + var variableId = parsed.Name; + var variableDefaultValue = parsed.DefaultValue; + + // check for an escape sequence of !! indicating the match is not a variable expression + if (0 < parsed.Index && '!' == value[parsed.Index - 1]) + { + var sb = new StringBuilder(value); + sb.Remove(parsed.Index - 1, 1); + value = sb.ToString(); + + updated = true; + start = parsed.Index + parsed.Length - 1; + + continue; + } + + string resolvedValue = null; + + if ("loc" == variableNamespace) + { + // localization variables do not support inline default values + if (variableDefaultValue != null) + { + this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue)); + continue; + } + + if (this.locVariables.TryGetValue(variableId, out var bindVariable)) + { + resolvedValue = bindVariable.Value; + } + } + else if ("wix" == variableNamespace) + { + if (this.wixVariables.TryGetValue(variableId, out var bindVariable)) + { + resolvedValue = bindVariable.Value ?? String.Empty; + defaulted = false; + } + else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified + { + resolvedValue = variableDefaultValue; + } + } + + if ("bind" == variableNamespace) + { + // Can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort. + delayed = true; + start = parsed.Index + parsed.Length - 1; + } + else + { + // insert the resolved value if it was found or display an error + if (null != resolvedValue) + { + if (parsed.Index == 0 && parsed.Length == value.Length) + { + value = resolvedValue; + } + else + { + var sb = new StringBuilder(value); + sb.Remove(parsed.Index, parsed.Length); + sb.Insert(parsed.Index, resolvedValue); + value = sb.ToString(); + } + + updated = true; + start = parsed.Index; + } + else + { + if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable + { + this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId)); + } + else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable + { + this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId)); + } + + start = parsed.Index + parsed.Length; + } + } + } + + return new VariableResolution + { + DelayedResolve = delayed, + IsDefault = defaulted, + UpdatedValue = updated, + Value = value, + }; + } + + private static bool TryAddWixVariable(IDictionary variables, BindVariable variable) + { + if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable)) + { + variables[variable.Id] = variable; + return true; + } + + return variable.Overridable; + } + } +} diff --git a/src/wix/WixToolset.Core/WixToolset.Core.csproj b/src/wix/WixToolset.Core/WixToolset.Core.csproj new file mode 100644 index 00000000..7242d500 --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolset.Core.csproj @@ -0,0 +1,47 @@ + + + + + + netstandard2.0 + $(TargetFrameworks);net461;net472 + Core + WiX Toolset Core + embedded + true + true + true + + + + + <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject b/src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject new file mode 100644 index 00000000..c6001ebe --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + ..\..\version.json + + + \ No newline at end of file diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs new file mode 100644 index 00000000..5d700ba0 --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs @@ -0,0 +1,117 @@ +// 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 +{ + using System; + using System.Collections.Generic; + using WixToolset.Core.CommandLine; + using WixToolset.Core.ExtensibilityServices; + using WixToolset.Data; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class WixToolsetServiceProvider : IWixToolsetCoreServiceProvider + { + public WixToolsetServiceProvider() + { + this.CreationFunctions = new Dictionary, object>>(); + this.Singletons = new Dictionary(); + + // Singletons. + this.AddService((provider, singletons) => AddSingleton(singletons, new ExtensionManager(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new Messaging())); + this.AddService((provider, singletons) => AddSingleton(singletons, new SymbolDefinitionCreator(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new ParseHelper(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new PreprocessHelper(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new BackendHelper(provider))); + this.AddService((provider, singletons) => AddSingleton(singletons, new PathResolver())); + this.AddService((provider, singletons) => AddSingleton(singletons, new WixBranding())); + + // Transients. + this.AddService((provider, singletons) => new CommandLineArguments(provider)); + this.AddService((provider, singletons) => new CommandLineContext(provider)); + this.AddService((provider, singletons) => new CommandLine.CommandLine(provider)); + this.AddService((provider, singletons) => new PreprocessContext(provider)); + this.AddService((provider, singletons) => new CompileContext(provider)); + this.AddService((provider, singletons) => new LibraryContext(provider)); + this.AddService((provider, singletons) => new LinkContext(provider)); + this.AddService((provider, singletons) => new ResolveContext(provider)); + this.AddService((provider, singletons) => new BindContext(provider)); + this.AddService((provider, singletons) => new DecompileContext(provider)); + this.AddService((provider, singletons) => new LayoutContext(provider)); + this.AddService((provider, singletons) => new InscribeContext(provider)); + this.AddService((provider, singletons) => new UnbindContext(provider)); + + this.AddService((provider, singletons) => new BindFileWithPath()); + this.AddService((provider, singletons) => new BindPath()); + this.AddService((provider, singletons) => new BindResult()); + this.AddService((provider, singletons) => new ComponentKeyPath()); + this.AddService((provider, singletons) => new DecompileResult()); + this.AddService((provider, singletons) => new IncludedFile()); + this.AddService((provider, singletons) => new PreprocessResult()); + this.AddService((provider, singletons) => new ResolvedDirectory()); + this.AddService((provider, singletons) => new ResolveFileResult()); + this.AddService((provider, singletons) => new ResolveResult()); + this.AddService((provider, singletons) => new ResolvedCabinet()); + this.AddService((provider, singletons) => new VariableResolution()); + + this.AddService((provider, singletons) => new Binder(provider)); + this.AddService((provider, singletons) => new Compiler(provider)); + this.AddService((provider, singletons) => new Decompiler(provider)); + this.AddService((provider, singletons) => new LayoutCreator(provider)); + this.AddService((provider, singletons) => new Preprocessor(provider)); + this.AddService((provider, singletons) => new Librarian(provider)); + this.AddService((provider, singletons) => new Linker(provider)); + this.AddService((provider, singletons) => new Resolver(provider)); + this.AddService((provider, singletons) => new Unbinder(provider)); + + this.AddService((provider, singletons) => new LocalizationParser(provider)); + this.AddService((provider, singletons) => new VariableResolver(provider)); + } + + private Dictionary, object>> CreationFunctions { get; } + + private Dictionary Singletons { get; } + + public object GetService(Type serviceType) + { + if (serviceType == null) + { + throw new ArgumentNullException(nameof(serviceType)); + } + + if (!this.Singletons.TryGetValue(serviceType, out var service)) + { + if (this.CreationFunctions.TryGetValue(serviceType, out var creationFunction)) + { + service = creationFunction(this, this.Singletons); + +#if DEBUG + if (!serviceType.IsAssignableFrom(service?.GetType())) + { + throw new InvalidOperationException($"Creation function for service type: {serviceType.Name} created incompatible service with type: {service?.GetType()}"); + } +#endif + } + } + + return service; + } + + public void AddService(Type serviceType, Func, object> creationFunction) + { + this.CreationFunctions[serviceType] = creationFunction; + } + + public void AddService(Func, T> creationFunction) where T : class + { + this.AddService(typeof(T), creationFunction); + } + + private static T AddSingleton(Dictionary singletons, T service) where T : class + { + singletons.Add(typeof(T), service); + return service; + } + } +} diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs b/src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs new file mode 100644 index 00000000..8e07070b --- /dev/null +++ b/src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs @@ -0,0 +1,21 @@ +// 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 +{ + using WixToolset.Extensibility.Services; + + /// + /// Class for creating . + /// + public static class WixToolsetServiceProviderFactory + { + /// + /// Creates a new . + /// + /// The created + public static IWixToolsetCoreServiceProvider CreateServiceProvider() + { + return new WixToolsetServiceProvider(); + } + } +} diff --git a/src/wix/appveyor.cmd b/src/wix/appveyor.cmd new file mode 100644 index 00000000..02db695b --- /dev/null +++ b/src/wix/appveyor.cmd @@ -0,0 +1,20 @@ +@setlocal +@pushd %~dp0 +@set _P=%~dp0build\Release\publish +@set _C=Release +@if /i "%1"=="debug" set _C=Debug + +:: Restore +msbuild -p:Configuration=%_C% -t:Restore || exit /b + +:: Build +msbuild -p:Configuration=%_C% || exit /b + +:: Test +dotnet test -c %_C% --no-build || exit /b + +:: Pack +msbuild -p:Configuration=%_C% -p:NoBuild=true -t:Pack || exit /b + +@popd +@endlocal diff --git a/src/wix/appveyor.yml b/src/wix/appveyor.yml new file mode 100644 index 00000000..364569cf --- /dev/null +++ b/src/wix/appveyor.yml @@ -0,0 +1,44 @@ +# 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. +# +# Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml +# then update all of the repos. + +branches: + only: + - master + - develop + +image: Visual Studio 2019 + +version: 0.0.0.{build} +configuration: Release + +environment: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + NUGET_XMLDOC_MODE: skip + +build_script: + - appveyor.cmd + +test: off + +pull_requests: + do_not_increment_build_number: true + +nuget: + disable_publish_on_pr: true + +skip_branch_with_pr: true +skip_tags: true + +artifacts: +- path: build\Release\**\*.nupkg + name: nuget +- path: build\Release\**\*.snupkg + name: snupkg + +notifications: +- provider: Slack + incoming_webhook: + secure: p5xuu+4x2JHfwGDMDe5KcG1k7gZxqYc4jWVwvyNZv5cvkubPD2waJs5yXMAXZNN7Z63/3PWHb7q4KoY/99AjauYa1nZ4c5qYqRPFRBKTHfA= diff --git a/src/wix/nuget.config b/src/wix/nuget.config new file mode 100644 index 00000000..022f9240 --- /dev/null +++ b/src/wix/nuget.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj b/src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj new file mode 100644 index 00000000..88210bd4 --- /dev/null +++ b/src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj @@ -0,0 +1,32 @@ + + + + + + netcoreapp3.1 + false + Exe + + + + + + + + + $(BaseOutputPath)TestData\$(Configuration)\example.wixlib + + + + + + + + + + diff --git a/src/wix/test/CompileCoreTestExtensionWixlib/Program.cs b/src/wix/test/CompileCoreTestExtensionWixlib/Program.cs new file mode 100644 index 00000000..323b5e5e --- /dev/null +++ b/src/wix/test/CompileCoreTestExtensionWixlib/Program.cs @@ -0,0 +1,37 @@ +// 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. + +using System.Collections.Generic; +using WixToolset.Core.TestPackage; + +namespace CompileCoreTestExtensionWixlib +{ + // We want to be able to test Core with extensions, but there's no easy way to build an extension without Tools. + // So we have this helper exe. + public class Program + { + public static void Main(string[] args) + { + var intermediateFolder = args[0]; + var wixlibPath = args[1]; + + var buildArgs = new List(); + buildArgs.Add("build"); + buildArgs.Add("-bindfiles"); + buildArgs.Add("-bindpath"); + buildArgs.Add("Data"); + buildArgs.Add("-intermediateFolder"); + buildArgs.Add(intermediateFolder); + buildArgs.Add("-o"); + buildArgs.Add(wixlibPath); + + foreach (var path in args[2].Split(';')) + { + buildArgs.Add(path); + } + + var result = WixRunner.Execute(buildArgs.ToArray()); + + result.AssertSuccess(); + } + } +} diff --git a/src/wix/test/Example.Extension/Data/example.txt b/src/wix/test/Example.Extension/Data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/Example.Extension/Data/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/wix/test/Example.Extension/Data/example.wxs b/src/wix/test/Example.Extension/Data/example.wxs new file mode 100644 index 00000000..af5d5086 --- /dev/null +++ b/src/wix/test/Example.Extension/Data/example.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/Example.Extension/Example.Extension.csproj b/src/wix/test/Example.Extension/Example.Extension.csproj new file mode 100644 index 00000000..9be10d35 --- /dev/null +++ b/src/wix/test/Example.Extension/Example.Extension.csproj @@ -0,0 +1,24 @@ + + + + + + netcoreapp3.1 + false + embedded + + + + + + + + + + + + + + + + diff --git a/src/wix/test/Example.Extension/ExampleCompilerExtension.cs b/src/wix/test/Example.Extension/ExampleCompilerExtension.cs new file mode 100644 index 00000000..5b8d4b3f --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleCompilerExtension.cs @@ -0,0 +1,195 @@ +// 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 Example.Extension +{ + using System; + using System.Collections.Generic; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class ExampleCompilerExtension : BaseCompilerExtension + { + public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs"; + public string BundleExtensionId => "ExampleBundleExtension"; + + public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) + { + var processed = false; + + switch (parentElement.Name.LocalName) + { + case "Bundle": + case "Fragment": + switch (element.Name.LocalName) + { + case "ExampleEnsureTable": + this.ParseExampleEnsureTableElement(intermediate, section, element); + processed = true; + break; + case "ExampleSearch": + this.ParseExampleSearchElement(intermediate, section, element); + processed = true; + break; + case "ExampleSearchRef": + this.ParseExampleSearchRefElement(intermediate, section, element); + processed = true; + break; + } + break; + case "Component": + switch (element.Name.LocalName) + { + case "Example": + this.ParseExampleElement(intermediate, section, element); + processed = true; + break; + } + break; + } + + if (!processed) + { + base.ParseElement(intermediate, section, parentElement, element, context); + } + } + + private void ParseExampleElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + string value = null; + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + + case "Value": + value = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseAttribute(intermediate, section, element, attrib, null); + } + } + + if (null == id) + { + //this.Messaging(WixErrors.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + var symbol = this.ParseHelper.CreateSymbol(section, sourceLineNumbers, "Example", id); + symbol.Set(0, value); + } + } + + private void ParseExampleEnsureTableElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + this.ParseHelper.EnsureTable(section, sourceLineNumbers, ExampleTableDefinitions.NotInAll); + } + + private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + string searchFor = null; + string variable = null; + string condition = null; + string after = null; + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Variable": + variable = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + after = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SearchFor": + searchFor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseAttribute(intermediate, section, element, attrib, null); + } + } + + if (null == id) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + this.ParseHelper.CreateWixSearchSymbol(section, sourceLineNumbers, element.Name.LocalName, id, variable, condition, after, this.BundleExtensionId); + } + + if (!this.Messaging.EncounteredError) + { + var symbol = section.AddSymbol(new ExampleSearchSymbol(sourceLineNumbers, id) + { + SearchFor = searchFor, + }); + } + } + + private void ParseExampleSearchRefElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + var refId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, ExampleSymbolDefinitions.ExampleSearch, refId); + break; + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleExtensionData.cs b/src/wix/test/Example.Extension/ExampleExtensionData.cs new file mode 100644 index 00000000..91d60eb9 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleExtensionData.cs @@ -0,0 +1,23 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + using WixToolset.Extensibility; + + internal class ExampleExtensionData : IExtensionData + { + public string DefaultCulture => null; + + public Intermediate GetLibrary(ISymbolDefinitionCreator symbolDefinitions) + { + return Intermediate.Load(typeof(ExampleExtensionData).Assembly, "Example.Extension.Example.wixlib", symbolDefinitions); + } + + public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition) + { + symbolDefinition = ExampleSymbolDefinitions.ByName(name); + return symbolDefinition != null; + } + } +} \ No newline at end of file diff --git a/src/wix/test/Example.Extension/ExampleExtensionFactory.cs b/src/wix/test/Example.Extension/ExampleExtensionFactory.cs new file mode 100644 index 00000000..e54561ee --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleExtensionFactory.cs @@ -0,0 +1,54 @@ +// 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 Example.Extension +{ + using System; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + public class ExampleExtensionFactory : IExtensionFactory + { + private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension; + + public ExampleExtensionFactory(IWixToolsetCoreServiceProvider serviceProvider) + { + this.ServiceProvider = serviceProvider; + } + + /// + /// This exists just to show it is possible to get a service provider to the extension factory. + /// + private IWixToolsetCoreServiceProvider ServiceProvider { get; } + + public bool TryCreateExtension(Type extensionType, out object extension) + { + if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension)) + { + if (this.preprocessorExtension == null) + { + this.preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine(); + } + + extension = this.preprocessorExtension; + } + else if (extensionType == typeof(ICompilerExtension)) + { + extension = new ExampleCompilerExtension(); + } + else if (extensionType == typeof(IExtensionData)) + { + extension = new ExampleExtensionData(); + } + else if (extensionType == typeof(IWindowsInstallerBackendBinderExtension)) + { + extension = new ExampleWindowsInstallerBackendExtension(); + } + else + { + extension = null; + } + + return extension != null; + } + } +} diff --git a/src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs b/src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs new file mode 100644 index 00000000..7244798a --- /dev/null +++ b/src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs @@ -0,0 +1,57 @@ +// 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 Example.Extension +{ + using System; + using System.Collections.Generic; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine + { + private string exampleValueFromCommandLine; + + public IReadOnlyCollection CommandLineSwitches => throw new NotImplementedException(); + + public ExamplePreprocessorExtensionAndCommandLine() + { + this.Prefixes = new[] { "ex" }; + } + + public void PreParse(ICommandLineContext context) + { + } + + public bool TryParseArgument(ICommandLineParser parser, string argument) + { + if (parser.IsSwitch(argument) && argument.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase)) + { + this.exampleValueFromCommandLine = parser.GetNextArgumentOrError(argument); + return true; + } + + return false; + } + + public bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command) + { + command = null; + return false; + } + + public void PostParse() + { + } + + public override string GetVariableValue(string prefix, string name) + { + if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase)) + { + return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine; + } + + return null; + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleRow.cs b/src/wix/test/Example.Extension/ExampleRow.cs new file mode 100644 index 00000000..fc20c6c9 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleRow.cs @@ -0,0 +1,32 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + + public class ExampleRow : Row + { + public ExampleRow(SourceLineNumber sourceLineNumbers, Table table) + : base(sourceLineNumbers, table) + { + } + + public ExampleRow(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition) + : base(sourceLineNumbers, tableDefinition) + { + } + + public string Example + { + get { return (string)this.Fields[0].Data; } + set { this.Fields[0].Data = value; } + } + + public string Value + { + get { return (string)this.Fields[1].Data; } + set { this.Fields[1].Data = value; } + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleSearchSymbol.cs b/src/wix/test/Example.Extension/ExampleSearchSymbol.cs new file mode 100644 index 00000000..40a39292 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleSearchSymbol.cs @@ -0,0 +1,30 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + + public enum ExampleSearchSymbolFields + { + SearchFor, + } + + public class ExampleSearchSymbol : IntermediateSymbol + { + public ExampleSearchSymbol() : base(ExampleSymbolDefinitions.ExampleSearch, null, null) + { + } + + public ExampleSearchSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.ExampleSearch, sourceLineNumber, id) + { + } + + public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index]; + + public string SearchFor + { + get => this.Fields[(int)ExampleSearchSymbolFields.SearchFor]?.AsString(); + set => this.Set((int)ExampleSearchSymbolFields.SearchFor, value); + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleSymbol.cs b/src/wix/test/Example.Extension/ExampleSymbol.cs new file mode 100644 index 00000000..314087e9 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleSymbol.cs @@ -0,0 +1,30 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + + public enum ExampleSymbolFields + { + Value, + } + + public class ExampleSymbol : IntermediateSymbol + { + public ExampleSymbol() : base(ExampleSymbolDefinitions.Example, null, null) + { + } + + public ExampleSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.Example, sourceLineNumber, id) + { + } + + public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index]; + + public string Value + { + get => this.Fields[(int)ExampleSymbolFields.Value]?.AsString(); + set => this.Set((int)ExampleSymbolFields.Value, value); + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs b/src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs new file mode 100644 index 00000000..f13d716d --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs @@ -0,0 +1,67 @@ +// 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 Example.Extension +{ + using System; + using WixToolset.Data; + using WixToolset.Data.Burn; + + public enum ExampleSymbolDefinitionType + { + Example, + ExampleSearch, + } + + public static class ExampleSymbolDefinitions + { + public static readonly IntermediateSymbolDefinition Example = new IntermediateSymbolDefinition( + ExampleSymbolDefinitionType.Example.ToString(), + new[] + { + new IntermediateFieldDefinition(nameof(ExampleSymbolFields.Value), IntermediateFieldType.String), + }, + typeof(ExampleSymbol)); + + public static readonly IntermediateSymbolDefinition ExampleSearch = new IntermediateSymbolDefinition( + ExampleSymbolDefinitionType.ExampleSearch.ToString(), + new[] + { + new IntermediateFieldDefinition(nameof(ExampleSearchSymbolFields.SearchFor), IntermediateFieldType.String), + }, + typeof(ExampleSearchSymbol)); + + static ExampleSymbolDefinitions() + { + ExampleSearch.AddTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag); + } + + public static bool TryGetSymbolType(string name, out ExampleSymbolDefinitionType type) + { + return Enum.TryParse(name, out type); + } + + public static IntermediateSymbolDefinition ByName(string name) + { + if (!TryGetSymbolType(name, out var type)) + { + return null; + } + return ByType(type); + } + + public static IntermediateSymbolDefinition ByType(ExampleSymbolDefinitionType type) + { + switch (type) + { + case ExampleSymbolDefinitionType.Example: + return ExampleSymbolDefinitions.Example; + + case ExampleSymbolDefinitionType.ExampleSearch: + return ExampleSymbolDefinitions.ExampleSearch; + + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + } + } +} diff --git a/src/wix/test/Example.Extension/ExampleTableDefinitions.cs b/src/wix/test/Example.Extension/ExampleTableDefinitions.cs new file mode 100644 index 00000000..a2b81698 --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleTableDefinitions.cs @@ -0,0 +1,34 @@ +// 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 Example.Extension +{ + using WixToolset.Data.WindowsInstaller; + + public static class ExampleTableDefinitions + { + public static readonly TableDefinition ExampleTable = new TableDefinition( + "Wix4Example", + ExampleSymbolDefinitions.Example, + new[] + { + new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), + new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), + }, + strongRowType: typeof(ExampleRow), + symbolIdIsPrimaryKey: true + ); + + public static readonly TableDefinition NotInAll = new TableDefinition( + "TableDefinitionNotExposedByExtension", + null, + new[] + { + new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier), + new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted), + }, + symbolIdIsPrimaryKey: true + ); + + public static readonly TableDefinition[] All = new[] { ExampleTable }; + } +} diff --git a/src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs b/src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs new file mode 100644 index 00000000..afccc56f --- /dev/null +++ b/src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs @@ -0,0 +1,33 @@ +// 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 Example.Extension +{ + using System.Collections.Generic; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility; + + internal class ExampleWindowsInstallerBackendExtension : BaseWindowsInstallerBackendBinderExtension + { + public override IReadOnlyCollection TableDefinitions => ExampleTableDefinitions.All; + + public override bool TryProcessSymbol(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData output, TableDefinitionCollection tableDefinitions) + { + if (ExampleSymbolDefinitions.TryGetSymbolType(symbol.Definition.Name, out var symbolType)) + { + switch (symbolType) + { + case ExampleSymbolDefinitionType.Example: + { + var row = (ExampleRow)this.BackendHelper.CreateRow(section, symbol, output, ExampleTableDefinitions.ExampleTable); + row.Example = symbol.Id.Id; + row.Value = symbol[0].AsString(); + } + return true; + } + } + + return base.TryProcessSymbol(section, symbol, output, tableDefinitions); + } + } +} diff --git a/src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs b/src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs new file mode 100644 index 00000000..a83da7f6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs @@ -0,0 +1,44 @@ +// 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 WixToolsetTest.Core.Burn +{ + using System; + using WixToolset.Core.Burn.Bundles; + using Xunit; + + public class BurnReaderFixture + { + [Fact] + public void CanReadUInt16Max() + { + var bytes = new byte[] { 0xFF, 0xFF }; + var offset = 0u; + + var result = BurnCommon.ReadUInt16(bytes, offset); + + Assert.Equal(UInt16.MaxValue, result); + } + + [Fact] + public void CanReadUInt32Max() + { + var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; + var offset = 0u; + + var result = BurnCommon.ReadUInt32(bytes, offset); + + Assert.Equal(UInt32.MaxValue, result); + } + + [Fact] + public void CanReadUInt64Max() + { + var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + var offset = 0u; + + var result = BurnCommon.ReadUInt64(bytes, offset); + + Assert.Equal(UInt64.MaxValue, result); + } + } +} diff --git a/src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj b/src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj new file mode 100644 index 00000000..175ee1a9 --- /dev/null +++ b/src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj @@ -0,0 +1,28 @@ + + + + + + netcoreapp3.1 + false + embedded + + + + NU1701 + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs new file mode 100644 index 00000000..47b47ef5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs @@ -0,0 +1,64 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ApprovedExeFixture + { + [Fact] + public void CanBuildWithApprovedExe() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleWithApprovedExe", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.NotEqual(0, result.ExitCode); + Assert.False(File.Exists(exePath)); + } + } + + [Fact] + public void CanBuildWithApprovedExe64() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleWithApprovedExe", "Bundle64.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.NotEqual(0, result.ExitCode); + Assert.False(File.Exists(exePath)); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs new file mode 100644 index 00000000..62ffe1eb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs @@ -0,0 +1,148 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class BadInputFixture + { + [Fact] + public void SwitchIsNotConsideredAnArgument() + { + var result = WixRunner.Execute(new[] + { + "build", + "-bindpath", "-thisisaswitchnotanarg", + }); + + Assert.Single(result.Messages, m => m.Id == (int)ErrorMessages.Ids.ExpectedArgument); + // TODO: when CantBuildSingleExeBundleWithInvalidArgument is fixed, uncomment: + //Assert.Equal((int)ErrorMessages.Ids.ExpectedArgument, result.ExitCode); + } + + [Fact] + public void HandleInvalidIds() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "InvalidIds.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(330, result.ExitCode); + } + } + + [Fact] + public void CantBuildSingleExeBundleWithInvalidArgument() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + "-nonexistentswitch", "param", + }); + + Assert.NotEqual(0, result.ExitCode); + Assert.False(File.Exists(exePath)); + } + } + + [Fact] + public void RegistryKeyWithoutAttributesDoesntCrash() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "RegistryKey.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.InRange(result.ExitCode, 2, Int32.MaxValue); + } + } + + [Fact] + public void BundleVariableWithBadTypeIsRejected() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleVariable.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(21, result.ExitCode); + } + } + + [Fact] + public void BundleVariableWithHiddenPersistedIsRejected() + { + var folder = TestData.Get(@"TestData\BadInput"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "HiddenPersistedBundleVariable.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(193, result.ExitCode); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs new file mode 100644 index 00000000..39e6b4aa --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs @@ -0,0 +1,96 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class BindVariablesFixture + { + [Fact] + public void CanBuildBundleWithPackageBindVariables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleBindVariables", "CacheIdFromPackageDescription.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + [Fact] + public void CanBuildWithDefaultValue() + { + var folder = TestData.Get(@"TestData", "BindVariables"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DefaultedVariable.wxs"), + "-bf", + "-intermediateFolder", intermediateFolder, + "-bindpath", folder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + } + } + + [Fact] + public void CannotBuildWixlibWithBinariesFromMissingNamedBindPaths() + { + var folder = TestData.Get(@"TestData", "WixlibWithBinaries"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-bf", + "-bindpath", Path.Combine(folder, "data"), + // Use names that aren't excluded in default .gitignores. + "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", + "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", + "-bindpath", $"{Path.Combine(folder, "data", "alpha")}", + "-bindpath", $"{Path.Combine(folder, "data", "powerpc")}", + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal(103, result.ExitCode); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs new file mode 100644 index 00000000..9bdc9496 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs @@ -0,0 +1,46 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class BootstrapperApplicationFixture + { + [Fact] + public void CanSetBootstrapperApplicationDllDpiAwareness() + { + var folder = TestData.Get(@"TestData\BootstrapperApplication"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DpiAwareness.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var baDllSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(baDllSymbol); + + Assert.Equal(WixBootstrapperApplicationDpiAwarenessType.GdiScaled, baDllSymbol.DpiAwareness); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs new file mode 100644 index 00000000..b33b8891 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs @@ -0,0 +1,58 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Extensibility.Services; + using Xunit; + + public class BundleExtractionFixture + { + [Fact] + public void CanExtractBundleWithDetachedContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + var baFolderPath = Path.Combine(extractFolderPath, "UX"); + var attachedContainerFolderPath = Path.Combine(extractFolderPath, "AttachedContainer"); + + // TODO: use WixRunner.Execute(string[]) to always go through the command line. + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleWithDetachedContainer", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }, serviceProvider, out var messages).Result; + + WixRunnerResult.AssertSuccess(result, messages); + Assert.Empty(messages.Where(m => m.Level == MessageLevel.Warning)); + + Assert.True(File.Exists(exePath)); + + var unbinder = serviceProvider.GetService(); + unbinder.Unbind(exePath, OutputType.Bundle, extractFolderPath); + + Assert.True(File.Exists(Path.Combine(baFolderPath, "manifest.xml"))); + Assert.False(Directory.Exists(attachedContainerFolderPath)); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs new file mode 100644 index 00000000..ab644080 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs @@ -0,0 +1,478 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.Burn; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + using WixToolset.Dtf.Resources; + using Xunit; + + public class BundleFixture + { + [Fact] + public void CanBuildMultiFileBundle() + { + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), + Path.Combine(folder, "MultiFileBundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildSimpleBundle() + { + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Bundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + Assert.Empty(result.Messages.Where(m => m.Level == MessageLevel.Warning)); + + Assert.True(File.Exists(exePath)); + Assert.True(File.Exists(pdbPath)); + + using (var wixOutput = WixOutput.Read(pdbPath)) + { + + var intermediate = Intermediate.Load(wixOutput); + var section = intermediate.Sections.Single(); + + var bundleSymbol = section.Symbols.OfType().Single(); + Assert.Equal("1.0.0.0", bundleSymbol.Version); + + var previousVersion = bundleSymbol.Fields[(int)WixBundleSymbolFields.Version].PreviousValue; + Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString()); + + var msiSymbol = section.Symbols.OfType().Single(); + Assert.Equal("test.msi", msiSymbol.Id.Id); + + var extractResult = BundleExtractor.ExtractBAContainer(null, exePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var burnManifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); + var extractedBurnManifestData = File.ReadAllText(Path.Combine(baFolderPath, "manifest.xml"), Encoding.UTF8); + Assert.Equal(extractedBurnManifestData, burnManifestData); + + var baManifestData = wixOutput.GetData(BurnConstants.BootstrapperApplicationDataWixOutputStreamName); + var extractedBaManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BootstrapperApplicationData.xml"), Encoding.UTF8); + Assert.Equal(extractedBaManifestData, baManifestData); + + var bextManifestData = wixOutput.GetData(BurnConstants.BundleExtensionDataWixOutputStreamName); + var extractedBextManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BundleExtensionData.xml"), Encoding.UTF8); + Assert.Equal(extractedBextManifestData, bextManifestData); + + var logElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Log"); + var logElement = (XmlNode)Assert.Single(logElements); + Assert.Equal("", logElement.GetTestXml()); + + var registrationElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration"); + var registrationElement = (XmlNode)Assert.Single(registrationElements); + Assert.Equal($"" + + "" + + "", registrationElement.GetTestXml()); + + var msiPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='test.msi']"); + var msiPayload = (XmlNode)Assert.Single(msiPayloads); + Assert.Equal("", + msiPayload.GetTestXml(new Dictionary>() { { "Payload", new List { "FileSize", "Hash" } } })); + } + + var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); + manifestResource.Load(exePath); + var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data); + Assert.Equal("" + + "" + + "" + + "~TestBundle" + + "" + + "" + + "" + + "true/pmPerMonitorV2, PerMonitor" + + "", actualManifestData); + } + } + + [Fact] + public void CanBuildX64Bundle() + { + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] // TODO: go back to elevating warnings as errors. + { + "build", + "-arch", "x64", + Path.Combine(folder, "Bundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + var warning = Assert.Single(result.Messages.Where(m => m.Level == MessageLevel.Warning)); + Assert.Equal((int)WarningMessages.Ids.ExperimentalBundlePlatform, warning.Id); + + Assert.True(File.Exists(exePath)); + Assert.True(File.Exists(pdbPath)); + + var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033); + manifestResource.Load(exePath); + var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data); + Assert.Equal("" + + "" + + "" + + "~TestBundle" + + "" + + "" + + "" + + "true/pmPerMonitorV2, PerMonitor" + + "", actualManifestData); + } + } + + [Fact] + public void CanBuildSimpleBundleUsingExtensionBA() + { + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildSingleExeBundle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + [Fact] + public void CanBuildSingleExeRemotePayloadBundle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SingleExeBundle", "SingleExeRemotePayload.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + Assert.True(File.Exists(pdbPath)); + + using (var wixOutput = WixOutput.Read(pdbPath)) + { + var intermediate = Intermediate.Load(wixOutput); + var section = intermediate.Sections.Single(); + + var payloadSymbol = section.Symbols.OfType().Where(x => x.Id.Id == "NetFx462Web").Single(); + Assert.Equal(Int64.MaxValue, payloadSymbol.FileSize); + } + } + } + + [Fact] + public void CantBuildWithDuplicateCacheIds() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "DuplicateCacheIds.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal(8001, result.ExitCode); + } + } + + [Fact] + public void CantBuildWithDuplicatePayloadNames() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "DuplicatePayloadNames.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + var attachedContainerWarnings = result.Messages.Where(m => m.Id == (int)BurnBackendWarnings.Ids.AttachedContainerPayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'Auto2' has a duplicate Name 'burn.exe' in the attached container. When extracting the bundle with dark.exe, the file will get overwritten.", + }, attachedContainerWarnings); + + var baContainerErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.BAContainerPayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'DuplicatePayloadNames.wxs' has a duplicate Name 'fakeba.dll' in the BA container. When extracting the container at runtime, the file will get overwritten.", + "The Payload 'uxTxMXPVMXwQrPTMIGa5WGt93w0Ns' has a duplicate Name 'BootstrapperApplicationData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", + "The Payload 'uxYRbgitOs0K878jn5L_z7LdJ21KI' has a duplicate Name 'BundleExtensionData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.", + }, baContainerErrors); + + var externalErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.ExternalPayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The external Payload 'HiddenPersistedBundleVariable.wxs' has a duplicate Name 'PayloadCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", + "The external Container 'MsiPackagesContainer' has a duplicate Name 'ContainerCollision'. When building the bundle or laying out the bundle, the file will get overwritten.", + }, externalErrors); + + var packageCacheErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.PackageCachePayloadCollision) + .Select(m => m.ToString()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'test.msi' has a duplicate Name 'test.msi' in package 'test.msi'. When caching the package, the file will get overwritten.", + }, packageCacheErrors); + + Assert.Equal(14, result.Messages.Length); + } + } + + [Fact] + public void CantBuildWithOrphanPayload() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "OrphanPayload.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.OrphanedPayload, result.ExitCode); + } + } + + [Fact] + public void CantBuildWithPackageInMultipleContainers() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "PackageInMultipleContainers.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.PackageInMultipleContainers, result.ExitCode); + } + } + + [Fact] + public void CantBuildWithUnscheduledPackage() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "UnscheduledPackage.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.UnscheduledChainPackage, result.ExitCode); + } + } + + [Fact] + public void CantBuildWithUnscheduledRollbackBoundary() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadInput", "UnscheduledRollbackBoundary.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.UnscheduledRollbackBoundary, result.ExitCode); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs new file mode 100644 index 00000000..6d769bd6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -0,0 +1,365 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class BundleManifestFixture + { + [Fact] + public void PopulatesBAManifestWithBootstrapperApplicationBundleCustomData() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTableBA"); + Assert.Equal(3, customElements.Count); + Assert.Equal("", customElements[0].GetTestXml()); + Assert.Equal("", customElements[1].GetTestXml()); + Assert.Equal("", customElements[2].GetTestXml()); + } + } + + [Fact] + public void PopulatesBAManifestWithPackageInformation() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "CustomPackageDescription", "CustomPackageDescription.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var packageElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPackageProperties"); + var ignoreAttributesByElementName = new Dictionary> + { + { "WixPackageProperties", new List { "DownloadSize", "PackageSize", "InstalledSize", "Version" } }, + }; + Assert.Equal(3, packageElements.Count); + Assert.Equal("", packageElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", packageElements[1].GetTestXml()); + Assert.Equal("", packageElements[2].GetTestXml(ignoreAttributesByElementName)); + } + } + + [Fact] + public void PopulatesBAManifestWithPayloadInformation() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var payloadElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties"); + var ignoreAttributesByElementName = new Dictionary> + { + { "WixPayloadProperties", new List { "Size" } }, + }; + Assert.Equal(4, payloadElements.Count); + Assert.Equal("", payloadElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[1].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[2].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", payloadElements[3].GetTestXml(ignoreAttributesByElementName)); + } + } + + [Fact] + public void PopulatesBEManifestWithBundleExtensionBundleCustomData() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var customElements = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='CustomTableExtension']/be:BundleCustomTableBE"); + Assert.Equal(3, customElements.Count); + Assert.Equal("", customElements[0].GetTestXml()); + Assert.Equal("", customElements[1].GetTestXml()); + Assert.Equal("", customElements[2].GetTestXml()); + } + } + + [Fact] + public void PopulatesManifestWithBundleExtension() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleExtension", "BundleExtension.wxs"), + Path.Combine(folder, "BundleExtension", "SimpleBundleExtension.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); + Assert.Equal(1, bundleExtensions.Count); + Assert.Equal("", bundleExtensions[0].GetTestXml()); + + var bundleExtensionPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload[@Id='ExampleBext']"); + Assert.Equal(1, bundleExtensionPayloads.Count); + var ignored = new Dictionary> + { + { "Payload", new List { "FileSize", "Hash", "SourcePath" } }, + }; + Assert.Equal("", bundleExtensionPayloads[0].GetTestXml(ignored)); + } + } + + [Fact] + public void PopulatesManifestWithBundleExtensionSearches() + { + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleExtension", "BundleExtensionSearches.wxs"), + Path.Combine(folder, "BundleExtension", "BundleWithSearches.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); + Assert.Equal(1, bundleExtensions.Count); + Assert.Equal("", bundleExtensions[0].GetTestXml()); + + var extensionSearches = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:ExtensionSearch"); + Assert.Equal(2, extensionSearches.Count); + Assert.Equal("", extensionSearches[0].GetTestXml()); + Assert.Equal("", extensionSearches[1].GetTestXml()); + + var bundleExtensionDatas = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']"); + Assert.Equal(1, bundleExtensionDatas.Count); + Assert.Equal("" + + "" + + "" + + "", bundleExtensionDatas[0].GetTestXml()); + + var exampleSearches = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']/be:ExampleSearch"); + Assert.Equal(2, exampleSearches.Count); + } + } + + [Fact] + public void PopulatesManifestWithExePackages() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); + var ignoreAttributesByElementName = new Dictionary> + { + { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, + }; + Assert.Equal(2, exePackageElements.Count); + Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); + Assert.Equal("", exePackageElements[1].GetTestXml(ignoreAttributesByElementName)); + } + } + + [Fact] + public void PopulatesManifestWithSetVariables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SetVariable", "Simple.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var setVariables = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:SetVariable"); + Assert.Equal(6, setVariables.Count); + Assert.Equal("", setVariables[0].GetTestXml()); + Assert.Equal("", setVariables[1].GetTestXml()); + Assert.Equal("", setVariables[2].GetTestXml()); + Assert.Equal("", setVariables[3].GetTestXml()); + Assert.Equal("", setVariables[4].GetTestXml()); + Assert.Equal("", setVariables[5].GetTestXml()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs new file mode 100644 index 00000000..ad62dea6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs @@ -0,0 +1,107 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class CabFixture + { + [Fact] + public void CabinetFilesSequencedCorrectly() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + Assert.True(File.Exists(cabPath)); + + var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); + var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); + + Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray()); + Assert.Equal(new[] { "Notepad.exe", "test.txt" }, fileRows.Select(f => f.Name).ToArray()); + + var files = Query.GetCabinetFiles(cabPath); + Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); + } + } + + [Fact(Skip = "Sequence number of file from merge module is 0 but should be 1.")] + public void CabinetFilesSequencedCorrectlyUsingMergeModule() + { + var folder = TestData.Get(@"TestData\SimpleMerge"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, ".data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + Assert.True(File.Exists(cabPath)); + + var fileTable = Query.QueryDatabase(msiPath, new[] { "File" }); + var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); + + Assert.Equal(new[] { 1 }, fileRows.Select(f => f.Sequence).ToArray()); + Assert.Equal(new[] { "test.txt" }, fileRows.Select(f => f.Name).ToArray()); + + var files = Query.GetCabinetFiles(cabPath); + Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); + } + } + + private class FileRow + { + public FileRow(string row) + { + row = row.Substring("File:".Length); + + var split = row.Split('\t'); + this.Id = split[0]; + this.Name = split[2]; + this.Sequence = Convert.ToInt32(split[7]); + } + + public string Id { get; set; } + + public string Name { get; set; } + + public int Sequence { get; set; } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs new file mode 100644 index 00000000..d24ba08c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs @@ -0,0 +1,45 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class ComponentFixture + { + [Fact] + public void CanDetectDuplicateComponentGuids() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Component", "GuidCollision.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + var errors = result.Messages.Where(m => m.Level == MessageLevel.Error); + Array.Equals(new[] + { + 369, + 369 + }, errors.Select(e => e.Id).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs new file mode 100644 index 00000000..dd381dfe --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs @@ -0,0 +1,385 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.Burn; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ContainerFixture + { + [Fact(Skip = "Test demonstrates failure")] + public void CanBuildWithCustomAttachedContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder, buildToSubfolder: true); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "HarvestIntoAttachedContainer.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); + Assert.Equal(4, payloads.Count); + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + Assert.Equal(@"", payloads[0].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[1].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[2].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[3].GetTestXml(ignoreAttributes)); + } + } + + [Fact] + public void HarvestedPayloadsArePutInCorrectContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload"); + Assert.Equal(4, payloads.Count); + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + Assert.Equal(@"", payloads[0].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[1].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[2].GetTestXml(ignoreAttributes)); + Assert.Equal(@"", payloads[3].GetTestXml(ignoreAttributes)); + } + } + + [Fact] + public void HarvestedPayloadsArePutInCorrectPackage() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributes = new Dictionary> + { + { "MsiPackage", new List { "CacheId", "InstallSize", "Size", "ProductCode" } }, + { "Provides", new List { "Key" } }, + }; + var msiPackages = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributes)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "", + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "", + }, msiPackages); + } + } + + [Fact] + public void LayoutPayloadIsPutInContainer() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + WixAssert.CompareLineByLine(new string[] + { + "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributes)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, payloads); + } + } + + [Fact] + public void MultipleAttachedContainersAreNotCurrentlySupported() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Container", "MultipleAttachedContainers.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + Assert.Equal((int)BurnBackendErrors.Ids.MultipleAttachedContainersUnsupported, result.ExitCode); + } + } + + [Fact] + public void PayloadIsNotPutInMultipleContainers() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "Container", "PayloadInMultipleContainers.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'SharedPayload' can't be added to Container 'FirstX64' because it was already added to Container 'FirstX86'.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributes = new Dictionary> { { "Payload", new List { "FileSize", "Hash" } } }; + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributes)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, payloads); + } + } + + [Fact] + public void PopulatesBAManifestWithLayoutOnlyPayloads() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + this.BuildMsis(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath + }); + + WixAssert.CompareLineByLine(new string[] + { + "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributesByElementName = new Dictionary> + { + { "WixPayloadProperties", new List { "Size" } }, + }; + var payloads = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + "", + "", + "", + }, payloads); + } + } + + private void BuildMsis(string folder, string intermediateFolder, string binFolder, bool buildToSubfolder = false) + { + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX86" : ".", "FirstX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX64" : ".", "FirstX64.msi"), + }); + + result.AssertSuccess(); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs new file mode 100644 index 00000000..c6fa602b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs @@ -0,0 +1,48 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class CopyFileFixture + { + [Fact] + public void CanBuildCopyFile() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CopyFile", "CopyFile.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + var copyFileSymbol = section.Symbols.OfType().Single(); + Assert.Equal("MoveText", copyFileSymbol.Id.Id); + Assert.True(copyFileSymbol.Delete); + Assert.Equal("OtherFolder", copyFileSymbol.DestFolder); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs new file mode 100644 index 00000000..636b86a6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs @@ -0,0 +1,169 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class CustomActionFixture + { + [Fact] + public void CanDetectCustomActionCycle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomAction", "CustomActionCycle.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + Assert.Equal(176, result.ExitCode); + Assert.Equal("The InstallExecuteSequence table contains an action 'Action1' that is scheduled to come before or after action 'Action3', which is also scheduled to come before or after action 'Action1'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", result.Messages[0].ToString()); + } + } + + [Fact] + public void CanDetectCustomActionCycleWithTail() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomAction", "CustomActionCycleWithTail.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + Assert.Equal(176, result.ExitCode); + Assert.Equal("The InstallExecuteSequence table contains an action 'Action2' that is scheduled to come before or after action 'Action4', which is also scheduled to come before or after action 'Action2'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", result.Messages[0].ToString()); + } + } + + [Fact] + public void PopulatesCustomActionTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomAction", "UnscheduledCustomAction.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { + "ActionText", + "AdminExecuteSequence", + "AdminUISequence", + "AdvtExecuteSequence", + "Binary", + "CustomAction", + "InstallExecuteSequence", + "InstallUISequence", + "Property", + }).Where(x => !x.StartsWith("Property:") || x.StartsWith("Property:MsiHiddenProperties\t")).ToArray(); + Assert.Equal(new[] + { + "ActionText:CustomAction2\tProgess2Text\t", + "AdminExecuteSequence:CostFinalize\t\t1000", + "AdminExecuteSequence:CostInitialize\t\t800", + "AdminExecuteSequence:CustomAction2\t\t801", + "AdminExecuteSequence:FileCost\t\t900", + "AdminExecuteSequence:InstallAdminPackage\t\t3900", + "AdminExecuteSequence:InstallFiles\t\t4000", + "AdminExecuteSequence:InstallFinalize\t\t6600", + "AdminExecuteSequence:InstallInitialize\t\t1500", + "AdminExecuteSequence:InstallValidate\t\t1400", + "AdminUISequence:CostFinalize\t\t1000", + "AdminUISequence:CostInitialize\t\t800", + "AdminUISequence:CustomAction2\t\t801", + "AdminUISequence:ExecuteAction\t\t1300", + "AdminUISequence:FileCost\t\t900", + "AdvtExecuteSequence:CostFinalize\t\t1000", + "AdvtExecuteSequence:CostInitialize\t\t800", + "AdvtExecuteSequence:CustomAction2\t\t801", + "AdvtExecuteSequence:InstallFinalize\t\t6600", + "AdvtExecuteSequence:InstallInitialize\t\t1500", + "AdvtExecuteSequence:InstallValidate\t\t1400", + "AdvtExecuteSequence:PublishFeatures\t\t6300", + "AdvtExecuteSequence:PublishProduct\t\t6400", + "Binary:Binary1\t[Binary data]", + "CustomAction:CustomAction1\t1\tBinary1\tInvalidEntryPoint\t", + "CustomAction:CustomAction2\t51\tTestAdvtExecuteSequenceProperty\t1\t", + "CustomAction:CustomActionWithHiddenTarget\t9217\tBinary1\tInvalidEntryPoint\t", + "CustomAction:DiscardOptimismAllBeingsWhoProceed\t19\t\tAbandon hope all ye who enter here.\t", + "InstallExecuteSequence:CostFinalize\t\t1000", + "InstallExecuteSequence:CostInitialize\t\t800", + "InstallExecuteSequence:CreateFolders\t\t3700", + "InstallExecuteSequence:CustomAction2\t\t801", + "InstallExecuteSequence:FileCost\t\t900", + "InstallExecuteSequence:FindRelatedProducts\t\t25", + "InstallExecuteSequence:InstallFiles\t\t4000", + "InstallExecuteSequence:InstallFinalize\t\t6600", + "InstallExecuteSequence:InstallInitialize\t\t1500", + "InstallExecuteSequence:InstallValidate\t\t1400", + "InstallExecuteSequence:LaunchConditions\t\t100", + "InstallExecuteSequence:MigrateFeatureStates\t\t1200", + "InstallExecuteSequence:ProcessComponents\t\t1600", + "InstallExecuteSequence:PublishFeatures\t\t6300", + "InstallExecuteSequence:PublishProduct\t\t6400", + "InstallExecuteSequence:RegisterProduct\t\t6100", + "InstallExecuteSequence:RegisterUser\t\t6000", + "InstallExecuteSequence:RemoveExistingProducts\t\t1401", + "InstallExecuteSequence:RemoveFiles\t\t3500", + "InstallExecuteSequence:RemoveFolders\t\t3600", + "InstallExecuteSequence:UnpublishFeatures\t\t1800", + "InstallExecuteSequence:ValidateProductID\t\t700", + "InstallUISequence:CostFinalize\t\t1000", + "InstallUISequence:CostInitialize\t\t800", + "InstallUISequence:CustomAction2\t\t801", + "InstallUISequence:ExecuteAction\t\t1300", + "InstallUISequence:FileCost\t\t900", + "InstallUISequence:FindRelatedProducts\t\t25", + "InstallUISequence:LaunchConditions\t\t100", + "InstallUISequence:MigrateFeatureStates\t\t1200", + "InstallUISequence:ValidateProductID\t\t700", + "Property:MsiHiddenProperties\tCustomActionWithHiddenTarget", + }, results); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs new file mode 100644 index 00000000..ee93b03a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs @@ -0,0 +1,234 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Xml.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class CustomTableFixture + { + [Fact] + public void PopulatesCustomTable1() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTable1" }); + Assert.Equal(new[] + { + "CustomTable1:Row1\ttest.txt", + "CustomTable1:Row2\ttest.txt", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithLocalization() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" }); + Assert.Equal(new[] + { + "CustomTableLocalized:Row1\tThis is row one", + "CustomTableLocalized:Row2\tThis is row two", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithFilePath() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); + Assert.Equal(new[] + { + "CustomTableWithFile:Row1\t[Binary data]", + "CustomTableWithFile:Row2\t[Binary data]", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithFilePathSerialized() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-lib", wixlibPath, + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); + Assert.Equal(new[] + { + "CustomTableWithFile:Row1\t[Binary data]", + "CustomTableWithFile:Row2\t[Binary data]", + }, results); + } + } + + [Fact] + public void UnrealCustomTableIsNotPresentInMsi() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" }); + Assert.Empty(results); + } + } + + [Fact] + public void CanCompileAndDecompile() + { + var folder = TestData.Get(@"TestData"); + var expectedFile = Path.Combine(folder, "CustomTable", "CustomTable-Expected.wxs"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + var decompiledWxsPath = Path.Combine(baseFolder, @"decompiled.wxs"); + + var result = WixRunner.Execute(new[] + { + "build", + "-d", "ProductCode=83f9c623-26fe-42ab-951e-170022117f54", + Path.Combine(folder, "CustomTable", "CustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + Assert.True(File.Exists(msiPath)); + + result = WixRunner.Execute(new[] + { + "decompile", msiPath, + "-sw1060", + "-intermediateFolder", intermediateFolder, + "-o", decompiledWxsPath + }); + + result.AssertSuccess(); + + WixAssert.CompareXml(expectedFile, decompiledWxsPath); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs new file mode 100644 index 00000000..ab04da15 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs @@ -0,0 +1,86 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class DecompileFixture + { + private static void DecompileAndCompare(string sourceFolder, string msiName, string expectedWxsName) + { + var folder = TestData.Get(sourceFolder); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs"); + + var result = WixRunner.Execute(new[] + { + "decompile", + Path.Combine(folder, msiName), + "-intermediateFolder", intermediateFolder, + "-o", outputPath + }); + + result.AssertSuccess(); + + WixAssert.CompareXml(Path.Combine(folder, expectedWxsName), outputPath); + } + } + + [Fact] + public void CanDecompileSingleFileCompressed() + { + DecompileAndCompare(@"TestData\DecompileSingleFileCompressed", "example.msi", "Expected.wxs"); + } + + [Fact] + public void CanDecompile64BitSingleFileCompressed() + { + DecompileAndCompare(@"TestData\DecompileSingleFileCompressed64", "example.msi", "Expected.wxs"); + } + + [Fact] + public void CanDecompileNestedDirSearchUnderRegSearch() + { + DecompileAndCompare(@"TestData\AppSearch", "NestedDirSearchUnderRegSearch.msi", "DecompiledNestedDirSearchUnderRegSearch.wxs"); + } + + [Fact] + public void CanDecompileOldClassTableDefinition() + { + // The input MSI was not created using standard methods, it is an example of a real world database that needs to be decompiled. + // The Class/@Feature_ column has length of 32, the File/@Attributes has length of 2, + // and numerous foreign key relationships are missing. + DecompileAndCompare(@"TestData\Class", "OldClassTableDef.msi", "DecompiledOldClassTableDef.wxs"); + } + + [Fact] + public void CanDecompileSequenceTables() + { + DecompileAndCompare(@"TestData\SequenceTables", "SequenceTables.msi", "DecompiledSequenceTables.wxs"); + } + + [Fact] + public void CanDecompileShortcuts() + { + DecompileAndCompare(@"TestData\Shortcut", "shortcuts.msi", "DecompiledShortcuts.wxs"); + } + + [Fact] + public void CanDecompileNullComponent() + { + DecompileAndCompare(@"TestData\DecompileNullComponent", "example.msi", "Expected.wxs"); + } + + [Fact] + public void CanDecompileMergeModuleWithTargetDirComponent() + { + DecompileAndCompare(@"TestData\DecompileTargetDirMergeModule", "MergeModule1.msm", "Expected.wxs"); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs new file mode 100644 index 00000000..840b411e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs @@ -0,0 +1,180 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class DependencyExtensionFixture + { + [Fact] + public void CanBuildBundleUsingExePackageWithProvides() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Dependency", "ExePackageProvidesBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage/burn:Provides") + .Cast() + .Select(e => e.GetTestXml()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, provides); + } + } + + [Fact] + public void CanBuildBundleUsingMsiWithProvides() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "UsingProvides", "Package.wxs"), + Path.Combine(folder, "UsingProvides", "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "UsingProvides", "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "UsingProvides"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "UsingProvides.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Dependency", "UsingProvidesBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage/burn:Provides") + .Cast() + .Select(e => e.GetTestXml()) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + "", + }, provides); + } + } + + [Fact] + public void CanBuildBundleWithCustomProviderKey() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var bundlePath = Path.Combine(binFolder, "test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Dependency", "CustomProviderKeyBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributesByElementName = new Dictionary> + { + { "Registration", new List { "Id" } }, + }; + var registration = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, registration); + } + } + + [Fact] + public void CanBuildPackageUsingProvides() + { + var folder = TestData.Get(@"TestData\UsingProvides"); + var build = new Builder(folder, null, new[] { folder }); + + var results = build.BuildAndQuery(Build, "WixDependencyProvider"); + Assert.Equal(new[] + { + "WixDependencyProvider:dep74OfIcniaqxA7EprRGBw4Oyy3r8\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tUsingProvides\t\t\t", + }, results); + } + + private static void Build(string[] args) + { + var result = WixRunner.Execute(args) + .AssertSuccess(); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs new file mode 100644 index 00000000..a61bdff3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs @@ -0,0 +1,271 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class DirectoryFixture + { + [Fact] + public void CanGet32bitProgramFiles6432Folder() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Directory", "Empty.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", + "ProgramFiles6432Folder:ProgramFilesFolder:.", + "ProgramFilesFolder:TARGETDIR:PFiles", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); + } + } + + [Fact] + public void CanGet64bitProgramFiles6432Folder() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "Directory", "Empty.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", + "ProgramFiles6432Folder:ProgramFiles64Folder:.", + "ProgramFiles64Folder:TARGETDIR:PFiles64", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); + } + } + + [Fact] + public void CanGetDefaultName() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Directory", "DefaultName.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + WixAssert.CompareLineByLine(new[] + { + "BinFolder\tCompanyFolder\t.", + "CompanyFolder\tProgramFilesFolder\tExample Corporation", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + WixAssert.CompareLineByLine(new[] + { + "BinFolder\tCompanyFolder\t.", + "CompanyFolder\tProgramFilesFolder\tu7-b4gch|Example Corporation", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); + } + } + + [Fact] + public void CanGetDuplicateDir() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "DuplicateDir", "DuplicateDir.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "dZsSsu81KcG46xXTwc4mTSZO5Zx4:INSTALLFOLDER:dupe", + "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage", + "ProgramFiles6432Folder:ProgramFiles64Folder:.", + "ProgramFiles64Folder:TARGETDIR:PFiles64", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); + } + } + + [Fact] + public void CanGetWithMultiNestedSubdirectory() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "Directory", "Nested.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "BinFolder:ProgramFilesFolder:Example Corporation\\Test Product\\bin", + "ProgramFilesFolder:TARGETDIR:PFiles", + "TARGETDIR::SourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + Assert.Equal(new[] + { + "d4EceYatXTyy8HXPt5B6DT9Rj.wE:ProgramFilesFolder:u7-b4gch|Example Corporation", + "dSJ1pgiASlW7kJTu0wqsGBklJsS0:d4EceYatXTyy8HXPt5B6DT9Rj.wE:vjj-gxay|Test Product", + "BinFolder:dSJ1pgiASlW7kJTu0wqsGBklJsS0:bin", + "ProgramFilesFolder:TARGETDIR:PFiles", + "TARGETDIR::SourceDir" + }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).ToArray()); + } + } + + [Fact] + public void CanGetDuplicateTargetSourceName() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-arch", "x64", + Path.Combine(folder, "Directory", "DuplicateTargetSourceName.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().ToList(); + Assert.Equal(new[] + { + "BinFolder\tProgramFilesFolder\tbin", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + Assert.Equal(new[] + { + "BinFolder\tProgramFilesFolder\tbin", + "ProgramFilesFolder\tTARGETDIR\tPFiles", + "TARGETDIR\t\tSourceDir" + }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs new file mode 100644 index 00000000..e2306dcd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs @@ -0,0 +1,52 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ExePackageFixture + { + [Fact] + public void ErrorWhenMissingDetectCondition() + { + var folder = TestData.Get(@"TestData", "ExePackage"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MissingDetectCondition.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(1153, result.ExitCode); + } + } + + [Fact] + public void ErrorWhenRequireDetectCondition() + { + var folder = TestData.Get(@"TestData", "ExePackage"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "RequireDetectCondition.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(401, result.ExitCode); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs new file mode 100644 index 00000000..089658e6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs @@ -0,0 +1,153 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class ExtensionFixture + { + [Fact] + public void CanBuildAndQuery() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var build = new Builder(folder, typeof(ExampleExtensionFactory), new[] { Path.Combine(folder, "data") }); + + var results = build.BuildAndQuery(Build, "Wix4Example"); + Assert.Equal(new[] + { + "Wix4Example:Foo\tBar" + }, results); + } + + [Fact] + public void CanBuildWithExampleExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\example.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + + var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single(); + Assert.Equal("Foo", example.Id?.Id); + Assert.Equal("Bar", example[0].AsString()); + } + } + + [Fact] + public void CanParseCommandLineWithExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-example", "test", + "-o", Path.Combine(intermediateFolder, @"bin\extest.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")); + var section = intermediate.Sections.Single(); + + var property = section.Symbols.OfType().Where(p => p.Id.Id == "ExampleProperty").Single(); + Assert.Equal("ExampleProperty", property.Id.Id); + Assert.Equal("test", property.Value); + } + } + + [Fact] + public void CannotBuildWithMissingExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var exception = Assert.Throws(() => + WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-ext", "ExampleExtension.DoesNotExist" + })); + + Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist' could not be found. Checked paths: ", exception.Message); + } + } + + [Fact] + public void CannotBuildWithMissingVersionedExtension() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var exception = Assert.Throws(() => + WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-ext", "ExampleExtension.DoesNotExist/1.0.0" + })); + + Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist/1.0.0' could not be found. Checked paths: ", exception.Message); + } + } + + private static void Build(string[] args) + { + var result = WixRunner.Execute(args) + .AssertSuccess(); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs new file mode 100644 index 00000000..db9708a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class LanguageFixture + { + [Fact] + public void CanBuildWithDefaultProductLanguage() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var directorySymbols = section.Symbols.OfType(); + WixAssert.CompareLineByLine(new[] + { + "INSTALLFOLDER:Example Corporation\\MsiPackage", + "ProgramFilesFolder:PFiles", + "TARGETDIR:SourceDir" + }, directorySymbols.OrderBy(s => s.Id.Id).Select(s => s.Id.Id + ":" + s.Name).ToArray()); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("0", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;0", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("1252", summaryCodepage.Value); + + var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var directoryRows = data.Tables["Directory"].Rows; + WixAssert.CompareLineByLine(new[] + { + "d4EceYatXTyy8HXPt5B6DT9Rj.wE:u7-b4gch|Example Corporation", + "INSTALLFOLDER:oekcr5lq|MsiPackage", + "ProgramFilesFolder:PFiles", + "TARGETDIR:SourceDir" + }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(2)).ToArray()); + } + } + + [Fact] + public void CanBuildEnuWxl() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("1033", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;1033", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("1252", summaryCodepage.Value); + } + } + + [Fact] + public void CanBuildJpnWxl() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.ja-jp.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("1041", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;1041", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("932", summaryCodepage.Value); + } + } + + [Fact] + public void CanBuildJpnWxlWithEnuSummaryInfo() + { + var folder = TestData.Get(@"TestData", "Language"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "PackageWithEnSummaryInfo.ja-jp.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var propertySymbol = section.Symbols.OfType().Single(p => p.Id.Id == "ProductLanguage"); + Assert.Equal("1041", propertySymbol.Value); + + var summaryPlatform = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("Intel;1041", summaryPlatform.Value); + + var summaryCodepage = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.Codepage); + Assert.Equal("1252", summaryCodepage.Value); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs new file mode 100644 index 00000000..cfe4d3f1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + using Xunit; + + public class LinkerFixture + { + [Fact] + public void MustCompileBeforeLinking() + { + var intermediate1 = new Intermediate("TestIntermediate1", new[] { new IntermediateSection("test1", SectionType.Product) }, null); + var intermediate2 = new Intermediate("TestIntermediate2", new[] { new IntermediateSection("test2", SectionType.Fragment) }, null); + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + + var listener = new TestMessageListener(); + var messaging = serviceProvider.GetService(); + messaging.SetListener(listener); + + var creator = serviceProvider.GetService(); + var context = serviceProvider.GetService(); + context.Extensions = Array.Empty(); + context.ExtensionData = Array.Empty(); + context.Intermediates = new[] { intermediate1, intermediate2 }; + context.SymbolDefinitionCreator = creator; + + var linker = serviceProvider.GetService(); + linker.Link(context); + + Assert.Equal((int)ErrorMessages.Ids.IntermediatesMustBeCompiled, messaging.LastErrorNumber); + Assert.Single(listener.Messages); + Assert.EndsWith("TestIntermediate1, TestIntermediate2", listener.Messages[0].ToString()); + } + + [Fact] + public void CanBuildWithOverridableActions() + { + var folder = TestData.Get(@"TestData\OverridableActions"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1008", // this is expected for this test + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var actions = section.Symbols.OfType().Where(wat => wat.Action.StartsWith("Set")).ToList(); + Assert.Equal(2, actions.Count); + //Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileSymbolFields.Source].AsPath().Path); + //Assert.Equal(@"test.txt", wixFile[WixFileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void MissingEntrySectionDetectedProduct() + { + var folder = TestData.Get(@"TestData\OverridableActions"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + try + { + WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + } + catch (WixException we) + { + Assert.Equal("Could not find entry section in provided list of intermediates. Expected section of type 'Product'.", we.Message); + return; + } + + Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); + } + } + + [Fact] + public void MissingEntrySectionDetectedWixipl() + { + var folder = TestData.Get(@"TestData\OverridableActions"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + try + { + WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.wixipl") + }); + } + catch (WixException we) + { + Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message); + return; + } + + Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); + } + } + + [Fact] + public void MissingEntrySectionDetectedUnknown() + { + var folder = TestData.Get(@"TestData\OverridableActions"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + try + { + WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.bob") + }); + } + catch (WixException we) + { + Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message); + return; + } + + Assert.True(false, "Expected WixException for missing entry section but expectations were not met."); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs new file mode 100644 index 00000000..de18e30c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs @@ -0,0 +1,62 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class MediaFixture + { + [Fact] + public void CanBuildMultiMedia() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Media", "MultiMedia.wxs"), + "-bindpath", Path.Combine(folder, "Media", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var mediaSymbols = section.Symbols.OfType().OrderBy(m => m.DiskId).ToList(); + var fileSymbols = section.Symbols.OfType().OrderBy(f => f.Sequence).ToList(); + Assert.Equal(1, mediaSymbols[0].DiskId); + Assert.Equal(2, mediaSymbols[0].LastSequence); + Assert.Equal(2, mediaSymbols[1].DiskId); + Assert.Equal(4, mediaSymbols[1].LastSequence); + Assert.Equal(new[] + { + "a1.txt", + "a2.txt", + "b1.txt", + "b2.txt", + }, fileSymbols.Select(f => f.Name).ToArray()); + Assert.Equal(new[] + { + 1, + 2, + 3, + 4, + }, fileSymbols.Select(f => f.Sequence).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs new file mode 100644 index 00000000..17e91692 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs @@ -0,0 +1,113 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class ModuleFixture + { + [Fact] + public void CanBuildSimpleModule() + { + var folder = TestData.Get(@"TestData\SimpleModule"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Module.wxs"), + "-loc", Path.Combine(folder, "Module.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msm") + }); + + result.AssertSuccess(); + + var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); + Assert.True(File.Exists(msmPath)); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var dirSymbols = section.Symbols.OfType().OrderBy(d => d.Id.Id).ToList(); + WixAssert.CompareLineByLine(new[] + { + "MergeRedirectFolder\tTARGETDIR\t.", + "NotTheMergeRedirectFolder\tTARGETDIR\t.", + "TARGETDIR\t\tSourceDir" + }, dirSymbols.Select(d => String.Join("\t", d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray()); + + var fileSymbols = section.Symbols.OfType().OrderBy(d => d.Id.Id).ToList(); + WixAssert.CompareLineByLine(new[] + { + $"File1\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", + $"File2\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt", + }, fileSymbols.Select(fileSymbol => String.Join("\t", fileSymbol.Id.Id, fileSymbol[FileSymbolFields.Source].AsPath().Path, fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path)).ToArray()); + + var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var fileRows = data.Tables["File"].Rows; + Assert.Equal(new[] + { + "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + }, fileRows.Select(r => r.FieldAsString(0)).ToArray()); + + var cabPath = Path.Combine(intermediateFolder, "msm-test.cab"); + Query.ExtractStream(msmPath, "MergeModule.CABinet", cabPath); + var files = Query.GetCabinetFiles(cabPath); + Assert.Equal(new[] + { + "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE", + }, files.Select(f => Path.Combine(f.Path, f.Name)).ToArray()); + } + } + + [Fact] + public void CanSuppressModularization() + { + var folder = TestData.Get(@"TestData\SuppressModularization"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Module.wxs"), + "-loc", Path.Combine(folder, "Module.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-sw1079", + "-sw1086", + "-o", Path.Combine(intermediateFolder, @"bin\test.msm") + }); + + result.AssertSuccess(); + + var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm"); + + var rows = Query.QueryDatabase(msmPath, new[] { "CustomAction", "Property" }); + WixAssert.CompareLineByLine(new[] + { + "CustomAction:Test\t11265\tFakeCA.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tTestEntry\t", + "Property:MsiHiddenProperties\tTest" + }, rows); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs new file mode 100644 index 00000000..3bdfa0ef --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs @@ -0,0 +1,838 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class MsiFixture + { + [Fact] + public void CanBuildSingleFile() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + + Assert.False(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Compiled)); + Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Linked)); + Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Resolved)); + Assert.True(intermediate.HasLevel(WixToolset.Data.WindowsInstaller.IntermediateLevels.FullyBound)); + + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().First(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CanBuildSingleFileCompressed() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CanBuildSingleFileCompressedWithMediaTemplate() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression() + { + var folder = TestData.Get(@"TestData\SingleFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel=low", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildMultipleFilesCompressed() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1079", // TODO: why does this test need to create a second cab which is empty? + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example1.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example2.cab"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanFailBuildMissingFile() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "does-not-exist"), + "-bindpath", Path.Combine(folder, "also-does-not-exist"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }, out var messages); + Assert.Equal(103, result); + + var error = messages.Single(m => m.Level == MessageLevel.Error); + var errorMessage = error.ToString(); + var checkedPaths = errorMessage.Substring(errorMessage.IndexOf(':') + 1).Split(new[] { ',' }).Select(s => s.Trim()).ToArray(); + Assert.Equal(new[] + { + "test.txt", + Path.Combine(folder, "does-not-exist", "test.txt"), + Path.Combine(folder, "also-does-not-exist", "test.txt"), + }, checkedPaths); + } + } + + [Fact] + public void CanBuildWithErrorTable() + { + var folder = TestData.Get(@"TestData\ErrorsInUI"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var errors = section.Symbols.OfType().ToDictionary(t => t.Id.Id); + Assert.Equal("Category 55 Emergency Doomsday Crisis", errors["1234"].Message.Trim()); + Assert.Equal(" ", errors["5678"].Message); + + var customAction1 = section.Symbols.OfType().Where(t => t.Id.Id == "CanWeReferenceAnError_YesWeCan").Single(); + Assert.Equal("1234", customAction1.Target); + + var customAction2 = section.Symbols.OfType().Where(t => t.Id.Id == "TextErrorsWorkOKToo").Single(); + Assert.Equal("If you see this, something went wrong.", customAction2.Target); + } + } + + [Fact] + public void CanLoadPdbGeneratedByBuild() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + + var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); + Assert.True(File.Exists(pdbPath)); + + var output = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: true); + Assert.NotNull(output); + } + } + + [Fact] + public void CanLoadPdbGeneratedByBuildViaWixOutput() + { + var folder = TestData.Get(@"TestData\MultiFileCompressed"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-d", "MediaTemplateCompressionLevel", + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab"))); + + var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); + Assert.True(File.Exists(pdbPath)); + + var wixOutput = WixOutput.Read(pdbPath); + var output = WindowsInstallerData.Load(wixOutput, suppressVersionCheck: true); + Assert.NotNull(output); + } + } + + [Fact] + public void CanBuildManualUpgrade() + { + var folder = TestData.Get(@"TestData\ManualUpgrade"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }, out var messages); + + Assert.Equal(0, result); + + var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb"); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); + Assert.True(File.Exists(pdbPath)); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(pdbPath); + var section = intermediate.Sections.Single(); + + var upgradeSymbol = section.Symbols.OfType().Single(); + Assert.False(upgradeSymbol.ExcludeLanguages); + Assert.True(upgradeSymbol.IgnoreRemoveFailures); + Assert.False(upgradeSymbol.VersionMaxInclusive); + Assert.True(upgradeSymbol.VersionMinInclusive); + Assert.Equal("13.0.0", upgradeSymbol.VersionMax); + Assert.Equal("12.0.0", upgradeSymbol.VersionMin); + Assert.False(upgradeSymbol.OnlyDetect); + Assert.Equal("BLAHBLAHBLAH", upgradeSymbol.ActionProperty); + + var pdb = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: false); + var secureProperties = pdb.Tables["Property"].Rows.Where(row => row.GetKey() == "SecureCustomProperties").Single(); + Assert.Contains("BLAHBLAHBLAH", secureProperties.FieldAsString(1)); + } + } + + [Fact] + public void CanBuildWixipl() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.wixipl") + }, out var messages); + + Assert.Equal(0, result); + + var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin")); + + Assert.Equal(new[]{ + "test.wixipl" + }, builtFiles.Select(Path.GetFileName).ToArray()); + } + } + + [Fact] + public void CanBuildWixlib() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.wixlib") + }, out var messages); + + Assert.Equal(0, result); + + var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin")); + + Assert.Equal(new[]{ + "test.wixlib" + }, builtFiles.Select(Path.GetFileName).ToArray()); + } + } + + [Fact] + public void CanBuildBinaryWixlib() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute( + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-bindfiles", + "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); + + result.AssertSuccess(); + + using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) + { + Assert.NotNull(wixout.GetDataStream("wix-ir.json")); + + var text = wixout.GetData("wix-ir/test.txt"); + Assert.Equal("This is test.txt.", text); + } + } + } + + [Fact] + public void CanBuildBinaryWixlibWithCollidingFilenames() + { + var folder = TestData.Get(@"TestData\SameFileFolders"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute( + "build", + Path.Combine(folder, "TestComponents.wxs"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-bindfiles", + "-o", Path.Combine(baseFolder, @"bin\test.wixlib")); + + result.AssertSuccess(); + + using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib"))) + { + Assert.NotNull(wixout.GetDataStream("wix-ir.json")); + + var text = wixout.GetData("wix-ir/test.txt"); + Assert.Equal(@"This is a\test.txt.", text); + + var text2 = wixout.GetData("wix-ir/test.txt-1"); + Assert.Equal(@"This is b\test.txt.", text2); + + var text3 = wixout.GetData("wix-ir/test.txt-2"); + Assert.Equal(@"This is c\test.txt.", text3); + } + } + } + + [Fact] + public void CanBuildWithIncludePath() + { + var folder = TestData.Get(@"TestData\IncludePath"); + var bindpath = Path.Combine(folder, "data"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute( + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", bindpath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi"), + "-i", bindpath); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CanBuildWithAssembly() + { + var folder = TestData.Get(@"TestData\Assembly"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\AssemblyMsiPackage\candle.exe"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\candle.exe"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"candle.exe", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + + var msiAssemblyNameSymbols = section.Symbols.OfType(); + Assert.Equal(new[] + { + "culture", + "fileVersion", + "name", + "processorArchitecture", + "publicKeyToken", + "version" + }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Name).ToArray()); + + Assert.Equal(new[] + { + "neutral", + "3.11.11810.0", + "candle", + "x86", + "256B3414DFA97718", + "3.0.0.0" + }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Value).ToArray()); + } + } + + [Fact] + public void CanBuild64bit() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var platformSummary = section.Symbols.OfType().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage); + Assert.Equal("x64;1033", platformSummary.Value); + } + } + + [Fact] + public void CanBuildSharedComponent() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + // Only one component is shared. + var sharedComponentSymbols = section.Symbols.OfType(); + Assert.Equal(1, sharedComponentSymbols.Sum(t => t.Shared ? 1 : 0)); + + // And it is this one. + var sharedComponentSymbol = sharedComponentSymbols.Single(t => t.Id.Id == "Shared.dll"); + Assert.True(sharedComponentSymbol.Shared); + } + } + + [Fact] + public void CanBuildSetProperty() + { + var folder = TestData.Get(@"TestData\SetProperty"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var output = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"), false); + var caRows = output.Tables["CustomAction"].Rows.Single(); + Assert.Equal("SetINSTALLLOCATION", caRows.FieldAsString(0)); + Assert.Equal("51", caRows.FieldAsString(1)); + Assert.Equal("INSTALLLOCATION", caRows.FieldAsString(2)); + Assert.Equal("[INSTALLFOLDER]", caRows.FieldAsString(3)); + } + } + + [Fact] + public void CanBuildVersionIndependentProgId() + { + var folder = TestData.Get(@"TestData\ProgId"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\Foo.exe"))); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var progids = section.Symbols.OfType().OrderBy(symbol => symbol.ProgId).ToList(); + Assert.Equal(new[] + { + "Foo.File.hol", + "Foo.File.hol.15" + }, progids.Select(p => p.ProgId).ToArray()); + + Assert.Equal(new[] + { + "Foo.File.hol.15", + null + }, progids.Select(p => p.ParentProgIdRef).ToArray()); + } + } + + [Fact] + public void CanBuildInstanceTransform() + { + var folder = TestData.Get(@"TestData\InstanceTransform"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var output = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"), false); + var substorage = output.SubStorages.Single(); + Assert.Equal("I1", substorage.Name); + + var data = substorage.Data; + Assert.Equal(new[] + { + "_SummaryInformation", + "Property", + "Upgrade" + }, data.Tables.Select(t => t.Name).ToArray()); + + Assert.Equal(new[] + { + "INSTANCEPROPERTY\tI1", + "ProductName\tMsiPackage (Instance 1)", + }, JoinRows(data.Tables["Property"])); + + Assert.Equal(new[] + { + "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", + "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t0\t0", + "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", + "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t0\t0" + }, JoinRows(data.Tables["Upgrade"])); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void FailsBuildAtLinkTimeForMissingEnsureTable() + { + var folder = TestData.Get(@"TestData"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BadEnsureTable", "BadEnsureTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + Assert.Collection(result.Messages, + first => + { + Assert.Equal(MessageLevel.Error, first.Level); + Assert.Equal("The identifier 'WixCustomTable:TableDefinitionNotExposedByExtension' could not be found. Ensure you have typed the reference correctly and that all the necessary inputs are provided to the linker.", first.ToString()); + }); + + Assert.False(File.Exists(msiPath)); + } + } + + private static string[] JoinRows(Table table) + { + return table.Rows.Select(r => JoinFields(r.Fields)).ToArray(); + + string JoinFields(Field[] fields) + { + return String.Join('\t', fields.Select(f => f.ToString())); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs new file mode 100644 index 00000000..71edddc6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs @@ -0,0 +1,1040 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class MsiQueryFixture + { + [Fact] + public void PopulatesAppIdTableWhenAdvertised() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppId", "Advertised.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppId" }); + WixAssert.CompareLineByLine(new[] + { + "AppId:{D6040299-B15C-4C94-AE26-0C9B60D14C35}\t\t\t\t\t\t", + }, results); + } + } + + [Fact] + public void PopulatesAppSearchTablesFromComponentSearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "ComponentSearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "CompLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLECOMPFOUND\tSampleCompSearch", + "CompLocator:SampleCompSearch\t{4D9A0D20-D0CC-40DE-B580-EAD38B985217}\t1", + }, results); + } + } + + [Fact] + public void PopulatesAppSearchTablesFromDirectorySearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "DirectorySearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLEDIRFOUND\tSampleDirSearch", + "DrLocator:SampleDirSearch\t\tC:\\SampleDir\t", + }, results); + } + } + + [Fact] + public void PopulatesAppSearchTablesFromFileSearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "FileSearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator", "IniLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLEFILEFOUND\tSampleFileSearch", + "DrLocator:SampleFileSearch\tSampleIniFileSearch\t\t", + "IniLocator:SampleFileSearch\tsample.fil\tMySection\tMyKey\t\t1", + }, results); + } + } + + [Fact] + public void PopulatesAppSearchTablesFromRegistrySearch() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "RegistrySearch.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", + "RegLocator:SampleRegSearch\t2\tSampleReg\t\t2", + }, results); + } + } + + [Fact] + public void PopulatesAppSearchTablesFromRegistrySearch64() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AppSearch", "RegistrySearch64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" }); + WixAssert.CompareLineByLine(new[] + { + "AppSearch:SAMPLEREGFOUND\tSampleRegSearch", + "RegLocator:SampleRegSearch\t2\tSampleReg\t\t18", + }, results); + } + } + + [Fact] + public void PopulatesClassTablesWhenIconIndexIsZero() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Class", "IconIndex0.wxs"), + Path.Combine(folder, "Icon", "SampleIcon.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, ".Data"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Class" }); + WixAssert.CompareLineByLine(new[] + { + "Class:{3FAED4CC-C473-4B8A-BE8B-303871377A4A}\tLocalServer32\tClassComp\t\tFakeClass3FAE\t\t\tSampleIcon\t0\t\t\tProductFeature\t", + }, results); + } + } + + [Fact] + public void PopulatesClassTablesWhenProgIdIsNestedUnderAdvertisedClass() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ProgId", "NestedUnderClass.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Class", "ProgId", "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Class:{F12A6F69-117F-471F-AE73-F8E74218F498}\tLocalServer32\tProgIdComp\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tFakeClassF12A\t\t\t\t\t\t\tProductFeature\t", + "ProgId:73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\t\t{F12A6F69-117F-471F-AE73-F8E74218F498}\tFakeClassF12A\t\t", + "Registry:regUIIK326nDZpkWHuexeF58EikQvA\t0\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tNoOpen\tNoOpen73E7\tProgIdComp", + "Registry:regvrhMurMp98anbQJkpgA8yJCefdM\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\Version\t\t0.0.0.1\tProgIdComp", + "Registry:regY1F4E2lvu_Up6gV6c3jeN5ukn8s\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\LocalServer32\tThreadingModel\tApartment\tProgIdComp", + }, results); + } + } + + [Fact] + public void PopulatesControlTables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DialogsInInstallUISequence", "PackageComponents.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + + var results = Query.QueryDatabase(msiPath, new[] { "CheckBox", "Control", "ControlCondition", "InstallUISequence" }); + WixAssert.CompareLineByLine(new[] + { + "CheckBox:WIXUI_EXITDIALOGOPTIONALCHECKBOX\t1", + "Control:FirstDialog\tHeader\tText\t0\t13\t90\t13\t3\t\tFirstDialogHeader\tTitle\t", + "Control:FirstDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tFirstDialogTitle\tHeader\t", + "Control:SecondDialog\tOptionalCheckBox\tCheckBox\t0\t13\t100\t40\t2\tWIXUI_EXITDIALOGOPTIONALCHECKBOX\t[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]\tTitle\tOptional checkbox|Check this box for fun", + "Control:SecondDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tSecondDialogTitle\tOptionalCheckBox\t", + "ControlCondition:FirstDialog\tHeader\tDisable\tInstalled", + "ControlCondition:FirstDialog\tHeader\tHide\tInstalled", + "ControlCondition:SecondDialog\tOptionalCheckBox\tShow\tWIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT AND NOT Installed", + "InstallUISequence:CostFinalize\t\t1000", + "InstallUISequence:CostInitialize\t\t800", + "InstallUISequence:ExecuteAction\t\t1300", + "InstallUISequence:FileCost\t\t900", + "InstallUISequence:FindRelatedProducts\t\t25", + "InstallUISequence:FirstDialog\tInstalled AND PATCH\t1298", + "InstallUISequence:LaunchConditions\t\t100", + "InstallUISequence:MigrateFeatureStates\t\t1200", + "InstallUISequence:SecondDialog\tNOT Installed\t1299", + "InstallUISequence:ValidateProductID\t\t700", + }, results); + } + } + + [Fact] + public void PopulatesCreateFolderTableForNullKeypathComponents() + { + var folder = TestData.Get(@"TestData\Components"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CreateFolder" }); + WixAssert.CompareLineByLine(new[] + { + "CreateFolder:INSTALLFOLDER\tNullKeypathComponent", + }, results); + } + } + + [Fact] + public void PopulatesDirectoryTableWithValidDefaultDir() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1031", // this is expected for this test + Path.Combine(folder, "DefaultDir", "DefaultDir.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Directory" }); + WixAssert.CompareLineByLine(new[] + { + "Directory:DUPLICATENAMEANDSHORTNAME\tINSTALLFOLDER\tduplicat", + "Directory:Folder1\tINSTALLFOLDER\tFolder.1", + "Directory:Folder12\tINSTALLFOLDER\tFolder.12", + "Directory:Folder123\tINSTALLFOLDER\tFolder.123", + "Directory:Folder1234\tINSTALLFOLDER\tyakwclwy|Folder.1234", + "Directory:INSTALLFOLDER\tProgramFiles6432Folder\t1egc1laj|MsiPackage", + "Directory:NAMEANDSHORTNAME\tINSTALLFOLDER\tSHORTNAM|NameAndShortName", + "Directory:NAMEANDSHORTSOURCENAME\tINSTALLFOLDER\tNAMEASSN|NameAndShortSourceName", + "Directory:NAMEWITHSHORTVALUE\tINSTALLFOLDER\tSHORTVAL", + "Directory:ProgramFiles6432Folder\tProgramFilesFolder\t.", + "Directory:ProgramFilesFolder\tTARGETDIR\tPFiles", + "Directory:SHORTNAMEANDLONGSOURCENAME\tINSTALLFOLDER\tSHNALSNM:6ukthv5q|ShortNameAndLongSourceName", + "Directory:SHORTNAMEONLY\tINSTALLFOLDER\tSHORTONL", + "Directory:SOURCENAME\tINSTALLFOLDER\ts2s5bq-i|NameAndSourceName:dhnqygng|SourceNameWithName", + "Directory:SOURCENAMESONLY\tINSTALLFOLDER\t.:SRCNAMON|SourceNameOnly", + "Directory:SOURCENAMEWITHSHORTVALUE\tINSTALLFOLDER\t.:SRTSRCVL", + "Directory:TARGETDIR\t\tSourceDir", + }, results); + } + } + + [Fact] + public void PopulatesEnvironmentTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Environment", "Environment.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Environment" }); + WixAssert.CompareLineByLine(new[] + { + "Environment:PATH\t=-*PATH\t[INSTALLFOLDER]; ;[~]\tWixEnvironmentTest", + "Environment:WixEnvironmentTest1\t=-WixEnvTest1\t\tWixEnvironmentTest", + "Environment:WixEnvironmentTest2\t+-WixEnvTest1\t\tWixEnvironmentTest", + "Environment:WixEnvironmentTest3\t!-WixEnvTest1\t\tWixEnvironmentTest", + "Environment:WixEnvironmentTest4\t=-*WIX\t[INSTALLFOLDER]\tWixEnvironmentTest", + }, results); + } + } + + [Fact(Skip = "Test demonstrates failure")] + public void PopulatesExampleTableBecauseOfEnsureTable() + { + var folder = TestData.Get(@"TestData"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "EnsureTable", "EnsureTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabaseByTable(msiPath, new[] { "Wix4Example" }); + Assert.Empty(results["Wix4Example"]); + } + } + + [Fact] + public void PopulatesFeatureTableWithParent() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "FeatureGroup", "FeatureGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Feature" }); + WixAssert.CompareLineByLine(new[] + { + "Feature:ChildFeature\tParentFeature\tChildFeatureTitle\t\t2\t1\t\t0", + "Feature:ParentFeature\t\tParentFeatureTitle\t\t2\t1\t\t0", + "Feature:ProductFeature\t\tMsiPackageTitle\t\t2\t1\t\t0", + }, results); + } + } + + [Fact] + public void PopulatesFontTableFromFontTitle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Font", "FontTitle.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Font" }); + WixAssert.CompareLineByLine(new[] + { + "Font:test.txt\tFakeFont", + }, results); + } + } + + [Fact] + public void PopulatesFontTableFromTrueType() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Font", "TrueType.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Font" }); + WixAssert.CompareLineByLine(new[] + { + "Font:TrueTypeFontFile\t", + }, results); + } + } + + [Fact] + public void PopulatesInstallExecuteSequenceTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "InstallExecuteSequence" }); + WixAssert.CompareLineByLine(new[] + { + "InstallExecuteSequence:CostFinalize\t\t1000", + "InstallExecuteSequence:CostInitialize\t\t800", + "InstallExecuteSequence:CreateFolders\t\t3700", + "InstallExecuteSequence:FileCost\t\t900", + "InstallExecuteSequence:FindRelatedProducts\t\t25", + "InstallExecuteSequence:InstallFiles\t\t4000", + "InstallExecuteSequence:InstallFinalize\t\t6600", + "InstallExecuteSequence:InstallInitialize\t\t1500", + "InstallExecuteSequence:InstallValidate\t\t1400", + "InstallExecuteSequence:LaunchConditions\t\t100", + "InstallExecuteSequence:MigrateFeatureStates\t\t1200", + "InstallExecuteSequence:ProcessComponents\t\t1600", + "InstallExecuteSequence:PublishFeatures\t\t6300", + "InstallExecuteSequence:PublishProduct\t\t6400", + "InstallExecuteSequence:RegisterProduct\t\t6100", + "InstallExecuteSequence:RegisterUser\t\t6000", + "InstallExecuteSequence:RemoveExistingProducts\t\t1401", + "InstallExecuteSequence:RemoveFiles\t\t3500", + "InstallExecuteSequence:RemoveFolders\t\t3600", + "InstallExecuteSequence:UnpublishFeatures\t\t1800", + "InstallExecuteSequence:ValidateProductID\t\t700", + }, results); + } + } + + [Fact] + public void PopulatesLockPermissionsTableWithEmptyPermissions() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "LockPermissions", "EmptyPermissions.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "LockPermissions" }); + WixAssert.CompareLineByLine(new[] + { + "LockPermissions:INSTALLFOLDER\tCreateFolder\t\tAdministrator\t0", + }, results); + } + } + + [Fact] + public void PopulatesMsiAssemblyTables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Assembly", "Win32Assembly.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "Assembly", "data"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "MsiAssembly", "MsiAssemblyName" }); + WixAssert.CompareLineByLine(new[] + { + "MsiAssembly:test.txt\tProductFeature\ttest.dll.manifest\t\t1", + "MsiAssemblyName:test.txt\tname\tMyApplication.app", + "MsiAssemblyName:test.txt\tversion\t1.0.0.0", + }, results); + } + } + + [Fact] + public void PopulatesReserveCostTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ReserveCost", "ReserveCost.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "ReserveCost" }); + WixAssert.CompareLineByLine(new[] + { + "ReserveCost:TestCost\tReserveCostComp\tINSTALLFOLDER\t100\t200", + }, results); + } + } + + [Fact] + public void PopulatesServiceTables() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ServiceInstall", "OwnProcess.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "ServiceInstall", "ServiceControl" }); + WixAssert.CompareLineByLine(new[] + { + "ServiceControl:SampleService\tSampleService\t161\t\t1\ttest.txt", + "ServiceInstall:SampleService\tSampleService\t\t16\t4\t0\t\t\t\t\t\ttest.txt\t", + }, results); + } + } + + [Fact] + public void PopulatesTextStyleTableWhenColorIsNull() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "TextStyle", "ColorNull.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); + WixAssert.CompareLineByLine(new[] + { + "TextStyle:FirstTextStyle\tArial\t2\t\t", + }, results); + } + } + + [Fact] + public void PopulatesTextStyleTableWhenSizeIsLocalized() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "TextStyle", "SizeLocalized.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-loc", Path.Combine(folder, "TextStyle", "SizeLocalized.en-us.wxl"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" }); + WixAssert.CompareLineByLine(new[] + { + "TextStyle:CustomFont\tTahoma\t8\t\t", + }, results); + } + } + + [Fact] + public void PopulatesTypeLibTableWhenLanguageIsZero() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "TypeLib", "Language0.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "TypeLib" }); + WixAssert.CompareLineByLine(new[] + { + "TypeLib:{765BE8EE-BD7F-491E-90D2-C5A972462B50}\t0\tTypeLibComp\t\t\t\tProductFeature\t", + }, results); + } + } + + [Fact] + public void PopulatesUpgradeTableFromManualUpgrade() + { + var folder = TestData.Get(@"TestData\ManualUpgrade"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }, out var messages); + + Assert.Equal(0, result); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); + WixAssert.CompareLineByLine(new[] + { + "Upgrade:{01120000-00E0-0000-0000-0000000FF1CE}\t12.0.0\t13.0.0\t\t260\t\tBLAHBLAHBLAH", + }, results); + } + } + + [Fact] + public void PopulatesUpgradeTableFromDetectOnlyUpgrade() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Upgrade", "DetectOnly.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" }); + WixAssert.CompareLineByLine(new[] + { + "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED", + "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED", + "Upgrade:{B05772EA-82B8-4DE0-B7EB-45B5F0CCFE6D}\t1.0.0\t\t\t256\t\tRELPRODFOUND", + }, results); + + var prefix = "Property:SecureCustomProperties\t"; + var secureProperties = Query.QueryDatabase(msiPath, new[] { "Property" }).Where(p => p.StartsWith(prefix)).Single(); + WixAssert.CompareLineByLine(new[] + { + "RELPRODFOUND", + "WIX_DOWNGRADE_DETECTED", + "WIX_UPGRADE_DETECTED", + }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p).ToArray()); + } + } + + [Fact] + public void CanMergeModule() + { + var folder = TestData.Get(@"TestData\SimpleMerge"); + + using (var fs = new DisposableFileSystem()) + { + var intermediateFolder = fs.GetFolder(); + var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi"); + var cabPath = Path.Combine(intermediateFolder, @"bin\cab1.cab"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, ".data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + Assert.Empty(section.Symbols.OfType()); + + var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + Assert.Empty(data.Tables["File"].Rows); + + var results = Query.QueryDatabase(msiPath, new[] { "File" }); + WixAssert.CompareLineByLine(new[] + { + "File:filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent.243FB739_4D05_472F_9CFB_EF6B1017B6DE\ttest.txt\t17\t\t\t512\t0" + }, results); + + var files = Query.GetCabinetFiles(cabPath); + WixAssert.CompareLineByLine(new[] + { + "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE" + }, files.Select(f => f.Name).ToArray()); + } + } + + [Fact] + public void CanPublishComponentWithMultipleFeatureComponents() + { + var folder = TestData.Get(@"TestData\PublishComponent"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "PublishComponent" }); + WixAssert.CompareLineByLine(new[] + { + "PublishComponent:{0A82C8F6-9CE9-4336-B8BE-91A39B5F7081} Qualifier2 Component2 AppData2 ProductFeature2", + "PublishComponent:{BD245B5A-EC33-46ED-98FF-E9D3D416AD04} Qualifier1 Component1 AppData1 ProductFeature1", + }, results); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs new file mode 100644 index 00000000..a566b490 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs @@ -0,0 +1,131 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class MsiTransactionFixture + { + [Fact] + public void CantBuildX64AfterX86Bundle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var exePath = Path.Combine(binFolder, "test.exe"); + + BuildMsiPackages(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1151", // this is expected for this test + Path.Combine(folder, "MsiTransaction", "X64AfterX86Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + Assert.Equal(390, result.ExitCode); + } + } + + [Fact] + public void CanBuildX86AfterX64Bundle() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var exePath = Path.Combine(binFolder, "test.exe"); + + BuildMsiPackages(folder, intermediateFolder, binFolder); + + var result = WixRunner.Execute(new[] + { + "build", + "-sw1151", // this is expected for this test + Path.Combine(folder, "MsiTransaction", "X86AfterX64Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", binFolder, + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + private static void BuildMsiPackages(string folder, string intermediateFolder, string binFolder) + { + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "FirstX86", "FirstX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "SecondX86.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(binFolder, "SecondX86", "SecondX86.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(binFolder, "FirstX64", "FirstX64.msi"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MsiTransaction", "SecondX64.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-arch", "x64", + "-o", Path.Combine(binFolder, "SecondX64", "SecondX64.msi"), + }); + + result.AssertSuccess(); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs new file mode 100644 index 00000000..475afcf0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs @@ -0,0 +1,36 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class MsuPackageFixture + { + [Fact] + public void CanBuildBundleWithMsuPackage() + { + var folder = TestData.Get(@"TestData", "MsuPackage"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, "bin", "test.exe") + }); + + result.AssertSuccess(); + Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.exe"))); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs new file mode 100644 index 00000000..6b2d8bfa --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs @@ -0,0 +1,211 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class PackagePayloadFixture + { + [Fact] + public void CanSpecifyPackagePayloadInPayloadGroup() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackagePayload", "PackagePayloadInPayloadGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage"); + var ignoreAttributesByElementName = new Dictionary> + { + { "ExePackage", new List { "CacheId", "InstallSize", "Size" } }, + }; + Assert.Equal(1, exePackageElements.Count); + Assert.Equal("", exePackageElements[0].GetTestXml(ignoreAttributesByElementName)); + + var payloadElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='burn.exe']"); + Assert.Equal(1, payloadElements.Count); + Assert.Equal("", payloadElements[0].GetTestXml()); + } + } + + [Fact] + public void ErrorWhenMissingSourceFileAndHash() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "MissingSourceFileAndHash.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(44, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MsuPackagePayload element's SourceFile or Hash attribute was not found; one of these is required.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenMissingSourceFileAndName() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "MissingSourceFileAndName.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(44, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MsiPackagePayload element's Name or SourceFile attribute was not found; one of these is required.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenSpecifiedHash() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SpecifiedHash.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(4, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MspPackagePayload element contains an unexpected attribute 'Hash'.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenSpecifiedHashAndMissingDownloadUrl() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SpecifiedHashAndMissingDownloadUrl.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(10, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The MsuPackagePayload/@DownloadUrl attribute was not found; it is required when attribute Hash is specified.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenSpecifiedSourceFileAndHash() + { + var folder = TestData.Get(@"TestData", "PackagePayload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "SpecifiedSourceFileAndHash.wxs"), + "-o", Path.Combine(baseFolder, "test.wixlib") + }); + + Assert.Equal(35, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The ExePackagePayload/@Hash attribute cannot be specified when attribute SourceFile is present.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + + [Fact] + public void ErrorWhenWrongPackagePayloadInPayloadGroup() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackagePayload", "WrongPackagePayloadInPayloadGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + Assert.Equal(407, result.ExitCode); + WixAssert.CompareLineByLine(new[] + { + "The ExePackagePayload element can only be used for ExePackages.", + "The location of the package related to previous error.", + "There is no payload defined for package 'WrongPackagePayloadInPayloadGroup'. This is specified on the MsiPackage element or a child MsiPackagePayload element.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs new file mode 100644 index 00000000..cdba85de --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs @@ -0,0 +1,36 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Linq; + using WixToolset.Core; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + using Xunit; + + public class ParseFixture + { + [Fact] + public void GeneratesCorrectCustomActionIdentifiers() + { + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var section = new IntermediateSection("section", SectionType.Fragment); + var parseHelper = serviceProvider.GetService(); + + parseHelper.CreateCustomActionReference(null, section, "CustomAction32", Platform.X86, CustomActionPlatforms.X86); + parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86); + parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); + parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86); + parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64); + + var simpleReferences = section.Symbols.OfType(); + Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction32_X86").FirstOrDefault()); + Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_X86").FirstOrDefault()); + Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_A64").FirstOrDefault()); + Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X86").FirstOrDefault()); + Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X64").FirstOrDefault()); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs new file mode 100644 index 00000000..483e3fd5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs @@ -0,0 +1,279 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text; + using System.Xml; + using System.Xml.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Burn; + using Xunit; + + public class PatchFixture + { + private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd"; + + [Fact] + public void CanBuildSimplePatch() + { + var folder = TestData.Get(@"TestData\PatchSingle"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + Assert.True(File.Exists(baselinePdb)); + Assert.True(File.Exists(update1Pdb)); + + var doc = GetExtractPatchXml(patchPath); + Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + + var names = Query.GetSubStorageNames(patchPath); + Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); + + var cab = Path.Combine(tempFolder, "foo.cab"); + Query.ExtractStream(patchPath, "foo.cab", cab); + Assert.True(File.Exists(cab)); + + var files = Query.GetCabinetFiles(cab); + Assert.Equal(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray()); + } + } + + [Fact] + public void CanBuildSimplePatchWithNoFileChanges() + { + var folder = TestData.Get(@"TestData\PatchNoFileChanges"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1", hasNoFiles: true); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + Assert.True(File.Exists(baselinePdb)); + Assert.True(File.Exists(update1Pdb)); + + var doc = GetExtractPatchXml(patchPath); + Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value); + + var names = Query.GetSubStorageNames(patchPath); + Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names); + + var cab = Path.Combine(tempFolder, "foo.cab"); + Query.ExtractStream(patchPath, "foo.cab", cab); + Assert.True(File.Exists(cab)); + + var files = Query.GetCabinetFiles(cab); + Assert.Empty(files); + } + } + + [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6387")] + public void CanBuildPatchFromProductWithFilesFromWixlib() + { + var folder = TestData.Get(@"TestData\PatchFromWixlib"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolderBaseline = fs.GetFolder(); + var tempFolderUpdate = fs.GetFolder(); + var tempFolderPatch = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(update1Pdb) }, hasNoFiles: true); + var patchPath = Path.ChangeExtension(patchPdb, ".msp"); + + Assert.True(File.Exists(baselinePdb)); + Assert.True(File.Exists(update1Pdb)); + } + } + + [Fact] + public void CanBuildBundleWithNonSpecificPatches() + { + var folder = TestData.Get(@"TestData\PatchNonSpecific"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.0", "A", "B"); + var updatePdb = BuildMsi("Update.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.1", "A", "B"); + var patchAPdb = BuildMsp("PatchA.msp", Path.Combine(folder, "PatchA"), tempFolder, "1.0.1", hasNoFiles: true); + var patchBPdb = BuildMsp("PatchB.msp", Path.Combine(folder, "PatchB"), tempFolder, "1.0.1", hasNoFiles: true); + var patchCPdb = BuildMsp("PatchC.msp", Path.Combine(folder, "PatchC"), tempFolder, "1.0.1", hasNoFiles: true); + var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); + var bundleBPdb = BuildBundle("BundleB.exe", Path.Combine(folder, "BundleB"), tempFolder); + var bundleCPdb = BuildBundle("BundleC.exe", Path.Combine(folder, "BundleC"), tempFolder); + + VerifyPatchTargetCodes(bundleAPdb, new[] + { + "", + }); + VerifyPatchTargetCodes(bundleBPdb, new[] + { + "", + "", + }); + VerifyPatchTargetCodes(bundleCPdb, new string[0]); + } + } + + [Fact] + public void CanBuildBundleWithSlipstreamPatch() + { + var folder = TestData.Get(@"TestData\PatchSingle"); + + using (var fs = new DisposableFileSystem()) + { + var tempFolder = fs.GetFolder(); + + var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0"); + var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1"); + var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1"); + var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder); + + using (var wixOutput = WixOutput.Read(bundleAPdb)) + { + var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); + var doc = new XmlDocument(); + doc.LoadXml(manifestData); + var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); + var slipstreamMspNodes = doc.SelectNodes("/w:BurnManifest/w:Chain/w:MsiPackage/w:SlipstreamMsp", nsmgr); + Assert.Equal(1, slipstreamMspNodes.Count); + Assert.Equal("", slipstreamMspNodes[0].GetTestXml()); + } + } + } + + private static void VerifyPatchTargetCodes(string pdbPath, string[] expected) + { + using (var wixOutput = WixOutput.Read(pdbPath)) + { + var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName); + var doc = new XmlDocument(); + doc.LoadXml(manifestData); + var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w"); + var patchTargetCodes = doc.SelectNodes("/w:BurnManifest/w:PatchTargetCode", nsmgr); + + var actual = new List(); + foreach (XmlNode patchTargetCodeNode in patchTargetCodes) + { + actual.Add(patchTargetCodeNode.GetTestXml()); + } + + WixAssert.CompareLineByLine(expected, actual.ToArray()); + } + } + + private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB) + { + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(sourceFolder, @"Package.wxs"), + "-d", "V=" + defineV, + "-d", "A=" + defineA, + "-d", "B=" + defineB, + "-bindpath", Path.Combine(sourceFolder, ".data"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", outputPath, + "-ext", extensionPath, + }); + + result.AssertSuccess(); + + return Path.ChangeExtension(outputPath, ".wixpdb"); + } + + private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable bindpaths = null, bool hasNoFiles = false) + { + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); + + var args = new List + { + "build", + hasNoFiles ? "-sw1079" : " ", + Path.Combine(sourceFolder, @"Patch.wxs"), + "-d", "V=" + defineV, + "-bindpath", Path.Combine(baseFolder, "bin"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", outputPath + }; + + foreach (var additionaBindPath in bindpaths ?? Enumerable.Empty()) + { + args.Add("-bindpath"); + args.Add(additionaBindPath); + } + + var result = WixRunner.Execute(args.ToArray()); + + result.AssertSuccess(); + + return Path.ChangeExtension(outputPath, ".wixpdb"); + } + + private static string BuildBundle(string outputName, string sourceFolder, string baseFolder) + { + var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName)); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(sourceFolder, @"Bundle.wxs"), + Path.Combine(sourceFolder, "..", "..", "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(sourceFolder, "..", "..", "SimpleBundle", "data"), + "-bindpath", Path.Combine(baseFolder, "bin"), + "-intermediateFolder", Path.Combine(baseFolder, "obj"), + "-o", outputPath + }); + + result.AssertSuccess(); + + return Path.ChangeExtension(outputPath, ".wixpdb"); + } + + private static XDocument GetExtractPatchXml(string path) + { + var buffer = new StringBuilder(65535); + var size = buffer.Capacity; + + var er = MsiExtractPatchXMLData(path, 0, buffer, ref size); + if (er != 0) + { + throw new Win32Exception(er); + } + + return XDocument.Parse(buffer.ToString()); + } + + [DllImport("msi.dll", EntryPoint = "MsiExtractPatchXMLDataW", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int MsiExtractPatchXMLData(string szPatchPath, int dwReserved, StringBuilder szXMLData, ref int pcchXMLData); + + [DllImport("msi.dll", EntryPoint = "MsiApplyPatchW", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine); + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs new file mode 100644 index 00000000..23f6a9ba --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs @@ -0,0 +1,212 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class PayloadFixture + { + [Fact] + public void CanParseValidName() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ValidName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + Assert.Empty(result.Messages); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var payloadSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(payloadSymbol); + + var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool + ? field.AsNullableNumber()?.ToString() + : field?.AsString()) + .ToList(); + Assert.Equal(@"dir\file.ext", fields[(int)WixBundlePayloadSymbolFields.Name]); + } + } + + [Fact] + public void CanCanonicalizeName() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(warningsAsErrors: false, new[] + { + "build", + Path.Combine(folder, "CanonicalizeName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + Assert.Single(result.Messages, m => m.Id == (int)WarningMessages.Ids.PathCanonicalized); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var payloadSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(payloadSymbol); + + var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool + ? field.AsNullableNumber()?.ToString() + : field?.AsString()) + .ToList(); + Assert.Equal(@"c\d.exe", fields[(int)WixBundlePayloadSymbolFields.Name]); + } + } + + [Fact] + public void RejectsAbsoluteName() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "AbsoluteName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.InRange(result.ExitCode, 2, int.MaxValue); + + var expectedIllegalRelativeLongFileName = 1; + var expectedPayloadMustBeRelativeToCache = 2; + Assert.Equal(expectedIllegalRelativeLongFileName, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.IllegalRelativeLongFilename).Count()); + Assert.Equal(expectedPayloadMustBeRelativeToCache, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.PayloadMustBeRelativeToCache).Count()); + } + } + + [Fact] + public void RejectsPayloadSharedBetweenPackageAndBA() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Payload", "SharedBAAndPackagePayloadBundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + Assert.Equal((int)LinkerErrors.Ids.PayloadSharedWithBA, result.ExitCode); + } + } + + [Fact] + public void ReplacesDownloadUrlPlaceholders() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(false, new[] + { + "build", + Path.Combine(folder, "Payload", "DownloadUrlPlaceholdersBundle.wxs"), + Path.Combine(folder, "SimpleBundle", "MultiFileBootstrapperApplication.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-bindpath", Path.Combine(folder, ".Data"), + "-intermediateFolder", intermediateFolder, + "-o", bundlePath, + }); + + result.AssertSuccess(); + + WixAssert.CompareLineByLine(new string[] + { + "The Payload 'burn.exe' is being added to Container 'PackagesContainer', overriding its Compressed value of 'no'.", + }, result.Messages.Select(m => m.ToString()).ToArray()); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var ignoreAttributesByElementName = new Dictionary> + { + { "Container", new List { "FileSize", "Hash" } }, + { "Payload", new List { "FileSize", "Hash" } }, + }; + var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + "", + "", + @"", + @"", + }, payloads); + + var containers = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Container") + .Cast() + .Select(e => e.GetTestXml(ignoreAttributesByElementName)) + .ToArray(); + WixAssert.CompareLineByLine(new string[] + { + "", + }, containers); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs new file mode 100644 index 00000000..ae8a1bcc --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs @@ -0,0 +1,181 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + using Xunit; + + public class PreprocessorFixture + { + [Fact] + public void PreprocessDirectly() + { + var folder = TestData.Get(@"TestData\IncludePath"); + var sourcePath = Path.Combine(folder, "Package.wxs"); + var includeFolder = Path.Combine(folder, "data"); + var includeFile = Path.Combine(includeFolder, "Package.wxi"); + + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + + var context = serviceProvider.GetService(); + context.SourcePath = sourcePath; + context.IncludeSearchPaths = new[] { includeFolder }; + + var preprocessor = serviceProvider.GetService(); + var result = preprocessor.Preprocess(context); + + var includedFile = result.IncludedFiles.Single(); + Assert.NotNull(result.Document); + Assert.Equal(includeFile, includedFile.Path); + Assert.Equal(sourcePath, includedFile.SourceLineNumbers.FileName); + Assert.Equal(1, includedFile.SourceLineNumbers.LineNumber.Value); + Assert.Equal($"{sourcePath}*1", includedFile.SourceLineNumbers.QualifiedFileName); + Assert.Null(includedFile.SourceLineNumbers.Parent); + } + + [Fact] + public void IncludeSourceLineNumbersPreserved() + { + var folder = TestData.Get(@"TestData\IncludePath"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(warningsAsErrors: false, new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-includepath", Path.Combine(folder, "data"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + using (var output = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) + { + var intermediate = Intermediate.Load(output); + var component = intermediate.Sections.Single().Symbols.OfType().Single(); + Assert.Equal(3, component.SourceLineNumbers.LineNumber); + Assert.Equal(5, component.SourceLineNumbers.Parent.LineNumber); + + var encoded = component.SourceLineNumbers.GetEncoded(); + var decoded = SourceLineNumber.CreateFromEncoded(encoded); + Assert.Equal(3, decoded.LineNumber); + Assert.Equal(5, decoded.Parent.LineNumber); + } + } + } + + [Fact] + /// + /// This test will fail on 32-bit operating systems because it depends on "CommonProgramFiles(x86)" + /// which is only defined on 64-bit Windows. + /// + public void SupportParensInEnvironmentVariables() + { + var folder = TestData.Get(@"TestData", "Preprocessor"); + + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var context = serviceProvider.GetService(); + context.SourcePath = Path.Combine(folder, "EnvParens.wxs"); + + var preprocessor = serviceProvider.GetService(); + var result = preprocessor.Preprocess(context); + Assert.NotNull(result.Document); + } + + [Fact] + public void VariableRedefinitionIsAWarning() + { + var folder = TestData.Get(@"TestData\Variables"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(warningsAsErrors: false, new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var warning = result.Messages.Where(message => message.Id == (int)WarningMessages.Ids.VariableDeclarationCollision); + Assert.Single(warning); + } + } + + [Fact] + public void ForEachLoopsWork() + { + var folder = TestData.Get(@"TestData\ForEach"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + } + } + + [Fact] + public void NonterminatedPreprocessorInstructionShowsSourceLineNumber() + { + var folder = TestData.Get(@"TestData\BadIf"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + Assert.Equal(147, result.ExitCode); + Assert.StartsWith("Found a ", result.Messages.Single().ToString()); + } + } + } +} + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs new file mode 100644 index 00000000..e4d95b5d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.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 WixToolsetTest.CoreIntegration +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class RegistryFixture + { + [Fact] + public void PopulatesRegistryTableFromRegistryValue() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RegistryValue.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Registry:reg04OIwIchl.9ZTjisTT6NzGSsQSM\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMiscComponent", + "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent", + }, results); + } + } + + [Fact] + public void PopulatesRegistryTableFromRegistryValueMultiString() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RegistryValueMultiString.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Registry:regitq_Wx9LfvJuNSc2un6gIHAzr4A\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMultiStringComponent", + "Registry:regmeTJMpOD41igfxhTcUVZ7kNG1Mo\t2\tPath\\To\\Key\t\ta[~]b[~][~]c[~]\tMultiStringComponent", + }, results); + } + } + + [Fact] + public void DuplicateRegistryValueIdsAreDetectedSmoothly() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "DuplicateRegistryValueIds.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }, out var messages); + + Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol).Count()); + Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol2).Count()); + } + } + + [Fact] + public void PopulatesRegistryTableFromRemoveRegistryKey() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RemoveRegistryKey.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Registry:RemoveAKeyName\t2\tAKeyName\t-\t\tRemoveRegistryKeyComp", + }, results); + } + } + + [Fact] + public void PopulatesRegistryTableWithoutExtraBackslash() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Registry", "RegistryKeyEndingWithBackslash.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Registry" }); + WixAssert.CompareLineByLine(new[] + { + "Registry:reg1\t2\tSoftware\\WBM\\WB\t*\t\tMiscComponent", + "Registry:reg2\t2\tSoftware\\WBM\\WB\tInstallationPath\t[INSTALLFOLDER]\tMiscComponent", + }, results); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs new file mode 100644 index 00000000..9e19abb0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs @@ -0,0 +1,41 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class RollbackBoundaryFixture + { + [Fact] + public void CanStartChainWithRollbackBoundary() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var exePath = Path.Combine(baseFolder, @"bin\test.exe"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "RollbackBoundary", "BeginningOfChain.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-o", exePath, + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(exePath)); + } + } + + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs new file mode 100644 index 00000000..3b6c50c0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs @@ -0,0 +1,78 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ShortcutFixture + { + [Fact] + public void CanBuildShortcutNameWithShortname() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Shortcut", "ShortcutSameNameShortName.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + var results = Query.QueryDatabase(msiPath, new[] { "Shortcut" }); + WixAssert.CompareLineByLine(new[] + { + "Shortcut:sctzJpBYlrhdx4Mm9Xh41X0KPWYiX0\tINSTALLFOLDER\tDaName\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", + }, results); + } + } + + [Fact] + public void PopulatesMsiShortcutPropertyTable() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Shortcut", "ShortcutProperty.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty", "Shortcut" }); + WixAssert.CompareLineByLine(new[] + { + "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue", + "Shortcut:TheShortcut\tINSTALLFOLDER\td\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t", + }, results); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs new file mode 100644 index 00000000..15276b18 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs @@ -0,0 +1,100 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using System.Xml.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class SoftwareTagFixture + { + private static readonly XNamespace BurnManifestNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; + private static readonly XNamespace SwidTagNamespace = "http://standards.iso.org/iso/19770/-2/2009/schema.xsd"; + + [Fact] + public void CanBuildPackageWithTag() + { + var folder = TestData.Get(@"TestData\ProductTag"); + var build = new Builder(folder, null, new[] { folder }); + + var results = build.BuildAndQuery(Build, "File", "SoftwareIdentificationTag"); + + var replacePackageCodeStart = results[2].IndexOf("\tmsi:package/") + "\tmsi:package/".Length; + var replacePackageCodeEnd = results[2].IndexOf("\t", replacePackageCodeStart); + results[2] = results[2].Substring(0, replacePackageCodeStart) + "???" + results[2].Substring(replacePackageCodeEnd); + WixAssert.CompareLineByLine(new[] + { + "File:filF5_pLhBuF5b4N9XEo52g_hUM5Lo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\texample.txt\t20\t\t\t512\t1", + "File:tagEYRYWwOt95punO7qPPAQ9p1GBpY\ttagEYRYWwOt95punO7qPPAQ9p1GBpY\trdcfonyt.swi|~TagTestPackage.swidtag\t449\t\t\t1\t2", + "SoftwareIdentificationTag:tagEYRYWwOt95punO7qPPAQ9p1GBpY\twixtoolset.org\tmsi:package/???\tmsi:upgrade/047730A5-30FE-4A62-A520-DA9381B8226A\t" + }, results.ToArray()); + } + + [Fact] + public void CanBuildBundleWithTag() + { + var testDataFolder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(testDataFolder, "ProductTag", "PackageWithTag.wxs"), + Path.Combine(testDataFolder, "ProductTag", "PackageComponents.wxs"), + "-loc", Path.Combine(testDataFolder, "ProductTag", "Package.en-us.wxl"), + "-bindpath", Path.Combine(testDataFolder, "ProductTag"), + "-intermediateFolder", Path.Combine(intermediateFolder, "package"), + "-o", Path.Combine(baseFolder, "package", @"test.msi") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(testDataFolder, "BundleTag", "BundleWithTag.wxs"), + "-bindpath", Path.Combine(testDataFolder, "BundleTag"), + "-bindpath", Path.Combine(baseFolder, "package"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + + using (var ouput = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) + { + var badata = ouput.GetDataStream("wix-burndata.xml"); + var doc = XDocument.Load(badata); + + var swidTag = doc.Root.Element(BurnManifestNamespace + "Registration").Element(BurnManifestNamespace + "SoftwareTag").Value; + + var swidTagPath = Path.Combine(baseFolder, "test.swidtag"); + File.WriteAllText(swidTagPath, swidTag); + + var docTag = XDocument.Load(swidTagPath); + var title = docTag.Root.Attribute("name").Value; + var version = docTag.Root.Attribute("version").Value; + Assert.Equal("~TagTestBundle", title); + Assert.Equal("4.3.2.1", version); + } + } + } + + private static void Build(string[] args) + { + var result = WixRunner.Execute(args) + .AssertSuccess(); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe new file mode 100644 index 00000000..2a4f423f Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs new file mode 100644 index 00000000..b34c547d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs new file mode 100644 index 00000000..4dd701f0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs new file mode 100644 index 00000000..6b9fe013 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs new file mode 100644 index 00000000..e255c83d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs new file mode 100644 index 00000000..c17d9848 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi new file mode 100644 index 00000000..ea1296c3 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs new file mode 100644 index 00000000..f800264d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs new file mode 100644 index 00000000..8be5abb2 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs new file mode 100644 index 00000000..c345305d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs new file mode 100644 index 00000000..e0c84c63 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs new file mode 100644 index 00000000..45cc7114 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe new file mode 100644 index 00000000..18129b73 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest new file mode 100644 index 00000000..0da1f6d0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs new file mode 100644 index 00000000..3caa20ff --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs new file mode 100644 index 00000000..1d7ebb94 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs new file mode 100644 index 00000000..2a75e3d7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs new file mode 100644 index 00000000..a2d49b18 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs new file mode 100644 index 00000000..0c350042 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs new file mode 100644 index 00000000..4fe7e097 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs new file mode 100644 index 00000000..5ebe5472 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs @@ -0,0 +1,6 @@ + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs new file mode 100644 index 00000000..78f3ebd3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs new file mode 100644 index 00000000..92a9602f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs new file mode 100644 index 00000000..a00874ce --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs new file mode 100644 index 00000000..c717680b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs new file mode 100644 index 00000000..fc53c4a2 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs new file mode 100644 index 00000000..6cf8528e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs new file mode 100644 index 00000000..c3528a67 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt new file mode 100644 index 00000000..3b862323 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt @@ -0,0 +1 @@ +This is test.txt diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs new file mode 100644 index 00000000..5b41e807 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs new file mode 100644 index 00000000..7f5ea456 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs new file mode 100644 index 00000000..e52302d4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs new file mode 100644 index 00000000..eefae822 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs new file mode 100644 index 00000000..fd8d3698 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs @@ -0,0 +1,8 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs new file mode 100644 index 00000000..c5a93eb3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs new file mode 100644 index 00000000..7303a05a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs new file mode 100644 index 00000000..f44fb7bc --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll new file mode 100644 index 00000000..64061ea0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll @@ -0,0 +1 @@ +This is fakeba.dll. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs new file mode 100644 index 00000000..78e754c1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs new file mode 100644 index 00000000..18cdfd32 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs new file mode 100644 index 00000000..a93b23ef --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs new file mode 100644 index 00000000..e738b407 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs new file mode 100644 index 00000000..b0bde4f6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs new file mode 100644 index 00000000..514f9243 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs new file mode 100644 index 00000000..c0dc9bc0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi new file mode 100644 index 00000000..2cd10f09 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs new file mode 100644 index 00000000..15a9a0ce --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs new file mode 100644 index 00000000..db07af2c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs new file mode 100644 index 00000000..7f17b538 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt new file mode 100644 index 00000000..8c874ae7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt @@ -0,0 +1 @@ +This is other.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs new file mode 100644 index 00000000..a0e921cb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs new file mode 100644 index 00000000..d7b5bdc0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs new file mode 100644 index 00000000..beaf70bf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs new file mode 100644 index 00000000..ec757c5d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs new file mode 100644 index 00000000..e175a18f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs new file mode 100644 index 00000000..0c5f8c7e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs new file mode 100644 index 00000000..28900e55 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs new file mode 100644 index 00000000..c7f549a3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs new file mode 100644 index 00000000..90d66cc3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs new file mode 100644 index 00000000..be991c65 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs new file mode 100644 index 00000000..c64ef143 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs new file mode 100644 index 00000000..ff8741cf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs new file mode 100644 index 00000000..f8ce1c38 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs new file mode 100644 index 00000000..10c4f91f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs new file mode 100644 index 00000000..d7d86008 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs new file mode 100644 index 00000000..d32e808c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs new file mode 100644 index 00000000..08a9c470 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl new file mode 100644 index 00000000..bc2ccf04 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl @@ -0,0 +1,7 @@ + + + + This is row one + This is row two + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs new file mode 100644 index 00000000..e1da74f8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt new file mode 100644 index 00000000..97f701ce --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt @@ -0,0 +1 @@ +This is file1.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt new file mode 100644 index 00000000..46493186 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt @@ -0,0 +1 @@ +This is file2.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs new file mode 100644 index 00000000..71553e2a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab new file mode 100644 index 00000000..125eeb2c Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi new file mode 100644 index 00000000..81335041 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs new file mode 100644 index 00000000..246bcafc --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab new file mode 100644 index 00000000..125eeb2c Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi new file mode 100644 index 00000000..9cb6d6bc Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs new file mode 100644 index 00000000..81915759 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab new file mode 100644 index 00000000..125eeb2c Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi new file mode 100644 index 00000000..762b136c Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs new file mode 100644 index 00000000..7c5fe3cf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm new file mode 100644 index 00000000..2a7b5e3a Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs new file mode 100644 index 00000000..2f277956 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs new file mode 100644 index 00000000..6df8a7c0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs new file mode 100644 index 00000000..4d188d3a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs new file mode 100644 index 00000000..9c3a9690 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs new file mode 100644 index 00000000..ec6e62df --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs new file mode 100644 index 00000000..3e7887c4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs new file mode 100644 index 00000000..6e9a4495 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs new file mode 100644 index 00000000..50cf6850 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs new file mode 100644 index 00000000..cc87b49f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs new file mode 100644 index 00000000..a58b68c8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs new file mode 100644 index 00000000..01767abb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs new file mode 100644 index 00000000..de9744a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl new file mode 100644 index 00000000..066e16bb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl @@ -0,0 +1,9 @@ + + + + + A newer version of [ProductName] is already installed. + MsiPackage + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs new file mode 100644 index 00000000..287085e8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs new file mode 100644 index 00000000..88a4ac81 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs new file mode 100644 index 00000000..5c84f33e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs new file mode 100644 index 00000000..7f17b538 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs new file mode 100644 index 00000000..e57180f7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs new file mode 100644 index 00000000..0b094860 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs new file mode 100644 index 00000000..be302720 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs new file mode 100644 index 00000000..6fb9ef05 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs new file mode 100644 index 00000000..6ac48963 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs new file mode 100644 index 00000000..8fff563e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs new file mode 100644 index 00000000..2a75e3d7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs new file mode 100644 index 00000000..1de84e81 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs new file mode 100644 index 00000000..0bd80c50 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs new file mode 100644 index 00000000..7a0485ed --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi new file mode 100644 index 00000000..03885e3e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi new file mode 100644 index 00000000..f2df3b86 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi @@ -0,0 +1,4 @@ + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs new file mode 100644 index 00000000..7826d673 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl new file mode 100644 index 00000000..f7453566 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl new file mode 100644 index 00000000..ef287da7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl new file mode 100644 index 00000000..10ebf2c5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs new file mode 100644 index 00000000..13c79e90 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl new file mode 100644 index 00000000..596ee077 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl @@ -0,0 +1,7 @@ + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs new file mode 100644 index 00000000..dfae2157 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs new file mode 100644 index 00000000..4fd3493a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs new file mode 100644 index 00000000..e7492db4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt new file mode 100644 index 00000000..ad9cdcb5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt @@ -0,0 +1 @@ +This is a1.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt new file mode 100644 index 00000000..d5de23de --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt @@ -0,0 +1 @@ +This is a2.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt new file mode 100644 index 00000000..88bc4a56 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt @@ -0,0 +1 @@ +This is b1.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt new file mode 100644 index 00000000..38525276 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt @@ -0,0 +1 @@ +This is b2.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs new file mode 100644 index 00000000..e72b6402 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs new file mode 100644 index 00000000..e6527a36 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs new file mode 100644 index 00000000..f1c939db --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs new file mode 100644 index 00000000..dbca3393 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll new file mode 100644 index 00000000..b3cf17d8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll @@ -0,0 +1 @@ +This is a fake BA DLL diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu new file mode 100644 index 00000000..d63da4be --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu @@ -0,0 +1 @@ +This is a fake MSU package diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs new file mode 100644 index 00000000..2b1a1a0f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs new file mode 100644 index 00000000..82797ebe --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs new file mode 100644 index 00000000..0bf0e963 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs new file mode 100644 index 00000000..5e1b99ff --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs new file mode 100644 index 00000000..f220d81a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs new file mode 100644 index 00000000..149870a4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs new file mode 100644 index 00000000..3c361c49 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs new file mode 100644 index 00000000..8e62f660 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs new file mode 100644 index 00000000..f79da874 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs new file mode 100644 index 00000000..dda306cf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt @@ -0,0 +1 @@ +This is A v1.0.0 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt new file mode 100644 index 00000000..b1f0bc01 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt @@ -0,0 +1 @@ +This ia A v1.0.1 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt new file mode 100644 index 00000000..ece55fec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt @@ -0,0 +1 @@ +This is B v1.0.0 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt new file mode 100644 index 00000000..cf3372fd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt @@ -0,0 +1 @@ +This ia B v1.0.1 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs new file mode 100644 index 00000000..c9dcdd72 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs new file mode 100644 index 00000000..d39170c0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs new file mode 100644 index 00000000..5cb8ede8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs new file mode 100644 index 00000000..52e87f64 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt @@ -0,0 +1 @@ +This is A v1.0.0 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs new file mode 100644 index 00000000..dab959d5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs new file mode 100644 index 00000000..889b1220 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs new file mode 100644 index 00000000..4a8f5630 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs new file mode 100644 index 00000000..7fb3cb56 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs new file mode 100644 index 00000000..201d177b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs new file mode 100644 index 00000000..62a89af3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs new file mode 100644 index 00000000..1b01774c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs new file mode 100644 index 00000000..f0630ead --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs new file mode 100644 index 00000000..f9d2a55a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt new file mode 100644 index 00000000..6fd385bd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt @@ -0,0 +1 @@ +This is A v1.0.0 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt new file mode 100644 index 00000000..b1f0bc01 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt @@ -0,0 +1 @@ +This ia A v1.0.1 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt new file mode 100644 index 00000000..ece55fec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt @@ -0,0 +1 @@ +This is B v1.0.0 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt new file mode 100644 index 00000000..cf3372fd --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt @@ -0,0 +1 @@ +This ia B v1.0.1 diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs new file mode 100644 index 00000000..bc460636 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs new file mode 100644 index 00000000..e3845382 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs new file mode 100644 index 00000000..52e87f64 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs new file mode 100644 index 00000000..dc94d688 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs new file mode 100644 index 00000000..544b80ec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs new file mode 100644 index 00000000..f8f38ea6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs new file mode 100644 index 00000000..5263cbd4 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs new file mode 100644 index 00000000..9c37a27d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs new file mode 100644 index 00000000..68d115c5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs @@ -0,0 +1,4 @@ + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs new file mode 100644 index 00000000..37a2c462 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs new file mode 100644 index 00000000..5bf78a9d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs new file mode 100644 index 00000000..f62bbd0e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs new file mode 100644 index 00000000..433be7f0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs new file mode 100644 index 00000000..0621eb8d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs new file mode 100644 index 00000000..d3b31db5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs new file mode 100644 index 00000000..5166be16 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs new file mode 100644 index 00000000..8f4f661d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs new file mode 100644 index 00000000..452aea69 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs new file mode 100644 index 00000000..1fb2e906 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs new file mode 100644 index 00000000..fe6e179e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs new file mode 100644 index 00000000..c62c571d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs new file mode 100644 index 00000000..a55a1e18 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs new file mode 100644 index 00000000..3218295b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs new file mode 100644 index 00000000..ecfccfcb --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs new file mode 100644 index 00000000..bbad63e6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt new file mode 100644 index 00000000..1970cae6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt @@ -0,0 +1 @@ +This is a\test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt new file mode 100644 index 00000000..fa2c7082 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt @@ -0,0 +1 @@ +This is b\test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt new file mode 100644 index 00000000..1c0cbda6 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt @@ -0,0 +1 @@ +This is c\test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs new file mode 100644 index 00000000..d5379e7b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi new file mode 100644 index 00000000..7f894091 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs new file mode 100644 index 00000000..65cba20e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs new file mode 100644 index 00000000..d3f8accf --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs new file mode 100644 index 00000000..7e8f2e99 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs new file mode 100644 index 00000000..f16fce0d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs new file mode 100644 index 00000000..da1e4f38 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs new file mode 100644 index 00000000..27f2ab9b --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs new file mode 100644 index 00000000..d704bbf1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi new file mode 100644 index 00000000..8737f3c2 Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl new file mode 100644 index 00000000..bc1dee83 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl @@ -0,0 +1,10 @@ + + + + + + ~TestBundle + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs new file mode 100644 index 00000000..21749c07 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs new file mode 100644 index 00000000..f5fe9885 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs new file mode 100644 index 00000000..48f53ae3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll new file mode 100644 index 00000000..0e461ba8 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll @@ -0,0 +1 @@ +This is Shared.dll. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt new file mode 100644 index 00000000..8b986220 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt @@ -0,0 +1 @@ +This is test.txt \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll new file mode 100644 index 00000000..970efdf0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll @@ -0,0 +1 @@ +This is a fakeba.dll \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi new file mode 100644 index 00000000..0722d60e Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm new file mode 100644 index 00000000..6f179aba Binary files /dev/null and b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm differ diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs new file mode 100644 index 00000000..3c999812 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl new file mode 100644 index 00000000..c74e86a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl @@ -0,0 +1,10 @@ + + + + + + Example Company + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj new file mode 100644 index 00000000..597d4318 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj @@ -0,0 +1,48 @@ + + + + Debug + x86 + 0.9 + 27df04c6-3cef-4b9a-bac6-4e78d188384f + MergeModule1 + Module + MergeModule1 + MergeModule1 + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + Debug + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + Debug + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + + + + + + + + + + FgwepExtension.wixext + $(WixExtDir)\FgwepExtension.wixext.dll + + + + + + + + \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs new file mode 100644 index 00000000..8317e7af --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs new file mode 100644 index 00000000..cad1f049 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs new file mode 100644 index 00000000..0d459f02 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs new file mode 100644 index 00000000..d7b5bdc0 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs new file mode 100644 index 00000000..b8e9f59c --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs new file mode 100644 index 00000000..baa0c6b1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl new file mode 100644 index 00000000..c74e86a7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl @@ -0,0 +1,10 @@ + + + + + + Example Company + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs new file mode 100644 index 00000000..f4ce9c48 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs new file mode 100644 index 00000000..669de6ec --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl new file mode 100644 index 00000000..77d46861 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl @@ -0,0 +1,13 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + Tahoma + 8 + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs new file mode 100644 index 00000000..a591fdd9 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs new file mode 100644 index 00000000..fa64f98f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs new file mode 100644 index 00000000..587d8e95 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs new file mode 100644 index 00000000..59839f30 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs new file mode 100644 index 00000000..7e459e9a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs new file mode 100644 index 00000000..7de55810 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs @@ -0,0 +1,31 @@ + + + + + + + += 4 AND $(sys.WIXMAJORVERSION) < 5 ?> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs new file mode 100644 index 00000000..f8203a07 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs new file mode 100644 index 00000000..df867923 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt new file mode 100644 index 00000000..eab3a9b5 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt @@ -0,0 +1 @@ +This is test2.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs new file mode 100644 index 00000000..7e6eee9f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs new file mode 100644 index 00000000..b29a785f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs new file mode 100644 index 00000000..7d1a4ae1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll new file mode 100644 index 00000000..fd36c768 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll @@ -0,0 +1 @@ +This is alpha\foo.dll. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll new file mode 100644 index 00000000..292925c7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll @@ -0,0 +1 @@ +This is mips\foo.dll. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll new file mode 100644 index 00000000..663e9d99 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll @@ -0,0 +1 @@ +This is powerpc\foo.dll. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs new file mode 100644 index 00000000..5330305e --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs @@ -0,0 +1,62 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using WixToolset.Core.TestPackage; + using Xunit; + + public class TestXmlFixture + { + [Fact] + public void ChangesIgnoredAttributesToStarToHelpMakeTestsLessFragile() + { + var original = @" + + + + +"; + var expected = ""; + var ignored = new Dictionary> { { "Target", new List { "One", "Two", "Missing" } } }; + Assert.Equal(expected, original.GetTestXml(ignored)); + } + + [Fact] + public void OutputsSingleQuotesSinceDoubleQuotesInCsharpLiteralStringsArePainful() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesAllNamespacesToReduceTyping() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesUnnecessaryWhitespaceToAvoidLineEndingIssues() + { + var original = @" + + + + +"; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesXmlDeclarationToReduceTyping() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs new file mode 100644 index 00000000..15e5d334 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs @@ -0,0 +1,75 @@ + +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using WixToolset.Core; + using WixToolset.Data; + using WixToolset.Data.Bind; + using WixToolset.Extensibility.Services; + using Xunit; + + public class VariableResolverFixture + { + [Fact] + public void CanRecursivelyResolveVariables() + { + var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider(); + var variableResolver = serviceProvider.GetService(); + + var variables = new Dictionary() + { + { "ProductName", new BindVariable() { Id = "ProductName", Value = "Localized Product Name" } }, + { "ProductNameEdition", new BindVariable() { Id = "ProductNameEdition", Value = "!(loc.ProductName) Enterprise Edition" } }, + { "ProductNameEditionVersion", new BindVariable() { Id = "ProductNameEditionVersion", Value = "!(loc.ProductNameEdition) v1.2.3" } }, + }; + + var localization = new Localization(0, null, "x-none", variables, new Dictionary()); + + variableResolver.AddLocalization(localization); + + var result = variableResolver.ResolveVariables(null, "These are not the loc strings you're looking for."); + Assert.Equal("These are not the loc strings you're looking for.", result.Value); + Assert.False(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductName)"); + Assert.Equal("Welcome to Localized Product Name", result.Value); + Assert.True(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEdition)"); + Assert.Equal("Welcome to Localized Product Name Enterprise Edition", result.Value); + Assert.True(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion)"); + Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3", result.Value); + Assert.True(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !(bind.property.ProductVersion)"); + Assert.Equal("Welcome to !(bind.property.ProductVersion)", result.Value); + Assert.False(result.UpdatedValue); + Assert.True(result.DelayedResolve); + + var withUnknownLocString = "Welcome to !(loc.UnknownLocalizationVariable)"; + Assert.Throws(() => variableResolver.ResolveVariables(null, withUnknownLocString)); + + result = variableResolver.ResolveVariables(null, withUnknownLocString, errorOnUnknown: false); + Assert.Equal(withUnknownLocString, result.Value); + Assert.False(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable)"); + Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable)", result.Value); + Assert.True(result.UpdatedValue); + + result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)"); + Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value); + Assert.True(result.UpdatedValue); + Assert.True(result.DelayedResolve); + + result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion) !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)"); + Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3 !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value); + Assert.True(result.UpdatedValue); + Assert.True(result.DelayedResolve); + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs new file mode 100644 index 00000000..c5b6c261 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs @@ -0,0 +1,63 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class WarningFixture + { + [Fact] + public void SuppressedWarningsWithWarningAsErrorsAreNotErrors() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(warningsAsErrors: true, new[] + { + "build", + "-sw1152", + Path.Combine(folder, "CanonicalizeName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + } + } + + [Fact] + public void WarningsAsErrorsTreatsWarningsAsErrors() + { + var folder = TestData.Get(@"TestData\Payload"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(warningsAsErrors: true, new[] + { + "build", + Path.Combine(folder, "CanonicalizeName.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + Assert.Equal((int)WarningMessages.Ids.PathCanonicalized, result.ExitCode); + + var message = Assert.Single(result.Messages); + Assert.Equal(MessageLevel.Warning, message.Level); // TODO: is this right? + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj new file mode 100644 index 00000000..fc62e932 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -0,0 +1,32 @@ + + + + + + netcoreapp3.1 + false + embedded + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs new file mode 100644 index 00000000..942f253f --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs @@ -0,0 +1,205 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Example.Extension; + using Xunit; + + public class WixiplFixture + { + [Fact] + public void CanBuildSingleFile() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixiplPath = Path.Combine(intermediateFolder, @"test.wixipl"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixiplPath, + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixiplPath); + + Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); + Assert.False(intermediate.HasLevel(IntermediateLevels.Resolved)); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(intermediateFolder, @"test.wixipl"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + + Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); + + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().First(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CannotBuildWithSourceFileAndWixipl() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixipl") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + Path.Combine(intermediateFolder, @"test.wixipl"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + Assert.Equal((int)ErrorMessages.Ids.WixiplSourceFileIsExclusive, result.ExitCode); + } + } + + [Fact] + public void CanBuildMsiUsingExtensionLibrary() + { + var folder = TestData.Get(@"TestData\Wixipl"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + "-ext", extensionPath, + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi"), + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + { + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + + { + var binary = section.Symbols.OfType().Single(); + var path = binary[BinarySymbolFields.Data].AsPath().Path; + Assert.StartsWith(Path.Combine(baseFolder, @"obj\Example.Extension"), path); + Assert.EndsWith(@"wix-ir\example.txt", path); + Assert.Equal(@"BinFromWir", binary.Id.Id); + } + } + } + + [Fact] + public void CanBuildWixiplUsingExtensionLibrary() + { + var folder = TestData.Get(@"TestData\Wixipl"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + "-ext", extensionPath, + Path.Combine(folder, "Package.wxs"), + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixipl"), + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(intermediateFolder, @"test.wixipl"), + "-ext", extensionPath, + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi"), + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + { + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + + { + var binary = section.Symbols.OfType().Single(); + var path = binary[BinarySymbolFields.Data].AsPath().Path; + Assert.StartsWith(Path.Combine(baseFolder, @"obj\test"), path); + Assert.EndsWith(@"wix-ir\example.txt", path); + Assert.Equal(@"BinFromWir", binary.Id.Id); + } + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs new file mode 100644 index 00000000..d7296cfe --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs @@ -0,0 +1,316 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System; + using System.IO; + using System.Linq; + using Example.Extension; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class WixlibFixture + { + [Fact] + public void CanBuildSimpleBundleUsingWixlib() + { + var folder = TestData.Get(@"TestData\SimpleBundle"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "MultiFileBundle.wxs"), + "-loc", Path.Combine(folder, "Bundle.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + } + } + + [Fact] + public void CanBuildWixlibWithBinariesFromNamedBindPaths() + { + var folder = TestData.Get(@"TestData\WixlibWithBinaries"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-bf", + "-bindpath", Path.Combine(folder, "data"), + // Use names that aren't excluded in default .gitignores. + "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}", + "-bindpath", $"MipsBits={Path.Combine(folder, "data", "mips")}", + "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}", + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var wixlib = Intermediate.Load(wixlibPath); + var binarySymbols = wixlib.Sections.SelectMany(s => s.Symbols).OfType().ToList(); + Assert.Equal(3, binarySymbols.Count); + Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll")); + Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-1")); + Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-2")); + } + } + + [Fact] + public void CanBuildSingleFileUsingWixlib() + { + var folder = TestData.Get(@"TestData\SingleFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var wixlib = Intermediate.Load(wixlibPath); + + Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); + Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); + Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); + Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + + Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); + Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); + + var section = intermediate.Sections.Single(); + + var wixFile = section.Symbols.OfType().First(); + Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"test.txt", wixFile[FileSymbolFields.Source].PreviousValue.AsPath().Path); + } + } + + [Fact] + public void CanOverridePathWixVariable() + { + var folder = TestData.Get(@"TestData\WixVariableOverride"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-bf", + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var wixlib = Intermediate.Load(wixlibPath); + + Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled)); + Assert.True(wixlib.HasLevel(IntermediateLevels.Combined)); + Assert.False(wixlib.HasLevel(IntermediateLevels.Linked)); + Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved)); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb")); + + Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled)); + Assert.False(intermediate.HasLevel(IntermediateLevels.Combined)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Linked)); + Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved)); + + var section = intermediate.Sections.Single(); + + var wixFile = section.Symbols.OfType().First(); + Assert.Equal(Path.Combine(folder, @"data\test2.txt"), wixFile.Data.Path); + } + } + + [Fact] + public void CanBuildWithExtensionUsingWixlib() + { + var folder = TestData.Get(@"TestData\ExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-ext", extensionPath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"test.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"test.wixlib"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbol = section.Symbols.OfType().Single(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path); + + var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single(); + Assert.Equal("Foo", example.Id?.Id); + Assert.Equal("Bar", example[0].AsString()); + } + } + + [Fact] + public void CanBuildWithExtensionUsingMultipleWixlibs() + { + var folder = TestData.Get(@"TestData\ComplexExampleExtension"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "PackageComponents.wxs"), + "-ext", extensionPath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"components.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "OtherComponents.wxs"), + "-ext", extensionPath, + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"other.wixlib") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Package.wxs"), + "-loc", Path.Combine(folder, "Package.en-us.wxl"), + "-lib", Path.Combine(intermediateFolder, @"components.wixlib"), + "-lib", Path.Combine(intermediateFolder, @"other.wixlib"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "data"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(intermediateFolder, @"bin\test.msi") + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb")); + var section = intermediate.Sections.Single(); + + var fileSymbols = section.Symbols.OfType().OrderBy(t => Path.GetFileName(t.Source.Path)).ToArray(); + Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbols[0][FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"example.txt", fileSymbols[0][FileSymbolFields.Source].PreviousValue.AsPath().Path); + Assert.Equal(Path.Combine(folder, @"data\other.txt"), fileSymbols[1][FileSymbolFields.Source].AsPath().Path); + Assert.Equal(@"other.txt", fileSymbols[1][FileSymbolFields.Source].PreviousValue.AsPath().Path); + + var examples = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).ToArray(); + Assert.Equal(new string[] { "Foo", "Other" }, examples.Select(t => t.Id?.Id).ToArray()); + Assert.Equal(new[] { "Bar", "Value" }, examples.Select(t => t[0].AsString()).ToArray()); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs new file mode 100644 index 00000000..57351b27 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs @@ -0,0 +1,81 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using Xunit; + + public class WixlibQueryFixture + { + [Fact] + public void UpgradeProducesReferenceToRemoveExistingProducts() + { + var folder = TestData.Get(@"TestData\Upgrade"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "DetectOnly.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var wixSimpleRefSymbols = allSymbols.OfType(); + var repRef = wixSimpleRefSymbols.Where(t => t.Table == "WixAction" && + t.PrimaryKeys == "InstallExecuteSequence/RemoveExistingProducts") + .SingleOrDefault(); + Assert.NotNull(repRef); + } + } + + [Fact] + public void TypeLibLanguageAsStringReturnsZero() + { + var folder = TestData.Get(@"TestData\TypeLib"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Language0.wxs"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath + }); + + result.AssertSuccess(); + + var intermediate = Intermediate.Load(wixlibPath); + var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols); + var typeLibSymbol = allSymbols.OfType() + .SingleOrDefault(); + Assert.NotNull(typeLibSymbol); + + var fields = typeLibSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool + ? field.AsNullableNumber()?.ToString() + : field?.AsString()) + .ToList(); + Assert.Equal("0", fields[1]); + } + } + } +} diff --git a/version.json b/version.json deleted file mode 100644 index 5f857771..00000000 --- a/version.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "4.0", - "publicReleaseRefSpec": [ - "^refs/heads/master$" - ], - "cloudBuild": { - "buildNumber": { - "enabled": true - } - } -} -- cgit v1.2.3-55-g6feb